项目背景
所谓工欲善其事必先利其器。近期公司有一个大数据处理平台的项目,由于项目启动时间实在太长,体验极差,且生产模式下还使用了Gulp
工具打包,所以组长让我重新选择构建工具改造。
打包工具权衡
组长给了我两个选择:
Vue Cli
Vite
说实话,直接使用Vue-Cli
应该是改造时间最少的,但是Vite
实在是太吸引人了,组内也开始准备将各个项目的构建工具换成Vite
,加上自己又想学习一下,就选择使用了Vite
(有点私心,哈哈哈哈)。
实现思路
实现之前还是进行了一点预研,避免浪费时间,以下是我设计本次改造的实现思路:
- 环境搭建: 首先搭建一个基于
Vue2
+Vite
的最小化环境,之后引入葱姜蒜等配套设施,Vue-Router
、Vuex
、Axios
、lodash
等。 - 代码迁移:在确保搭建的环境能够正常运行之后,将需要的项目源码迁移过来,当然这一步可以细分,避免一次性迁移出太多问题。
- 运行排坑:迁移代码之后,需要对代码是否能正常运行进行验证,这一步很重要,也是我花时间最多的一步。
- 项目优化:由于老项目里面本身有挺多问题,加上
Vite
不做优化的情况还是有一些问题的,故需做一些优化
具体实现
万物之始:环境搭建
首先运行yarn create vite
创建一个Vite
项目,这里不使用--template vue
的原因是用了之后会直接创建一个Vue3
的项目,emmm。。咱这个项目用的Vue2
,所以不这么干。
随后我们来添加Vue
的插件(开始脑阔疼),这里着重说一下,由于老项目采用的是固定版本Vue@2.5.2
,本来应该使用vite-plugin-vue2
这个创建,但是实践之后有个问题:
vue@2.5.2
+vue-template-compiler
这种方式会导致Vite
在run build
之后不退出,没有Done x.xx s .
这种输出,就卡在这儿了(我人傻了呀)。
而我们公司采用的是Jenkins
自动化打包流程,这一步卡住就会导致后续的步骤都无法进行下去。经过测试之后2.5.2
的这个版本有点问题(具体什么问题我至今也不是特别清楚),升级到2.6.0
之后就不会出现这个问题。
在权衡之后,为了项目的扩展性和可维护性(想用Composition Api
),并且查看更新文档之后,决定直接升级成Vue 2.7
。
这一系列操作之后,最终Vue
的插件使用了只支持Vue 2.7
的@vitejs/plugin-vue2
,并且也不用再安装template-compiler
。之后就是引入项目中的各种依赖,葱姜蒜三件套Vue-Router
、Vuex
、Axios
等。
这里贴一下简单的配置:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue2';
import { fileURLToPath, URL } from 'url';
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: 'localhost',
port: 8080,
open: true,
proxy: {
/** 这里随意 */
}
},
})
无话可说:代码迁移
这一步没什么好说的,直接复制粘贴,完事儿,只是项目中有一些gulp
的任务被我直接干掉了,实际上我看了代码也没做什么事情,还是调用webpack
的生产打包,额外生成了一些版本号的东西。
险些中道崩殂:运行排坑
这一步是我花时间比较多的,甚至差点就不想用Vite
了。主要描述下我碰到的各种问题:
- 路径里面不能带
#
,可能是配置问题; -
Commonjs
的方式必须修改为ES Module
,包括require.context()
这种写法需要修改为import.meta.globEager()
; -
import
会被Vite
预先处理,动态变量导入的时候,变量只能是单层级的目录,多层级的需自行处理; -
img url
不能直接用函数处理之后的字符串,应该使用new Url()
,参考cn.vitejs.dev/guide/asset… -
Vite vue
中<style>
部分不支持/deep/
或者>>>
的写法,正确写法为:deep(.child)
; -
Vite vue
中使用render写法的时候,必须做如下操作:
- 使用
@vitejs/plugin-vue2-jsx
插件,如果使用vite-plugin-vue2
的话,可以直接传入jsx: true
来代替。 - 指定
.vue
文件中<script>
上添加lang="jsx"
- 在
vite.config.js
中添加
{
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment'
}
}
还有一些项目中的问题就不在这儿一一叙述了,可能大家碰不到,大概都是跟导入有关的。
迎来曙光:项目优化
这一步主要是根据项目的情况做了一些优化,参考自三元大佬的小册深入浅出 Vite – 神三元 – 小册 (),非常的细嗷:
请求优化:
首先大家都知道,Vite
的性能瓶颈之一就是加载大量的script module
,传统的HTTP1.1
存在队头阻塞的情况,同一个 TCP 管道中同一时刻只能处理一个 HTTP 请求,也就是说如果当前请求没有处理完,其它的请求都处于阻塞状态,另外浏览器对于同一域名下的并发请求数量都有限制,比如 Chrome 中只允许并发6个请求。
在 Vite
中,我们可以通过vite-plugin-mkcert
在本地 Dev Server 上开启 HTTP2
: 由于 HTTP2 依赖 TLS
握手,插件会自动生成 TLS
证书,然后支持通过 HTTPS 的方式启动,而 Vite
会自动把 HTTPS 服务升级为 HTTP2。(此处引用神三元大佬的小册《深入浅出Vite》第19节性能优化)
压缩:
由于Vite
自带压缩代码功能,也可以自己配置,我这儿就没有额外配置。另外有很多同学都用了压缩图片的工具,但是这玩意儿我装不上(尬住),我就没用,我看有同学说这个插件用了图片压缩出来还有更大的情况(啊这。。)。
代码分割:
这里Vite
本身支持在build.rollupOptions.output.manualChunks
自定义分包行为,但是我太懒,直接采用vite-plugin-chunk-split
插件进行自动分包。
打包预览
打包预览能够让我们清晰地知道打包之后的体积,从而能更精确地去定位打包问题中可能发生的问题。这里采用的是Rollup
的插件rollup-plugin-visualizer
。
依赖预购建
预购建也是Vite
开发过程中很重要的一步,很早之前Vite
采用Rollup
进行这一步,新的Vite
也改成了Es Build
来实现。在某些动态 import
的场景下,由于 Vite
天然按需加载的特性,经常会导致某些依赖只能在运行时被识别出来,可能会出现new depenencies found
的情况,直接进行二次依赖预构建,代价很大,会重新请求所有模块,而且可能不止出现一次(如果你在开发过程中,发现点开某个模块,页面突然白了,多半可能是二次预构建了)。
所以通过optimizeDeps字段可以对预购建进行自定义配置,以下是我这部分的配置:
{
optimizeDeps: {
include: [
'@antv/g6',
'vue-codemirror',
'x2js',
'insert-css',
'vue',
'moment',
...codeMirrorDeps
]
}
}
这里着重说一下我解构的codeMirrorDeps
这个变量,在我实践的过程中,我发现Vite
并不能很好地处理这种导入方式:
import { CodeMirror } from 'vue-codemirror';
// language
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/python/python.js';
import 'codemirror/mode/sql/sql.js';
// theme css
import 'codemirror/theme/base16-light.css';
import 'codemirror/theme/monokai.css';
import 'codemirror/theme/solarized.css';
// hint
import 'codemirror/addon/hint/javascript-hint.js';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/selection/active-line.js';
// highlightSelectionMatches
import 'codemirror/addon/scroll/annotatescrollbar.js';
import 'codemirror/addon/search/match-highlighter.js';
import 'codemirror/addon/search/matchesonscrollbar.js';
import 'codemirror/addon/search/searchcursor.js';
// require active-line.js
import 'codemirror/addon/selection/active-line.js';
// closebrackets
import 'codemirror/addon/edit/closebrackets.js';
// keyMap
import 'codemirror/addon/comment/comment.js';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/dialog/dialog.js';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/search/search.js';
import 'codemirror/addon/search/searchcursor.js';
import 'codemirror/keymap/emacs.js';
import 'codemirror/keymap/sublime.js';
import 'codemirror/mode/clike/clike.js';
这种方式导致的就是,我在加载这个组件的时候,每次都会二次预购建,故在这儿我将这些路径抽离成变量codeMirrorDeps
,写在预构建中。
最终配置文件
展示下最终的vite.config.js
的文件:
import { defineConfig } from 'vite';
import { fileURLToPath, URL } from 'url';
// import viteEslint from 'vite-plugin-eslint';
import dns from 'dns';
import { visualizer } from 'rollup-plugin-visualizer';
import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
import mkcert from 'vite-plugin-mkcert';
import { codeMirrorDeps } from './config/codeMirrorImports.js';
import vue from '@vitejs/plugin-vue2';
import vueJsx from '@vitejs/plugin-vue2-jsx';
/**
* Node.js 在 v17 以下版本中默认会对 DNS 解析地址的结果进行重新排序。
* 访问 localhost 时,浏览器使用 DNS 来解析地址,这个地址可能与 Vite 正在监听的地址不同
* 使用 setDefaultResultOrder('verbatim') 禁用这个排序
*/
dns.setDefaultResultOrder('verbatim');
// https://vitejs.dev/config/
export default defineConfig({
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment'
},
plugins: [
vue(),
vueJsx(),
visualizer(),
chunkSplitPlugin({
customSplitting: {
// g6: ['@antv/g6']
}
}),
// 由于 HTTP2 依赖 TLS 握手,插件会自动生成 TLS 证书,然后支持通过 HTTPS 的方式启动,而 Vite 会自动把 HTTPS 服务升级为 HTTP2
mkcert()
],
optimizeDeps: {
include: [
'@antv/g6',
'vue-codemirror',
'x2js',
'insert-css',
'vue',
'moment',
...codeMirrorDeps
]
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
// 开发时运行在localhost: 8080 / 127.0.0.1: 8080
server: {
https: true,
host: 'localhost',
port: 8080,
open: true,
proxy: {
/** 省略.. */
}
},
publicDir: 'static',
build: {
// 8 KB
assetsInlineLimit: 8 * 1024
}
});
总结:最后一些话
从Webpack
切换成Vite
其实过程蛮复杂的,需要处理很多东西,这个过程中也有很多坑(其实大部分都跟导入有关,我碰到的是这样)。如果公司给的时间不够充裕的情况下,不建议使用Vite
。
本篇引用及致谢深入浅出 Vite – 神三元 – 课程 (),想深入学习Vite
的同学可以看看。