基于vue-cli@4.x + Vue2.x 使用Vite改造开发模式打包流程

600 阅读2分钟

痛点

随着时间的推移、业务的不断迭代,依赖、功能、代码越来越多,项目本地启动比较慢、开发热更新比较慢。从数据上来看,从打开项目到运行起来,Time≈30s(25063ms + 4588ms).

改造目标

  1. 保留原有的各模式下的打包方式。
  2. 开发模式下增加支持 Vite 构建打包
  3. 以最小破坏性为原则,尽可能减少改动,减少维护成本。

Milestones

  • 配置vite相关启动命令
  • 根据vue.config.js 无损增加 vite.config.js
  • 入口文件index.html支持Vite,保持原文件不改动
  • 处理 PoseCSS 或一些 loader, 处理全局引入公共样式文件,如 variables.less
  • 处理样式文件中导入文件路径~@开头的兼容
  • 解决CommonJS的一些引入

遇到问题

[ERROR] No matching export in "browser-external:timers" for import "setTimeout"

    node_modules/@xxx/es/vc-calendar/src/Picker.js:16:9:
      16 │ import { setTimeout } from 'timers';
         ╵          ~~~~~~~~~~

 ERROR  4:28:16 PM [vite] error while updating dependencies:                                                  16:28:16
Error: Build failed with 1 error:
node_modules/@xxx/es/vc-calendar/src/Picker.js:16:9: ERROR: No matching export in "browser-external:timers" for import "setTimeout"
    at failureErrorWithLog (/.../node_modules/esbuild/lib/main.js:1602:15)

从日志入手,看到是@xxx(包名有改动)中有个组件引用了一个timers的包。最直接的解决方案是手动安装所缺的包,但是究其原因,由于 Vitejs 不会自行填充 NodeJS 库,因此无法在浏览器中使用诸如Bufferfspath之类的核心库。

在这里多提一句,当遇到一大串报错什么的情况,很多同学都害怕去耐心分析问题。这是不能成长的。遇到错别怕,很多时候debug大半天,还不如一行一行看错误信息来得快。

vite.config.js

import { defineConfig } from 'vite';
import { createVuePlugin } from 'vite-plugin-vue2';
import { createHtmlPlugin } from 'vite-plugin-html';
import { cjs2esmVitePlugin } from 'cjs2esmodule';
// import legacy from '@vitejs/plugin-legacy';
import eslint from '@rollup/plugin-eslint';
import dotenv from 'dotenv';

const path = require( 'path' );
const resolve = (dir) => {
  return path.resolve( __dirname, dir );
};

const vueConfig = require( './vue.config.js' );
const vAlias = Object.assign( vueConfig.configureWebpack.resolve.alias, {
  '~@': resolve( 'src' ),
  '~@xxx': resolve( 'node_modules/@xxx' ),
} );

export default defineConfig( ({ mode }) => {
  dotenv.config();
  dotenv.config( { path: `./.env.${mode}` } );
  const env = process.env;
  const {
    VUE_APP_NAME: appName,
    VUE_APP_BUILD_MODE: buildMode,
    VUE_APP_BASE_URL: baseUrl,
    VUE_APP_PORT: appPort = 3001,
    VUE_APP_GATEWAY_PATH: GATEWAY_PATH = 'pscpadmin',
  } = env;

  return {
    base: buildMode === 'PROD' ? baseUrl : '/',
    envPrefix: 'VUE_APP_',
    define: {
      'process.env': JSON.stringify( env ),
    },
    esbuild: false,
    resolve: {
      alias: vAlias, // '~@': path.resolve(__dirname, 'src'),
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', ".json", '.vue'],
    },
    css: {
      preprocessorOptions: {
        // 全局变量注入
        less: {
          // 当 common.less 和 variables.less 拥有相同变量时, common.less 优化级更高
          // 这里的优先级更高
          modifyVars: {
            hack: `true; @import (reference) "${resolve( './src/styles/common.less' )}";`,
          },
          javascriptEnabled: true,
          additionalData: `@import "${resolve( './src/styles/variables.less' )}";`,
        },
      },
    },
		// 为了与某些 Rollup 插件兼容,可能需要强制执行插件的顺序
    // 可以使用 enforce 修饰符来强制插件的位置
    plugins: [
      {
        ...eslint({
          include: ['**/*.{js,ts,vue}'],
          exclude: [/^(?=.*type=(template|style)).*&/],
        }),
        enforce: 'pre',
        apply: 'serve',
      },
      cjs2esmVitePlugin(), // 将 commonJS 转成 ES Module
      // 在这里保持了原有的文件结构处理
      createHtmlPlugin( {
        template: './public/index.html',
        entry: './src/main.ts',
        inject: {
          data: {
            BASE_URL: baseUrl + 'dev/',
            VUE_APP_NAME: appName,
          },
        },
      } ),
      createVuePlugin( {
        jsx: true,
        jsxOptions: { compositionAPI: true },
      } ),
      // legacy({
      //   // targets: ['defaults', 'not IE 11'],
      //   targets: ['ie >= 9'],
      //   additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
      // }),
    ],

    optimizeDeps: {
      // exclude: ['@xxx'], // 忽略预编译的包
    },
    server: {
      host: true,
      port: appPort,
      open: true,
      proxy: {
        [`/${GATEWAY_PATH}`]: {
          target: 'http://proxy.domain.cn/', // test
          changeOrigin: true,
          bypass: (req, res, proxyOption) => {
            console.log( `当前请求代理:${req.url} -> ${proxyOption.target}` );
          },
        },
      },
    },
  };
} );

总结

一切回归文档。在接触一样新的事物的时候,最好也是最优的方法是过一篇文档。这样可以让我们有了“大局观”。