历史项目从诞生到持续的迭代后,往往很难去做一次大的重构,不如先从脚手架的迁移做起。

如何从@vue/cli 迁移至 vite?支持 vue2.x 需要做些什么?

下面是一些简短的步骤及关键点:

  1. 移除 @vue/cli 相关依赖,安装 vite 相关依赖(参考下面的对比图)

    • 配置项的迁移,这个需要看之前配置的复杂度,去做对应迁移,下面提供了一个基础的配置。
  2. 修改适配 Vite 环境境变量、资源的引用方式

    • process.env.VUE_APP_* 调整为 import.meta.env.VITE_APP_*,检查引用情况,如果不复杂可直接全量替换。

      • 在 vite.config.js 中如何使用环境变量

        1
        2
        3
        import { defineConfig, loadEnv } from 'vite'

        const env = loadEnv(process.env.NODE_ENV, process.cwd());
    • 静态资源引用从 require(x.png) 调整为 import(x.png)

      • 有对应 vite-plugin-require 插件,但如果项目体量较小,建议还是逐个自行处理

      • 封装全局函数,自行替换 require() 用法,需要注意不能直接在 src=”import(xxx.png)”,编译后会导致加载异常。

        1
        2
        3
        const mapJson = require('../../assets/js/china-n.json')
        ====>
        import mapJson from '../../assets/js/china-n.json';
        1
        2
        3
        export function getAssetsFile (url) {
        return new URL(`../assets/images/${url}`, import.meta.url).href
        }
    • 存在的CSS中资源~@别名问题,从~@/images 调整为 @/images

  3. 配套相关改动

    • eslint配置项从 eslintrc.js 迁移至 eslintrc.cjs
    • Index.html ejs 支持及环境变量相关调整。
      • 可通过安装 vite-plugin-ejs 来兼容 ejs 语法。

依赖对比图

  1. qiankun 插件迁移

    • vite-plugin-legacy-qiankun 提供了对应的封装,按照文档配置即可

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      // 但是由于自身也进行了二次封装,看了插件的代码,只需要适配插件的赋值、便可应用

      // 插件提供的 createLifecyle 赋值
      export const createLifecyle = (name: string, lifecyle: Lifecyle) => {
      const global = (0, eval)('window')
      global.legacyQiankun = global.legacyQiankun || {}
      global.legacyQiankun[name] = global.legacyQiankun[name] || {}
      global.legacyQiankun[name].lifecyle = lifecyle
      }

      // 插件应用时读取逻辑
      <script>
      (function (){
      const global = (0, eval)('window')
      const name = 'bigMemberSubApp'

      const app = global.legacyQiankun[name]
      if (!app.proxy) return
      window[name] = {
      bootstrap: (...args) => app.dynamicImport.then(() => app.lifecyle.bootstrap(...args)),
      mount: (...args) => app.dynamicImport.then(() => app.lifecyle.mount(...args)),
      unmount: (...args) => app.dynamicImport.then(() => app.lifecyle.unmount(...args)),
      update: (...args) => app.dynamicImport.then(() => app.lifecyle.update(...args)),
      }})()
      </script>

      // 根据上面的插件赋值、读取,可以根据自身的原有逻辑,进行适配,一般赋值进行适配即可。
      const app = easySubAppRegister({
      Vue,
      router,
      store,
      App,
      el: '#app'
      }, {
      bootstrap () {
      fixGetComputedStyle()
      }
      })

      global.legacyQiankun[name].lifecyle = app

可供参考的 vite.config.js 配置,包含:

  • Vue2 支持
  • 代理配置
  • CDN提取配置
  • SCSS公共样式配置
  • qiankun支持
  • EJS模版支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { resolve } from "path";

import {defineConfig, loadEnv} from 'vite'
import legacy from '@vitejs/plugin-legacy'
import vue2 from '@vitejs/plugin-vue2'
import {ViteEjsPlugin} from "vite-plugin-ejs";
import {viteExternalsPlugin} from 'vite-plugin-externals'
import { getProxyOfDomain } from './config/proxy'
import { legacyQiankun } from 'vite-plugin-legacy-qiankun'

const env = loadEnv(process.env.NODE_ENV, process.cwd());

const baseCDN = {
css: [
'xx'
],
js: [
'xx'
]
}

const prodCDN = {
js: [
'xx',
]
}

function getCDN () {
const isDev = process.env.NODE_ENV === 'development'
if (isDev) {
return baseCDN
} else {
const allCDN = Object.assign({}, baseCDN)
allCDN.css = allCDN.css.concat(prodCDN.css || [])
allCDN.js = allCDN.js.concat(prodCDN.js || [])
return allCDN
}
}

function getExternals () {
const isDev = process.env.NODE_ENV === 'development'

if (isDev) {
return {}
}

return {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
lodash: '_',
jquery: '$'
}
}

export default defineConfig({
base: env.VITE_APP_BASE_PUBLIC,
build: {
outDir: 'xxxx',
rollupOptions: {
external: [
'/common-module/xx.js'
]
}
},
optimizeDeps: {
exclude: [
'/common-module/xx.js'
]
},
plugins: [
vue2(),
ViteEjsPlugin(
{
cdn: getCDN(),
assets: {
version: Date.now()
}
}
),
viteExternalsPlugin(getExternals()),
legacy({
targets: ['ie >= 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
}),
legacyQiankun({
name: 'xxSubApp',
devSandbox: true
})
],
resolve: {
alias: [
// {
// find: /^~/,
// replacement: "",
// },
{
find: "~@",
replacement: resolve(__dirname, "src"),
},
{
find: "@",
replacement: resolve(__dirname, "src"),
},
],
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
css: {
preprocessorOptions: {
scss: {
additionalData: `
@import "@/assets/style/_mixin.scss";
@import "@/assets/style/_variables.scss";
`
}
}
},
server: {
port: 8080,
proxy: getProxyOfDomain('https://xx.com')
}
})

当然不同的项目有各式各样的依赖,兼容性、改造难度都需要逐步排查,可以去社区检索对应方案。