技术背景
vue-cli+vue2+npm
存在问题
随着项目的不断迭代加上项目包管理混乱,导致项目体积越来越大,本地serve的时间居然接近2min,并且hmr速度也非常慢,即使改动只有一点点。
这么慢的原因: Webpack启动后会做一堆事情,经历一条很长的编译打包链条,从入口开始需要逐步经历语法解析、依赖收集、代码转译、打包合并、代码优化,最终将高版本的、离散的源码编译打包成低版本、高兼容性的产物代码(polyfill),这可满满都是 CPU、IO 操作,在 Node 运行时下性能必然是有问题。
解决方案
一、优化项目依赖、代码结构
上面我们提到,wepack在启动够会做一堆事情,浅显来说,如果我们减少事件的数量,是可以加快速度的
- 按需依赖处理,举个🌰: element-ui的引入
js
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
],
2.优化依赖结构,根据项目依赖进行处理,这里我象形一下😁:
如上,项目A同时依赖库B、库A,但是库A也依赖库B,这种情况在项目里面是很常见的。相信大家都知道每个package.json里面有devDeps(开发依赖)、deps(生产依赖),这里有个注意的点就是如果库发布的时候将依赖放入devDeps时候,这个依赖是不会被打进去的。虽然npm在扁平化做依赖提升会帮我们做这个事情(同一版本下,这里就不展开说了),但是有个这种情况下是不是用的:项目A依赖1.6.6版本的库B,而库B依赖2.6.6版本的库B,这里库B就会被下载两次。 🤔根据上面所说的,首先我们需要统一库A、项目A依赖库B的版本,之后将库A的依赖库B放入devdeps。 3、升级vue-cli版本(额,这种搞脑子的东西就不说了)
但是一顿操作下来,似乎没什么显著的奇效。因为webpack的构建链路始终很长,基于node下io、cpu操作没办法避免。这时候就要请出我们的主角vite了
二、升级vue-cli至vite
-
简单介绍下vite
Vite作为一个基于浏览器原生ESM的构建工具,它省略了开发环境的打包过程,利用浏览器去解析imports,在服务端按需编译返回。同时,在开发环境拥有速度快到惊人的模块热更新,且热更新的速度不会随着模块增多而变慢。因此,使用Vite进行开发,至少会比Webpack快10倍左右。
-
Vite由两个主要部分组成:
2.1 dev server:利用浏览器的ESM能力来提供源文件,具有丰富的内置功能并具有高效的HMR
2.2 生产构建:生产环境利用Rollup来构建代码,提供指令用来优化构建过程
开始改造(vite4.0+vue2.7+pnpm)
这里我顺便把vue版本升至2.7(vue官方把vue3的编码特性全都迁入vue2.7,同时保留了2.0的optionsApi ,why not use😄),将包版本管理工具改成pnpm(这里就不展开说了,主要是能加快依赖下载速度以及依赖隔离)。
- 下载依赖、由于这里我们使用的是vue2.7 需要使用@vitejs/plugin-vue2
js
pnpm i vite @vitejs/plugin-vue2 -D
接下来就是创建vite.config.js文件,配置package.json script执行vite
这里还要注意 和vue-cli项目的结构不一样 模板index.html需要在项目根目录下 不再是在public下 还需要在index.html把入口main.js以esm的形式引入
js
<script src="/src/main.js" type="module" ></script>
当然,你也可以使用vite-plugin-html起指定模版和入口js的位置,但是这里有个坑点,如果你的项目使用了iframe,那么这个页面只能跳转去server代理的target。
接下来我们开始vite改造之旅(开始踩坑🫡)
初步配置下vite.config 本地跑起来
js
//vite.config.js
const { resolve } = require('path');
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue2';
export default defineConfig(({ mode }) => {
// 获取ENV变量 相当于process.env 进行一些环境配置映射 如代理target
const ENV = loadEnv(mode, process.cwd(), ''),
return {
plugins: [vue()],
server: {
host: '0.0.0.0',
open: 'your_route_path',
port: u_like_port,
cors: true, //支持cors,
// proxy 是http-proxy的扩展 可以去http-proxy看具体配置
proxy: {
//需要代理的路径
'u_api_path': {
target, //需要代理的源 如https:xxx/xxx
changeOrigin: true,
configure: (proxy, options) => {
// 这里主要是设置本地反向代理 和vue-cli配置差异其实不大 但是不能配置/
//设置本地代理要修改的请求头字段 如cookie或其他
proxy.on('proxyReq', (proxyReq, req) => {
proxyReq.setHeader('cookie', xx)
});
}
}
}
},
resolve: {
//这里wepack别名配置一样 按照项目目录结果来配置就可以了
alias: [
{ find: '@', replacement: resolve(__dirname, 'src') },
{ find: 'vue$', replacement: 'vue/dist/vue.esm.js' },
],
//这里是文件扩展名 前后有优先级
extensions: ['.vue', '.jsx', '.mjs', '.js', '.ts', '.json', '.scss']
},
define: {
// 同 webpack.DefinePlugin 这样就可以在项目无缝使用ENV了 不需要重新定性VITE_XXX
'process.env': `(${JSON.stringify({ ...ENV, IS_VITE: 1 })})`
}
}
})
啊报错了
问题一:expected selector /deep/ vite无法识别/deep/穿透该样式的写法,这个好改,项目批量处理一下改成::v-deep
js
问题二: Preprocessor dependency not found. Did you install it? 没有找到sass依赖,👌🏻 那下一个
js
pnpm i sass -D
问题三:Can not read properties of undefined(reading 'install')
举个🌰:element-ui被use注册的时候找不到install方法,这里的话是因为import引入的时候没有写详细路径,这里有两个解决方案:
1.可以使用unplugin-auto-import/vite自带elementresolvers插件自动注册,代码注册可以删除
2.使用vite-plugin-babel-import插件,手动写一下babel配置,这个就和bable.config.js配置一样的
这里我采用了第二个方案,因为项目里还有其他内部库,如果第一个方案的话需要写很多reslover,我把babel.config.js文件配置同步一下,先下载一下vite-plugin-babel-import
js
pnpm i vite-plugin-babel-import -D
在vite.config.js引入
js
...
import vitePluginImport from 'vite-plugin-babel-import';
export default defineConfig(({ mode }) => {
...,
plugins: [
...,
vitePluginImport(
[
{
libraryName: 'element-ui',
libraryDirectory: 'lib',
style(name) {
return `element-ui/lib/theme-chalk/${name}.css`;
}
}
],
...
)
],
...
})
问题四:The requested module 'xxx.js' does not provide an export named 'xxx'
这个是因为使用了cjs,使用exports.xxx导出方法导致,我们只需将写法改成esm的模式或者使用插件转成esm即可,这李我选择使用插件vite-plugin-commonjs
js
pnpm i vite-plugin-commonjs -D
再配置下
在引入
js
//vite.config.js
...
import commonjs from 'vite-plugin-commonjs'
export default defineConfig(({ mode }) => {
...,
plugins: [
//这里最好放在第一位
commonjs({}),
...
],
...
})
问题五:Failed to parse source for import analysis because the content contains invalid JS syntax。 这里是因为vue文件中使用了jsx语法,把使用了jsx语法vue文件的scirpt标签里面把lang属性改成jsx即可,这里也要顺便注意下使用了jsx语法的js文件也要改成jsx后缀
问题六:require is not defined 这个是webpack图片动态引用问题,vite不支持这种引用,这个问题我是直接把项目asset下的图片文件上传至云端,不想上传到云端的话可以使用
arduino
new URL('url_path', import.meta.url).href
至此,项目在本地运行起来了,但是页面跳转咋一卡一卡的🤥,并且页面会reload,这...
于是在vite官网看到原因,当发现新依赖的时候,会重新加载页面 在optimizeDeps源码中我们看见缓存会被写进.vite/deps/_metadata.json, 为了进一步确认是未发现的新依赖引起,我们在进入.vite/deps/_metadata.json,在页面reload后发现这个缓存文件里面多了qs 由于esm的引入是按需的,页面路由又是动态的,当进入新的页面时发现了新的依赖就触发了页面重载,的触发这个问题到这里也比较清晰了,我们可以手动声明预加载在项目动态路由里使用的依赖解决这个问题
js
...
import commonjs from 'vite-plugin-commonjs'
export default defineConfig(({ mode }) => {
...,
optimizeDeps: {
includes: ['your_pageage']
},
...
})
结语
至此,项目完美在本地跑起来了,在mac下这个冷启动速度不到1s,相比之前webpack的的2min有了巨大的提升,hmr几乎看不出刷新,本地开发太香了。但是在首屏方面,因为同请求数量太多还是会白屏一小会(大概6~8s),这个看了下也算是vite本地serve的一个通病。另外,vite4以上在window也做了冷启动优化,号称快了4倍,从效果来看确实是。 项目包管理工具改pnpm在mac下依赖确实快了很多😄,vue2.7 Composition API也确实香。
By the way,转战掘金的第一篇文章,大家在vue-cli转vite遇到的问题也可以评论区讨论下。