vue-cli4.5&&webpack4升级vite4全面总结

🙂前言

vite是什么?

js 早期只能 script 的属性 src 链接,无法像现在工程化依赖打包插件实现 import 模块化,所以在奔向 js 原生完成工程化构建的伟大航路中,先后有 requirejs, webpack等构建工具的出现,es6 后 js 增加了 type="module", vite 就是基于此属性扩展开发,相对于前面的构建工具来讲, vite 确实带来更加的效率,同时在精简易上手的基础上,也能完美满足更多的扩展性,属实算是当前的构建工具一哥。

扩展阅读可阅读 vite 官网,或者阅读 es6 的 type="module" 官方文档


为什么使用vite?

  1. 快!快!快!各种快就完事~
  2. 相对webpack和其他构建工具来讲,小波个人的体验各种配置和扩展插件更易上手,更好配置(当然需要再学习一个插件库 rollupjs)
  3. 方便后续升级 ts 和项目的更深入优化

😦vite升级思路

  1. 直接 vite 脚手架建个初始项目,再把原项目 src 文件夹等相关文件拷贝进去
  2. 基于现有项目直接升级(本文的升级步骤基于此思路)

🧐工作项目环境

  • vue-cli 4.5
  • webpack 4.4
  • vue 3.2
  • ant design vue 2.x

🤔步骤思路总结

  1. 删除 vue-cli, babel, webpack 相关依赖库和文件
  2. 升级vue3, @vue/compiler-sfc, eslintrc, prettier
  3. 安装 vite, 移动 index.html, 配置 vite.config.js
  4. 针对项目代码逐一修复出现的bug
  5. 打包优化(to do…)

😚vue-cli4.5 && webpack4 升级 vite4 步骤详解

  1. package.json 删除 vue-cli, babel, webpack 相应配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 删除
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "webpack": "^4.46.0"

    "serve": "node --max_old_space_size=4096 node_modules/@vue/cli-service/bin/vue-cli-service.js serve",
    "build": "node --max_old_space_size=4096 node_modules/@vue/cli-service/bin/vue-cli-service.js build",

    "babel-eslint": "^10.1.0",
    "babel-plugin-import": "^1.13.3",
  2. 删除 babel.config.js, vue.config.js, jsconfig.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // jsconfig.json
    {
    "compilerOptions": {
    "experimentalDecorators": true,
    "baseUrl": "./",
    "paths": {
    "@/*": ["src/*"],
    "~/*": ["src/assets/*"]
    }
    }
    }
  3. 删除 .eslintrc.js 的 babel 配置项,把 es6 改成 es2021

    1
    2
    3
    4
    5
    6
    7
    8
    parserOptions: {
    // parser: 'babel-eslint'
    },
    env: {
    node: true,
    // es6: true
    es2021: true
    },
  4. 升级 vue3 到最新版本( vite 需要最新版本), 升级 eslint, prettier( jsx 无法格式化需要升级这两个依赖)

1
2
3
4
5
6
7
8
9
10
// vue版本过低先升级vue
pnpm add vue
pnpm add @vue/compiler-sfc -D

// eslint 代码格式化
pnpm add eslint@8 eslint-plugin-vue@8 -D
pnpm add prettier eslint-plugin-prettier @vue/eslint-config-prettier -D

// 安装 Conflicting peer dependency: vue@3.3.4
pnpm add @vitejs/plugin-vue vite -D

提示

升级步骤是基于 pnpm 命令安装,提前全局安装一下

1
npm install pnpm -g
  1. 创建 vite.config.js 并配置
1
2
3
4
5
6
7
8
9
10
11
12
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})
  1. 移动 index.html 到根目录并修改相应配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!--remove-->
    <title><%= htmlWebpackPlugin.options.title %></title>
    <!--add-->
    <title>site Title</title>

    //...
    <!--remove-->
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    <!--add-->
    <strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>

    <!--remove-->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!--add-->
    <link rel="icon" href="/favicon.ico">

    <script type="module" src="/src/main.js"></script>
  2. package.json 修改脚本命令

    1
    2
    3
    4
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
  3. 更新环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .env                # loaded in all cases
    .env.local # loaded in all cases, ignored by git
    .env.[mode] # only loaded in specified mode
    .env.[mode].local # only loaded in specified mode, ignored by git
    // 文件名可以不变,但是,您不能再访问流程变量上的环境变量。 相反,它们可以在 import.meta.env 上找到。
    // old
    base: process.env.BASE_URL,
    // new
    base: import.meta.env.BASE_URL,

    用于使声明客户端暴露的环境变量更明显的 VUE_APP_ 前缀已更改为 VITE_,因此如果您有任何此类环境变量,则必须相应地更新它们。

  4. vite.config.js 配置 sfc 导入添加 .vue 扩展名

    1
    2
    3
    4
    5
    ...
    resolve: {
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    //...
    },
  5. 清理 webpack 魔法注释

    1
    /* webpackChunkName: "about" */

    提示

    vite 会自动根据文件夹进行按需分包加载,打包后的文件名格式 x+hash.js, 所以业务文件夹的 x.vue 尽量不要用 index.vue

  6. jsx 报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 1. 安装
    pnpm add @vitejs/plugin-vue-jsx -D

    // 2. 配置 vite.config.js
    import vueJsx from '@vitejs/plugin-vue-jsx'
    ...
    resolve: {},
    esbuild: { loader: { '.js': '.jsx' } }

    // 3. .vue 文件要把 lang="jsx" 补上, .js 文件要改成 .jsx
  7. Error: The following dependencies are imported but could not be resolved: qs

    1
    2
    // 缺少依赖库 安装 qs
    pnpm add qs
  8. Internal server error: Unable to parse HTML; parse5 error code end-tag-without-matching-open-element

    1
    2
    3
    4
    5
    6
    // 因为ant的导入样式 ~ 问题,在配置我增加了 { find: /^~/, replacement: ''} 导致无法找到 .less 文件,
    // 直接把 ~ 去掉
    // old
    @import '~ant-design-vue/dist/antd.less';
    // new
    @import 'ant-design-vue/dist/antd.less';
  9. Cannot read properties of undefined (reading ‘_android’)

    1
    2
    3
    4
    5
    6
    7
    pnpm add qrcodejs2-fix
    pnpm remove qrcodejs2

    //原来导入的地方把 名字 改成 下面代码
    import QRCode from 'qrcodejs2-fix'

    // 如果出现一直报错原来的 qrcodejs2,删除node_modules包

    类似问题参考文章

    github上iss

    QRCode undefined (reading ‘_android‘)

  10. 图片或者导入依赖库原来采用了 webpack 的 require , 报错 require is not defined

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // vite 返回静态资源图片 name = x.png/x.jpg 
    const getImageUrl = name => {
    return new URL(`../../assets/image/${name}`, import.meta.url).href
    }

    // 如果是 require 的依赖库 改成以下,注意要用 await
    // old
    let elementResizeDetectorMaker = require('element-resize-detector')
    // new
    let elementResizeDetectorMaker = await import('element-resize-detector')
    let erd = elementResizeDetectorMaker.default()

    注意:

    1. new URL(dep, import.meta.url) dep 无法使用 别名 @ 来描述路径
    2. new URL(dep, import.meta.url) 如果抽离为公共方法, dep 中路径不能是完整一个变量传入,变量只能是路径结尾的图片类型

    类似问题参考文章

    Vite 踩坑 —— require is not defined

  11. ReferenceError: Cannot access ‘store’ before initialization vuex,This could be due to syntax errors or importing non-existent modules

    1
    2
    3
    4
    // 出现类似这样报错,绝大部分可能性就是代码中出现了循环导入
    // eg: a import b, b import c, c import a

    // 只能自己跟着报错找到可能出现循环导入的文件然后检查代码

    提示

    Find unused exports

    小波推荐检查文件是否循环导入安装 vscode 插件 find unused exports

    使用说明:

    vscode左侧栏会有个相同图标,只要打开文件,切到面板就会自动分析文件的导入导出情况

    CIRCULAR IMPORTS 即是循环导入提示区域


    类似问题参考文章

    github的iss

    Vite HMR error: Cannot access ‘…’ before initialization ?

    pinia/vuex在封装请求的文件中使用报错的问题

    vue3+vite热更新遇到的问题HMR error: Cannot access ‘…’ before initialization

  12. You are running the esm-bundler build of vue-i18n. It is recommended to configure your bundler to explicitly replace feature flag globals with boolean literals to get proper tree-shaking in the final bundle

    1
    2
    3
    // 多语言报错  
    // vite.config.js
    alias: { ..., 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js' },
  13. ant design vue 没导出的组件去掉,未使用的函数要删除

    1
    eg:  moment  invalid 
  14. provide 的定义函数放到调用执行前面

    1
    理解js上下文执行顺序
  15. Added non-passive event listener to a scroll-blocking ‘wheel’ event. Consider marking event handler as ‘passive’ to make the page more responsive.

    1
    // 缺少对应的库,提示什么就安装什么
  16. module.exports 报错

    1
    2
    module.exports = {}
    要改成 esmodule 模式 => export {}

    感兴趣的童鞋可以深入阅读 commonjs 规范(module.exports),es6 规范(export [default]),amd 规范(require),这三者的区别

  17. 多语言报错 Not available in legacy mode

    1
    2
    3
    4
    5
    6
    7
    const i18n = createI18n({
    // ...
    // 设为true或者不设置
    legacy: true,
    allowComposition: true,
    // ...
    })
  18. 日期插件出现部分中文,部分英文

    1
    2
    3
    import 'moment/dist/locale/zh-cn'
    // 不仅需要调用,上面也必须导入
    moment.locale(locale)
  19. [unplugin-vue-components] component “xx“ has naming conflicts with other components, ignored.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 配置 vite.config.js
    import Components from 'unplugin-vue-components/vite'
    ...
    plugins: [
    Components({
    resolvers: [IconsResolver()],
    // 此属性解决报错
    directoryAsNamespace: true
    }),
    ]
  20. class关于Cannot access XXX before initialization的报错

    还是循环导入的锅,开发时热更新报上面的错误提示,小波最后只能强制采用刷新机制来处理,可以自己+个判断只对有问题的页面强制刷新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 配置 vite.config.js
    plugins: [
    ...
    // 强制刷新
    {
    name: 'singleHMR',
    handleHotUpdate({ modules }) {
    modules.map((m) => {
    m.importedModules = new Set()
    m.importers = new Set()
    })

    return modules
    }
    }
    ]

以上步骤是小波升级公司项目遇到的相关问题,希望为同样要把前端项目升级成 vite 构建的童鞋提供点点参考。

以下是一份小波升级后完整的 vite.config.js 配置,包含了分包等优化

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// vite.config.js
import { defineConfig, splitVendorChunkPlugin } from 'vite'
import path from 'path'

import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

// postcss相关插件
import postcssImport from 'postcss-import'
import autoprefixer from 'autoprefixer'
import tailwindcss from 'tailwindcss'

// 打包输出分析
import { visualizer } from 'rollup-plugin-visualizer'
// 文件压缩 gzip
import viteCompression from 'vite-plugin-compression'
// 图片压缩
// import viteImagemin from 'vite-plugin-imagemin'
// 省掉导入库的hooks eg: 无需导入 vue -> reactive 即可使用 reactive
import AutoImport from 'unplugin-auto-import/vite'

// 转外链
import externalGlobals from 'rollup-plugin-external-globals'
const globals = externalGlobals({
// moment: 'moment',
// 'video.js': 'videojs',
// jspdf: 'jspdf',
// xlsx: 'XLSX',
echarts: 'echarts'
})

// 支持 module.exports
// import commonjs from 'rollup-plugin-commonjs'
// const path = require('path')

// https://vitejs.dev/config/
export default defineConfig({
// module.exports和import混用
// rollupOutputOptions: {
// exports: 'default'
// },
// 支持 vue 和 vueJsx , commonjs()
plugins: [
vue(),
vueJsx(),
visualizer({ open: true }),
splitVendorChunkPlugin(),
viteCompression({
// 是否在控制台中输出压缩结果
verbose: true,
disable: false,
// 如果体积大于阈值,将被压缩,单位为b,体积过小时请不要压缩,以免适得其反 200k
threshold: 202400,
// 压缩算法,可选['gzip',' brotliccompress ','deflate ','deflateRaw']
algorithm: 'gzip',
ext: '.gz',
// 源文件压缩后是否删除(我为了看压缩后的效果,先选择了true)
deleteOriginFile: false
}),
// viteImagemin({
// gifsicle: {
// // 选择0到7之间的优化级别
// optimizationLevel: 7,
// // 隔行扫描gif进行渐进式渲染
// interlaced: false
// },
// optipng: {
// // 选择0到7之间的优化级别
// optimizationLevel: 7
// },
// mozjpeg: {
// quality: 20
// },
// pngquant: {
// quality: [0.8, 0.9],
// // 压缩速度,1(强力)到11(最快)
// speed: 4
// },
// svgo: {
// plugins: [
// {
// name: 'removeViewBox'
// },
// {
// name: 'removeEmptyAttrs',
// active: false
// }
// ]
// }
// }),
AutoImport({
// pinia
imports: ['vue', 'vue-router', 'vuex', '@vueuse/head'],
// 可以选择auto-import.d.ts生成的位置,使用ts建议设置为'src/auto-import.d.ts'
dts: 'src/auto-import.d.ts'
}),
// 强制刷新
{
name: 'singleHMR',
handleHotUpdate({ modules }) {
if (modules[0]['url'].toString().indexOf('page/custom') > -1) {
modules.map((m) => {
m.importedModules = new Set()
m.importers = new Set()
})
return modules
}
}
}
],
// 配置路径别名,f12抛出 i18n多语言警告
resolve: {
alias: { '@': path.resolve(__dirname, './src'), 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js' },
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
// less的变量报错无法找到
css: {
postcss: {
// postcssImport 对我们多个css文件进行合并 || autoprefixer +前缀 || tailwindcss 作为PostCSS插件存在,将基础的CSS拆分为原子级别。
plugins: [postcssImport, autoprefixer, tailwindcss]
},
preprocessorOptions: {
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve('src/assets/css/variable.less')}";`
},
javascriptEnabled: true
}
}
},
esbuild: {
// 删除 console.log
pure: ['console.log'],
// 删除 debugger drop: ['console,'debugger'], // 删除 所有的console 和 debugger
drop: ['debugger']
},
build: {
outDir: 'zowee_efactory_build',
// commonjsOptions: {
// include: /node_modules|core/,
// defaultIsModuleExports: 'auto'
// },
rollupOptions: {
// 转外链 'xlsx',
external: ['echarts'],
plugins: [globals],
output: {
// 引入文件名的名称 output.manualChunks 否则,它将会根据 chunk 的内容确定。
chunkFileNames: 'js/[name]-[hash].js',
// 包的入口文件名称
entryFileNames: 'js/[name]-[hash].js',
// 资源文件像 字体,图片等
// assetFileNames: '[ext]/[name]-[hash].[ext]',

manualChunks(id) {
if (id.includes('/src/components/')) {
return 'components-index'
}
if (id.includes('/src/assets/font')) {
return 'font-index'
}
if (id.includes('ant-design-vue') || id.includes('@ant-design')) {
return 'ant-vendor'
}
if (
id.includes('vuex') ||
id.includes('vue-router') ||
// id.includes('vue-i18n') ||
// id.includes('vue-loader-v16') ||
id.includes('vue3-touch') ||
id.includes('sortablejs') ||
id.includes('vuedraggable')
) {
return 'sortablejs-vendor'
}

if (
id.includes('axios') ||
id.includes('bignumber') ||
id.includes('qs') ||
// id.includes('element-resize-detector') ||
id.includes('figlet') ||
id.includes('moment') ||
id.includes('jszip') ||
id.includes('jsplumb') ||
id.includes('js-md5') ||
id.includes('qrcodejs2-fix') ||
// id.includes('sortablejs') ||
// id.includes('vuedraggable')
// id.includes('process') ||

// 以下未打包 项目未调用
id.includes('@turf') ||
id.includes('es6-tween') ||
id.includes('exec-mathexpress') ||
id.includes('file-saver') ||
id.includes('@flatten-js') ||
id.includes('html2canvas') ||
id.includes('import-three-examples') ||
id.includes('polybooljs') ||
id.includes('viewerjs') ||
id.includes('weixin-js-sdk')
) {
return 'axios-vendor'
}

if (id.includes('@wangeditor/editor') || id.includes('@wangeditor/editor')) {
return 'wangeditor-vendor'
}

// if (id.includes('echarts')) {
// return 'echarts-vendor'
// }

// if (id.includes('xlsx')) {
// return 'xlsx-vendor'
// }

// 把库全部单独分拆出来
// if (id.includes('node_modules')) {
// console.log('id----', id)
// return id
// .toString()
// .split('node_modules/')[1]
// .split('/')[0]
// .toString()
// }
}
}
}
}
})

😫一些奇奇怪怪的问题

1
2
3
4
5
Error:   Failed to scan for dependencies from entries:
E:/web/kjl/index.html
E:/web/kjl/public/html.html
E:/web/kjl/public/production.html
E:/web/kjl/src/assets/font/demo_index.html
  1. 安装依赖库报错 ERESOLVE could not resolve,强制安装

    1
    pnpm add [...] --legacy-peer-deps

😊来自小波的bilibili视频教程


🙂相关参考资料链接

关注廿壴(GANXB2)微信公众号

『旅行者』,帮小波关注一波公众号吧。

小波需要100位关注者才能申请红包封面设计资格,万分感谢!

关注后可微信小波,前66的童鞋可以申请专属红包封面设计。

THE END
作者
chopin gump chopin gump
小尾巴
Stay Hungry, Stay Foolish「求知若饥, 虚心若愚」 — 廿壴(GANXB2)
许可协议
vue-cli4.5&&webpack4升级vite4全面总结
https://blog.ganxb2.com/45497.html
微信

微信

支付宝

支付宝

- ( ゜- ゜)つロ 填写QQ邮箱自动获取头像亦更快获得回复通知
评论区显示“刷新”,多点几下或过会儿再来康康 _(≧∇≦」∠)_