likes
comments
collection
share

不修改任何现有源代码,将项目从 webpack 迁移到 vite

作者站长头像
站长
· 阅读数 45

背景

之前将公司项目开发环境从 webpack 迁移到 vite,实现了 dev 环境下使用 vite、打包使用 webpack 的共存方案。本文将讲述开发环境下 vue3 项目打包器从 webpack 迁移到 vite 过程中的所遇问题、解决方案、迁移感受,以及如何不修改任何源码完成迁移。

迁移的前提及目标

我们之前的项目大概有 10w+ 行代码,开发环境下冷启动所花费的时间大概 1 分钟多,所以迁移到 vite 就是看中了它的核心价值:快!但是迁移到 vite,也会伴随着风险:代码改动及回归成本。

作为一个大型的已上线项目,它的线上稳定性的一定比我们工程师开发时多减少一些项目启动时间的价值要高,所以如果迁移带来了很多线上问题,那便得不偿失了。

所以我们迁移过程中有前提也有目标:

  • 前提:不因为迁移打包工具引发线上问题
  • 目标:实现开发环境下的快速启动

方案

有了上述前提和目标,那我们的方案就可以从这两方面思考入手了。

  • 如何能确保实现前提?我们已有了稳定版本,那只要保证源代码不改动,线上的打包工具 webpack 及配置也不改动,就可以确保实现前提。
  • 如何实现目标?vite 的快主要是体现在开发环境,打包使用的 rollup 相比 webpack 速度上并无太明显的优势,所以我们只要开发环境下使用 vite 启动就可以实现目标。

由此得出最终方案:不改动任何现有源代码,开发环境使用 vite,线上打包使用 webpack。

迁移过程

安装 vite 及进行基础配置

  • 在终端执行下述命令,安装 vite 相关基础依赖:
    yarn add vite @vitejs/plugin-vue vite-plugin-html -D
    
  • 因为 vite 的 html 模板文件需要显示引入入口的 .js/.ts 文件,同时有一些模板变量上面的区别,为了完全不影响线上打包,在 /public 目录下新建一个 index.vite.html 文件。将 /public/index.html 文件的内容拷贝进来并添加入口文件的引用( /src/main.ts 指向项目的入口文件):
    <!DOCTYPE html>
    <html lang="">
      <!-- other code... -->
      <body>
        <!-- other code... -->
        <div id="app"></div>
    +   <script type="module" src="/src/main.ts"></script>
      </body>
    </html>
    
  • 新增 vite.config.js,内容如下:
    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import { createHtmlPlugin } from 'vite-plugin-html';
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        vue(),
        createHtmlPlugin({
          minify: true,
          /**
           * After writing entry here, you will not need to add script tags in `index.html`, the original tags need to be deleted
           * @default src/main.ts
           */
          entry: 'src/main.ts',
          /**
           * If you want to store `index.html` in the specified folder, you can modify it, otherwise no configuration is required
           * @default index.html
           */
          template: 'public/index.vite.html',
        }),
      ]
    });
    
  • package.jsonscripts 里新增一条 vite 开发启动的指令:
    {
      "scripts": {
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint",
    +   "vite": "vite"
      }
    }
    

到这里,我们基本的配置就已经完成了,现在可以通过 npm run vite 来启动 vite 开发环境了,只不过会有一大堆的报错,我们根据可能遇到的问题一个个去解决。

问题及解决方案

HtmlWebpackPlugin 变量处理

报错htmlWebpackPlugin is not defined 不修改任何现有源代码,将项目从 webpack 迁移到 vite

是因为之前在 webpack 的 HtmlWebpackPlugin 插件中配置了变量,而 vite 中没有这个插件,所以缺少这个变量。

我们先前安装了 vite-plugin-html 插件,所以可以在这个插件中配置变量来代替:

  • index.vite.html 中所有的 htmlWebpackPlugin.options.xxx 修改为 xxx,如:
    <!DOCTYPE html>
    <html lang="">
      <head>
    -   <title><%= htmlWebpackPlugin.options.title %></title>
    +   <title><%= title %></title>
      </head>
    </html>
    
    
  • vite.config.js 中添加如下内容:
    export default defineConfig({
      plugins: [
        createHtmlPlugin({
    +     inject: {
    +       data: {
    +         title: '我的项目',
    +       },
    +     },
        }),
      ]
    });
    

其他的 html 中未定义的变量亦可以通过此方案来解决。

alias 配置

报错Internal server error: Failed to resolve import "@/ok.ts" from "src/main.ts". Does the file exist? 不修改任何现有源代码,将项目从 webpack 迁移到 vite

通常我们的项目都会在 alias 中将 src 目录配置为 @ 来便于引用,所以遇到这个报错我们需要再 vite.config.js 中将之前 webpack 的 alias 配置补充进来(同时 vite 中 css 等样式文件的 alias 不需要加 ~ 前缀,所以也需要配置下 ~@):

import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: { 
    alias: { 
      '@': path.resolve(__dirname, './src'),
      '~@': path.resolve(__dirname, './src'),
      // 其他的 alias 配置...
    } 
  },
});

css 全局变量

报错Internal server error: [less] variable @primaryColor is undefined 不修改任何现有源代码,将项目从 webpack 迁移到 vite

是因为项目在 less 文件中定义了变量,并在 webpack 的配置中通过 style-resources-loader 将其设置为了全局变量。我们可以在 vite.config.js 中添加如下配置引入文件将其设置为全局变量:

// vite.coonfig.js

export default defineConfig({
  css: {
    preprocessorOptions: {
      less: {
        additionalData: `@import "src/styles/var.less";`
      },
    },
  },
});

环境变量

报错ReferenceError: VUE_APP_HOST is not defined 不修改任何现有源代码,将项目从 webpack 迁移到 vite

这是因为项目中在 .env.local 文件中设置了以 VUE_APP_XXX 开头的环境变量,我们通过可以通过在 vite.config.js 的 define 中定义为全局变量:

// vite.config.js
export default defineConfig({
  define: {
    'process.env': {
      NODE_ENV: import.meta.env,
      APP_NAME: '我的项目名称',
    },
+   VUE_APP_HOST: '"pinyin-pro.com"', // 这里需要注意定义为一个字符串 
  },
})

process 未定义

报错ReferenceError: process is not defined 不修改任何现有源代码,将项目从 webpack 迁移到 vite

这是因为 webpack 启动时会根据 node 环境将代码中的 process 变量会将值给替换,而 vite 未替换该变量,所以在浏览器环境下会报错。

我们可以通过在 vite.config.js 中将 process.env 定义成一个全局变量,将相应的属性给配置好:

// vite.config.js
export default defineConfig({
  define: {
    'process.env': {
      NODE_ENV: import.meta.env,
      APP_NAME: '我的项目名称',
    },
  },
})

使用 JSX

报错Uncaught ReferenceError: React is not defined 不修改任何现有源代码,将项目从 webpack 迁移到 vite

这是因为 react16 版本之后,babel 默认会将 .jsx/.tsx 语法转换为 react 函数,而我们需要以 vue 组件的方式来解析 .jsx/.tsx 文件,需要通过新的插件来解决:

  • 安装 @vitejs/plugin-vue-jsx 插件:
    yarn add @vitejs/plugin-vue-jsx -D
    
  • vite.config.js 文件中引入插件:
    // others
    import vueJsx from '@vitejs/plugin-vue-jsx';
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [
        vue(),
        vueJsx(),
        // others...
      ],
    });
    

CommonJS 不识别

报错ReferenceError: require is not defined 不修改任何现有源代码,将项目从 webpack 迁移到 vite

这是因为项目中通过 require() 引入了图片,webpack 支持 commonjs 语法,而 vite 开发环境是 esmodule 不支持 require。可以通过 @originjs/vite-plugin-commonjs 插件,它能解析 require 进行语法转换以支持同样效果:

  • 安装 @originjs/vite-plugin-commonjs 插件:
    yarn add @originjs/vite-plugin-commonjs -D
    
  • vite.config.js 中引入插件:
    import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
    
    export default defineConfig({
        plugins: [
            viteCommonjs()
        ]
    })
    

多模块导入

报错Uncaught ReferenceError: require is not defined 不修改任何现有源代码,将项目从 webpack 迁移到 vite

这个报错注意比前面的 ReferenceError: require is not defined 多了一个 Uncaught,是因为 @originjs/vite-plugin-commonjs 并不是对所有的 require 进行了转换,我们项目中还通过 webpack 提供的 require.context 进行了多模块导入。要解决这个问题可以通过 @originjs/vite-plugin-require-context 插件实现:

  • 安装 @originjs/vite-plugin-require-context 插件:
    yarn add @originjs/vite-plugin-require-context -D
    
  • vite.config.js 中引入插件:
    import ViteRequireContext from '@originjs/vite-plugin-require-context'
    
    export default defineConfig({
        plugins: [
            ViteRequireContext()
        ]
    })
    

其他 webpack 配置

其他的一些 webpack 配置例如 devServer 以及引用的一些 loader 和 plugin,只需要参考 vite 文档一一修改就行,由于各个团队的项目配置不同,我在这里就不展开了。需要注意的是,因为是开发环境下使用 vite,只需要适配开发环境的 webpack 配置就行,打包优化等不需要处理。

潜在隐患

上述方案中,我们通过不修改源代码 + 打包依然使用 webpack,保证了现有项目线上的稳定性:但还有一个潜在隐患:随着项目后期的迭代,因为开发环境是 vite,打包是 webpack,可能因为两种打包工具的不同导致开发和打包产物表现不同的缺陷。例如一旦你开发环境使用了 import.meta.xxx,打包后立马就会报错。

写在最后

我们当时采用此方案是因为 vite 刚发布没太久,用于正式环境有不少坑,而现在 vite 已经成为一款比较成熟的打包工具了,如果要迁移的话还是建议开发和打包都采用 vite,这种方面可以作为 webpack 迁移 vite 的短期过渡方案使用。(我们的项目现在打包也迁移到了 vite 了)

另外我们要明确,作为公司项目稳定性是第一位的,技术方案的变更需要明确能给项目带来收益。例如 webpack 迁移的 vite,是明确能够大幅优化开发环境的等待时间成本,而非看到别人都在用随大流而用。如果已知项目后期发展规模不会太大,当前项目启动时间也不长,就没有迁移的必要了。

上述迁移过程中遇到的坑只是针对我们的项目,没能包含全部的迁移坑点,大家有其他的遇到问题欢迎分享一起讨论。

最后推荐一个工具,可以将项目一键 webpack 迁移到 vite: webpack-to-vite