6年的老项目迁移vite2,提速几十倍,真香

11,802 阅读5分钟

vite-dev.png

背景

gou系统又老又大又乱,每一次的需求开发都极其难受,启动30|40几秒勉强能接受吧,毕竟一天也就这么一回,但是HMR更新也要好几秒实在是忍不了,看到了vite2就看到了曙光!盘它

先看看vue-cli3的启动编译吧...

编译-new-48803ms.png

  • 该项目为内部运营管理系统,年龄6岁+
  • 基于vue2+elementui,2年入职时将vue-cli2升级到了vue-cli3,2年后的今天迫不及待的的奔向vite2
  • 仅迁移开发环境(我的痛点只是开发环境,对于生产环境各位自行考虑)

痛点分析

实质上是对webpack工作原理的分析,webpack在开发环境的工作流大致如下(个人见解不喜勿喷):

查找入口文件 => 分析依赖关系 => 转化模块函数 => 打包生成bundle => node服务启动

所以随着项目越来越大,速度也就越来越慢...

至于HMR也是同理,只不过HMR是将当前文件作为入口,进行rebuild,涉及的相关依赖都需要重载

为什么是Vite

  • vite是基于esm实现的,主流浏览器已支持,所以不需要对文件进行打包编译
  • 项目启动超快(迁移后简单的概算数据是从30s 提升到 1s。30倍?3000%?一点都不夸张...)
  • 还是基于esmHMR很快,不需要编译重载,速度可以用一闪而过来形容...

vite大致工作流:

启动服务 => 查找入口文件(module script) => 浏览器发送请求 => vite劫持请求处理返回文件到浏览器

开盘,踏上迁移之路

  1. 安装相关npm包

    npm i vite vite-plugin-vue vite-plugin-html -D
    
    • vite-plugin-vue,用于构建vue,加载jsx
    • vite-plugin-html,用于入口文件模板注入
  2. package.json文件中,新增一个vite启动命令:

    "vite": "cross-env VITE_NODE_ENV=dev vite"
    
  3. 根目录新建vite.config.js文件

  4. public下的index.html复制一份到根目录

    仅迁移开发环境,public下仍然需要index.html,支持开发环境下vite和webpack两种模式

  5. 修改根目录下index.html(vite启动的入口文件,必须是根目录)

    <% if (htmlWebpackPlugin.options.isVite) { %>
      <script type="module" src="/src/main.js"></script>
    <%}%>
    

    htmlWebpackPlugin在vite.config.js注入,isVite用于标识是否是vite启动

    import { injectHtml } from 'vite-plugin-html';
    export default defineConfig({
      plugins:[
        injectHtml({
          injectData: {
            htmlWebpackPlugin: {
              options: {
                isVite: true
              }
            },
            title: '运营管理平台'
          }
        })
      ]
    })
    ​
    
  6. 完整vite.config.js 配置

    import { defineConfig } from 'vite'
    import path from 'path'
    import fs from 'fs'
    import { createVuePlugin } from 'vite-plugin-vue2'
    import { injectHtml, minifyHtml } from 'vite-plugin-html'
    import dotenv from 'dotenv'try {
        // 根据环境变量加载环境变量文件
        const VITE_NODE_ENV = process.env.VITE_NODE_ENV
        const envLocalSuffix = VITE_NODE_ENV === 'dev' ? '.local' : ''
        const file = dotenv.parse(fs.readFileSync(`./.env.${VITE_NODE_ENV}${envLocalSuffix}`), {
            debug: true
        })
        for (const key in file) {
            process.env[key] = file[key]
        }
    } catch (e) {
        console.error(e)
    }
    ​
    const resolve = (dir) => {
        return path.join(__dirname, './', dir)
    }
    export default defineConfig({
        root: './',
        publicDir: 'public',
        base: './',
        mode: 'development',
        optimizeDeps: {
            include: []
        },
        resolve: {
            alias: {
                'vendor': resolve('src/vendor'),
                '@': resolve('src'),
                '~component': resolve('src/components')
            },
            extensions: [
                '.mjs',
                '.js',
                '.ts',
                '.jsx',
                '.tsx',
                '.json',
                '.vue'
            ]
        },
        plugins: [
            createVuePlugin({
                jsx: true,
                jsxOptions: {
                    injectH: false
                }
            }),
            minifyHtml(),
            injectHtml({
                injectData: {
                    htmlWebpackPlugin: {
                        options: {
                            isVite: true
                        }
                    },
                    title: '运营管理平台'
                }
            })
        ],
        define: {
            'process.env': process.env
        },
        server: {
            host: '0.0.0.0',
            open: true,
            port: 3100,
            proxy: {}
        }
    })
    ​
    

    相关配置会在下文遇到的问题中做具体描述

迁移过程中遇到的问题

  1. Uncaught SyntaxError: The requested module 'xx.js' does not provide an export named 'xx'

    本人遇到的分以下两类情况:

    a. 一个模块只能有一个默认输出,导入默认输出时,import命令后不需要加大括号,否则会报错

    处理方式:将原先{}导入的keys,改成导入默认keyes6解构赋值

    -import { postRedeemDistUserUpdate } from '@/http-handle/api_types'
    ​
    +import api_types from '@/http-handle/api_types'
    +const { postRedeemDistUserUpdate } = api_types
    

    b. 浏览器仅支持 esm,不支持 cjs,需要将cjs改为esm (看了网文有通过cjs2esmodule处理的,但是本人应用有些场景是报错的,最后就去掉了)

    处理方式:不推荐使用cjs2esmodule,手动将module.exports更改为export

    -module.exports = {
    
    +export default {
    
  2. .vue文件扩展,最新版本的vite貌似已支持extensions添加.vue,不过还是推荐手动添加下后缀。(骚操作:正则匹配批量添加)

  3. Uncaught ReferenceError: require is not defined

    浏览器不支持cjs

    处理方式:require引用的文件都需要修改为import引用

  4. vite启动,页面空白

    处理方式:注意入口文件index.html,需要放置项目根目录

  5. vite环境下默认没有process.env,可通过define定义全局变量

    vue-cli模式下,环境变量都是读取根目录.env文件中的变量,那么vite模式下是否也可以读取.env文件中的变量最终注入到process.env中呢?

    这样不就可以两种模式共存了么?成本变小了么?

    处理方式:

    1. 安装环境变量加载工具:dotenv
    npm i dotenv -D
    
    1. 自定义全局变量process.env

      vite.config.js中配置

    define: {
      'process.env': {}
    }
    
    1. 加载环境变量,并添加到process.env

      vite.config.js中配置

      因为仅迁移开发环境,所以我这里默认是读取.local文件。

      VITE_NODE_ENV是在启动时通过cross-env注入的

import dotenv from 'dotenv'
try {
    const VITE_NODE_ENV = process.env.VITE_NODE_ENV
    const envLocalSuffix = VITE_NODE_ENV === 'dev' ? '.local' : ''
    const file = dotenv.parse(fs.readFileSync(`./.env.${VITE_NODE_ENV}${envLocalSuffix}`), {
        debug: true
    })
    console.log(file)
    for (const key in file) {
        process.env[key] = file[key]
    }
} catch (e) {
    console.error(e)
}
  1. jsx支持

    vite.config.js中配置

    plugins: [
      createVuePlugin({
        jsx: true,
        jsxOptions: {
          injectH: false
        }
    })
    
  2. webpack中require.context方法,在vite中使用import.meta.glob替换

现存问题

项目中导入/导出的功能,是纯前端实现的

require('script-loader!file-saver')
require('script-loader!@/vendor/Blob')

由于以上文件目前不支持import引入,webpack下是通过script-loader加载挂载到全局的,vite环境下未能解决。需要导入导出功能时只能切换到vue-cli模式启动服务...

如果各位大大有方案,麻烦指导指导~,实在是不想回到webpack开发了...

最后

总体迁移上并没有遇到什么疑难杂症,迁移成本还是不大的,实操1-2天,性价比很高哦,我这个项目按数据看就是几十倍的启动提效,几倍的HMR提效...各位可以在内部系统上做下尝试。

以上仅为个人实践记录,不具备指南价值,仅供参考...