目录
- 前言
- 迁移前后对比
- 迁移流程
- 迁移业务代码到vite项目
- 项目开发阶段报错处理
- 项目打包阶段处理
- 总结
一. 前言
公司有个特别大维护时间长的后台管理系统,使用的是vue2和webpack3,虽然配置了很多编译优化,但启动和开发热更新速度依然很慢,极大的影响了开发效率,于是准备迁移vite2来优化,现在已迁移升级,来复盘记录下过程,至于vite速度快于webpack的原因,已经有很多文章讲的很明白,这里只记录下迁移过程和遇到的问题。
二. 迁移前后对比
对比 | 迁移前webpack3 | 迁移后vite2 |
---|---|---|
启动开发模式 | 40秒 | 3秒之内 |
热更新 | 6秒+ | 1秒之内 |
打包构建(vite不做低版本浏览器兼容) | 2分30秒 | 40秒 |
打包构建(vite做低版本浏览器兼容) | 2分30秒 | 1分05秒 |
三. 迁移流程
- 先创建新的vite项目
- 新版vite项目默认是支持vue3的,需要把vue改成vue2版本后配置vite-plugin-vue2插件来支持vue2
- 把项目代码改成vue2写法,确保新vite项目可以正常运行vue2
- 把原webpack项目生产环境依赖复制到vite项目,剔除掉webpack相关的插件依赖
- 复制原项目src文件代码和其他业务相关代码到新vite项目。
- 新vite项目配置开发环境启动命令,根据报错信息来进行调整。
- 在测试开发和打包环境都没问题后,替换原先的项目。
四. 迁移业务代码到vite项目
4.1 创建新的vite项目
打开vite官网,按照官网提示创建新的vite项目(由于原先项目没有用ts,所以创建项目不选ts版本,包管理工具也依然选择是npm)。
npm init vite@latest my-vue-app -- --template vue
创建完成后,使用vs code打开,打开命令行,执行npm i安装依赖
npm i
安装依赖完成后,使用npm run dev启动项目
npm run dev
启动完成后,打开项目地址http://localhost:3000/
此时基本的vite2+vue3项目已经启动成功了,但此时vite支持的还是vue3版本的,我们需要让vite支持vue2版本。
4.2 配置vite支持vue2
此时打开vite.config.js,里面的代码为
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
})
@vitejs/plugin-vue插件是对vue3语法做支持,如果要支持vue2,需要用vite-plugin-vue2
第一步,从vite中删除 @vitejs/plugin-vue配置,从package.json文件中也删除。
npm uninstall @vitejs/plugin-vue -D
第二步,安装vite-plugin-vue2依赖
npm install vite-plugin-vue2 -D
第三步,在vite.config.js文件配置vite-plugin-vue2
import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [createVuePlugin()]
})
第四步,修改vue版本由3改为2版本
npm install vue@2 -S
第五步, 修改main.js,创建根vue实例写法改为vue2写法
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
第六步,修改main.js完成后,修改App.vue文件代码为vue2格式代码
vue
{{title}}
此时package.json, vite.config.js, main.js, App.vue代码分别为
执行npm run dev,即可看到启动成功,代表此时vite已经支持vue2语法了,可以开始项目迁移工作了。
4.3 复制原项目业务代码
第一步,复制原项目静态目录static下文件到vite项目public文件夹下
第二步,复制原项目index.html文件内容替换vite项目的index.html内容(注意本地静态资源引入的路径)替换后需要在body结束标签前添加
运营平台
第三步, 复制package.json中生产环境依赖到新vite项目,去除webpack相关配置依赖,此时最新的vite代码
{
"name": "my-vue-app",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^2.6.14",
"axios": "^0.18.0",
"babel-polyfill": "^6.26.0",
"dayjs": "^1.7.8",
"dingtalk-jsapi": "^2.7.3",
"element-ui": "^2.13.1",
"good-storage": "^1.0.1",
"js-base64": "^2.4.9",
"moment": "^2.23.0",
"nprogress": "^0.2.0",
"qs": "^6.5.2",
"sockjs-client": "^1.3.0",
"stompjs": "^2.3.3",
"swiper": "^4.3.5",
"v-viewer": "^1.2.1",
"vue-router": "^3.0.1",
"vue-virtual-scroller": "^1.0.10",
"vuex": "^3.0.1"
},
"devDependencies": {
"vite": "^2.9.2",
"vite-plugin-vue2": "^2.0.0"
}
}
第四步,复制原项目src业务文件代码,直接替换vite项目src文件,此时整体项目结构如下
4.4 配置vite的项目环境变量
由于原项目使用了cross-env来设置环境变量,为了可以正常启动项目,需要在vite里面也配置下环境变量
第一步,安装cross-env
npm install cross-env -D
第二步,配置scripts脚步命令,使用cross-env来设置环境变量BASE_ENV(这一步要按自己项目情况来配置)
"scripts": {
"dev:dev": "cross-env BASE_ENV=development vite",
"dev:test": "cross-env BASE_ENV=test vite",
"dev:pre": "cross-env BASE_ENV=pre vite",
"dev:prod": "cross-env BASE_ENV=production vite",
"build:dev": "node node_modules/esbuild/install.js && cross-env BASE_ENV=development vite build",
"build:test": "node node_modules/esbuild/install.js && cross-env BASE_ENV=test vite build",
"build:pre": "node node_modules/esbuild/install.js && cross-env BASE_ENV=pre vite build",
"build:prod": "node node_modules/esbuild/install.js && cross-env BASE_ENV=production vite build",
"preview": "vite preview"
}
- 打包添加node node_modules/esbuild/install.js,是用来解决esbuild安装时报错的bug相关issue
- 之所以环境变量没有使用NODE_ENV,是因为很多第三方包都使用NODE_ENV来判断开发环境和打包环境,为了不影响,采用新加一个环境变量的方式
第三步, 配置vite.config.js,使vite在项目里面注入环境变量
只是使用cross-env设置环境变量的话,项目里面是访问不到的,需要配置一下vite的define配置,才能访问到,类似于webpack的DefinePlugin插件功能
import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [createVuePlugin()],
define: {
/** 项目环境变量 **/
'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
},
})
此时前期准备工作都已经完成,可以开始启动项目了,肯定会有很多报错,需要根据报错信息来进行调整
五. 项目开发阶段报错处理
先根据上面配置好的命令,启动开发模式
npm run dev:dev
5.1 第一个报错,The following dependencies are imported but could not be resolved:
由提示信息很容易可以看出,是由于原项目使用webpack配置了别名,但新vite项目未配置别名造成的,我们需要在vite中添加别名配置
根据vite别名规则,vite.config.js添加配置,把@/指向src目录
import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [createVuePlugin()],
define: {
/** 项目环境变量 **/
'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
},
resolve: {
/** 添加alias规则 **/
alias: [
{
find: '@/',
replacement: '/src/'
}
],
},
})
添加后再次重启项目,出现第二个报错
5.2 第二个报错, Failed to resolve import "./App" from "src/main.js
报错是由于引入App组件的时候没有带文件后缀 .vue, 所以未找到,此时有两种解决方案
- 手动添加 .vue后缀,但是项目这么庞大,很多地方都没有带后缀,全部改肯定不容易。
- 配置vite.config.js的extensions字段,来添加自动查找文件扩展名后缀。
采用第二种vite配置extensions扩展名,在vite.config.js里面添加resolve.extensions配置
import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [createVuePlugin()],
define: {
/** 项目环境变量 **/
'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
},
resolve: {
/** 添加alias规则 **/
alias: [
{
find: '@/',
replacement: '/src/'
}
],
/** 暂时先加.vue, .js, .json **/
extensions: [".vue", ".js", ".json"],
},
})
添加后再次重启项目,出现第三个报错
5.3 第三个报错, dependency "sass" not found. Did you install it
由报错信息可知,是因为项目里面使用了sass,less,但是没有对应的解析配置,需要添加一下vite解析sass,less的配置
安装vite中解析sass, less插件
npm install less sass -D
在vite中只需要安装sass,less插件就可以了,vite会自动使用该插件去处理sass,less文件
添加后再次重启项目,出现第四个报错
5.4 第四个报错, Can't find stylesheet to import.
看报错信息是在配置element-ui自定义主题色的时候,在element-variables.scss文件中使用 @import了element-ui的主题文件,使用~前缀,而vite解析不了,所以没有找到对应的文件。
需要把前缀~去掉,直接去引用node_modules下的主题样式文件即可,但是由于element-ui使用calc语法,在sass2.0.0版本会删除对该方法的支持,所以控制台会报警告:
Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.
不影响正常项目运行,如果要解决的话,降低sass的版本就可以了,目前sass版本是1.51.1,手动降版本
npm install [email protected] -D
添加后再次重启项目, 控制台就不会出现警告了。
5.5 第五个报错, require is not defined
这次启动项目后,命令行没有报错了,然后打开浏览器,发现页面白屏,打开控制台看到控制台报错
token.js:18 Uncaught ReferenceError: require is not defined
at token.js
打开对应的token.js使用reuire引入了一张图片,而vite不支持require,我们需要换一种引入方式来引入图片。
有三种方案:
1. 第一种是采用import/from来引入,这种方式适合图片和所有模块,也是最符合规范,利用tree-shrink的。
2. 第二种是直接把图片提前压缩处理后放在public文件下,就可以通过根路径/xxx.png来访问到了。
3. 第三种使用vite提供的import.meta.glob()方法,但该方法返回的是异步的,适合配置懒加载动态路由。
这里我采用的是第二种方式,把boy.png放在public目录下,require引入改成固定字符串 '/boy.png' ,这样打包时就不用对图片做处理了,可以提高打包速度。
全局搜索一下require,把使用require引入图片的地方都改成绝对路径或者import/from引入。
修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面出现了第六个报错
5.6 第六个报错, Failed to resolve import ‘../xxx.png’.
根据提示信息找到对应的文件,能清晰的看到报错原因,是因为页面里面使用img标签,src使用的相对路径
static目录已经被public目录替换掉了,所以对于图片我们还是直接压缩处理后放的public文件夹,项目内使用绝对路径来引入,处理完后变成了
其他页面有类似情况的可以用同样的方式来处理。
修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面可以正常显示登录页面了,但是登录后页面又出现了第7个报错。
5.7 第七个报错, require引入动态异步组件问题.
看报错信息,这次和路由有关系,找到对应文件cache.js,发现页面使用require来引入动态路由组件了。
解决方案,采用vite提供的import.meta.glob方法,写法如下
const modules = import.meta.glob('./../../pages/**/*.vue');
function getBaseRouterWrap(item,PathMap,parentItem) {
return {
path: item.url,
name: item.routerName,
component: modules[`./../../${PathMap[item.routerName]}.vue`],
meta: {
parentName:parentItem ? parentItem.routerName : null,
title: item.name,
tag: true,
icon: item.ico || '',
needLogin: true,
count: item.count,
show: item.show
}
}
}
看一下const modules = import.meta.glob('./../../pages/**/**.vue');的返回结果
import.meta.glob方法会把路径下所有查询到的文件以路径为key,异步返回内容为value放到一个对象里面,使用的时候根据对于的文件名称引入,内容是 () => import('xxx.vue') 异步引入的,正好符合vue-router组件懒加载规则。
修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面可以正常登录跳转页面了,但是跳转后页面又出现了第8个报错。
5.8 第八个报错, ‘xxx.less’ wasn't found..
发现还是因为路径的问题,有两种解决方式:
1. 第一种将所有的~@/改成我们已经配置到别名@/。
2. 新配置一个别名让~@/也指向src目录。
这里采用的第二种方式,比较简单,只改一个地方就好了。
修改后再次重启项目,这次修改后命令后依然没有报错,打开浏览器后页面可以正常跳转了,切换多个页面都没有发现问题,测试一下热更新速度,在某个页面进行测试的时候发现,每次热更新状态都会重置,查看文件加载情况,发现又重新引入了main.js。
5.9 第九个报错, 热更新后触发重新实例化vue流程
在查找原因后,发现在main.js里面定义了退出登录的方法,而在触发热更新的页面从main.js里面引入了该方法,所以每次触发热更新,重新请求该页面 .vue文件时,也会触发重新加载main.js,从而引起该问题的产生,解决方法,把main.js暴露的方法单独抽离到其他js文件里面,抽离后再测试就没问题了。
至此,开发阶段出现的报错就都已经解决了。
六. 项目打包阶段处理
6.1 处理项目兼容低版本浏览器
vite打包后默认目标浏览器是指向现带浏览器,就是支持es module的浏览器,可是在很多时候我们需要兼容低版本的浏览器,vite官方为我们提供了开箱即用的插件@vitejs/plugin-legacy
先安装插件
npm install @vitejs/plugin-legacy -D
在vite.config.js的plguins插件里面添加
import legacy from '@vitejs/plugin-legacy'
legacy({
targets: ['ie >= 9'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
})
targets要根据自己项目情况来配置,插件还有很多其他的配置,可以看插件文档
6.2 处理css加前缀问题
原项目配置了pocss-loader来打包时自动加css前缀来兼容低版本浏览器,所以迁移到vite后也需要处理下,在vite中可以采用autoprefixer来实现,安装:
npm install autoprefixer -D
在vite.config.js的css.postcss.plugins里面添加autoprefixer插件,由于该插件默认只支持common.js,所以要用require引入,后面配置要支持的目标浏览器。
css: {
postcss: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 9', '> 1%'],
grid: true,
}),
]
}
}
配置完成后,再次打包后,就可以看到css已经加上了前缀。
七. 总结
上面的完成后,打包就也没问题了,就可以连接项目git或者覆盖复制到原项目中,提交到测试环境开始构建了。
现在vite的生态也趋于变的成熟,已经可以满足于大部分业务场景了,不过在迁移前还是尽量要做下调研,看目前vite生态是否满足本项目中一些定制化或者特殊的场景,比如微前端,electron支持等。
本文记录了在本项目迁移过程中遇到的问题,遇到的问题肯定只是webpack迁移vite很小的一部分问题,以后在迁移其他项目时候遇到新的问题也会补充进来。
本次迁移最终vite配置
import { defineConfig } from "vite";
import { createVuePlugin } from "vite-plugin-vue2";
import CompressionWebpackPlugin from 'vite-plugin-compression'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
/** 支持vue2 **/
createVuePlugin(),
/** gzip压缩 **/
CompressionWebpackPlugin({
algorithm: 'gzip',
threshold: 10240 //只有大小大于该值的资源会被处理。默认值是 10k
}),
/** 配置浏览器兼容 **/
legacy({
targets: ['ie >= 9'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
],
build: {
chunkSizeWarningLimit: 2000,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
brotliSize: false,
},
/** 为项目注入环境变量 **/
define: {
'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)
},
resolve: {
/** 引入文件未带后缀时,依次查找数组里面配置的后缀文件 **/
extensions: [".vue", ".js", ".json"],
/** 配置alias别名 **/
alias: [
{
find: '@/',
replacement: '/src/'
}
],
},
/** css自动加前缀 **/
css: {
postcss: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 9', '> 1%'],
grid: true,
}),
]
}
},
server: {
host: "0.0.0.0",
},
});
参考资料:
vite官网:https://vitejs.cn