一、背景
1、背景:
一个开发了6年的老项目,项目启动时间和热更新时间越来越慢,在日常维护5、6个项目时,几个项目切来切去启动浪费时间,另外一点一旦同时开了2个项目后保存编译时间也越来越慢,影响开发效率。
技术架构为vue2+webpack(3)+node(12)。
2、技术应用场景
适合所有vue2webpack老项目,大幅度的优化启动时间和编译时间。
3、整体思路
第一步:node由12升级到16,相关依赖插件升级。这一步是因为其他项目node为16,统一node版本。
第二部:webpack改为vite,处理相关不兼容问题。
二、解决过程
2.1优化前的准备工作
1、优化前数据保存
启动时间30s:
保存后编译时间1.5s:
2、因构建包升级,重新拉取一个git,在本地起一个分支,以免影响正在开发的任务。
3、最终效果希望减少项目启动时间在几秒内。
2.2进入优化阶段
1、node12升级为node16相关依赖升级
这一步主要是相关依赖升级,核心插件:
plugin-proposal-optional-chaining这个去除了,升级后默认支持,去除babel相关配置。
2、为什么选择修改构建工具为vite
一开始并没有想升级为vite,当node升级为16版本、相关依赖插件升级后,node的启动时长反而从30s加长到40多s,在优化后效果并不明显,达不到想要的效果,此时在进一步优化有2条路走:
a、检查工具链,分析时间加长的原因,进而解决
b、之前做了个vue3+vite项目,启动在3s内,那是不是vue2也可以兼容vite了
从可以看的到的效果上看,b方案已经是可以看得到的,而检查和分析工具链有相当的复杂度和耗时,并且结果并不确定,综上分析尝试使用b方案。
3、当确定将构建工具webpack改为vite后,官网无解决方法,社区有一些零散的方案,因此选择这个方案注定要碰一些壁。
a、首先对相关包升级,核心工具如下:
b、异常问题解决
(1)环境和启动修改
// 环境启动变量vue改为vite
// env
// webpack
VUE_APP_VERSION
VUE_APP_BUILD
VUE_APP_API_BASE_UR
VUE_APP_UPGRADE_URL
VUE_APP_UPLOAD_LOG
VUE_APP_AUDITION_AUDIO_FILE
// 环境变量值获取
process.env.VUE_APP_MODE
// vite
VITE_APP_VERSION
VITE_APP_BUILD
VITE_BASE_URL
VITE_APP_UPGRADE_URL
VITE_APP_UPLOAD_LOG
VITE_APP_AUDITION_AUDIO_FILE
// 环境变量值获取
import.meta.env.VITE_APP_MODE
(2)删除babel.config.js文件
(3).vue组件引入修改
// webpack
import MainHeader from '@/components/include/mainHeader'
// vite
import HomeMenu from '@/components/include/homeMenu.vue'
(4)~@图片引入修改
// webpack
background-image: url(~@/assets/images/icon-test-ok.png)
// vite
background-image: url(@/assets/images/icon-test-ok.png)
(5)scssdeep样式穿透修改
// webpack
/deep/ .elst-switch-item {}
// vite
::v-deep .elst-switch-item {}
(6)element-ui生产环境下图标、字体显示失败,console有黄色警告
// webpack
@import "~element-ui/packages/theme-chalk/src/index"
// vite
@import "element-ui/packages/theme-chalk/src/index.scss"
(7)require引入修改
// require图片引入问题几种处理方式
// 全局mixin
Vue.mixin({
methods: {
getImageUrl(name) {
return new URL(name, import.meta.url).href
}
}
})
// 动态图片引入
popupImg() {
// webpack
return require(`@/assets/images/history/integral-value-${this.profit}.png`)
// vite
return new URL(`../../../assets/images/history/integral-value-${this.profit}.png`, import.meta.url).href
}
// 静态图片、媒体资源引入
import blockImg from '@/assets/images/empty-block.png'
import audioMatchingSrc from '@/assets/media/arena/matching.mp3'
// 文件引入
//webpack
const configJson = require('@/config/config.json')
// vite
import configJson from '@/config/config.json'
(7)全局组件注册修改
// webpack
const componentsFiles = require.context('./', true, /Result[A-Z]\w+.(vue|js)$/)
let components = {}
componentsFiles.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = componentsFiles(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/.\w+$/, '')
)
)
components[componentName] = componentConfig.default || componentConfig
})
export default Vue => {
for (let key in components) {
Vue.component(key, components[key])
}
}
// vite
async function loadComponents() {
const componentsList = import.meta.glob('./Result*.vue')
const moduleList = []
for (const moduleEle in componentsList) {
const module = componentsList[moduleEle]().then(mod => mod.default)
moduleList.push(module)
}
const componentsFiles = await Promise.all(moduleList)
let components = {}
componentsFiles.forEach(fileName => {
const componentName = upperFirst(
camelCase(
fileName.name.split('/').pop().replace(/.\w+$/, '')
)
)
components[componentName] = fileName
})
return components
}
// 导出一个函数,该函数调用 loadComponents 并等待其完成
export default Vue => {
loadComponents().then(components => {
for (let key in components) {
Vue.component(key, components[key])
}
})
}
(8)main.js文件修改,不支持main.js使用jsx写法,引入插件并修改main.js为main.jsx文件,index引入文件也一并修改,这里index文件位置可能也要修改。
// index.html
// vite.config.js,这种方式埋下一个伏笔后面会讲到
import { createVuePlugin } from 'vite-plugin-vue2'
---
createVuePlugin({
jsx: true,
}),
(9)路由router修改
// 去除用于打包的分类的webpackChunkName
/* webpackChunkName: "group-aistudy" */
/*路由注册修改 这里有个坑 当我在routes.js使用await顶级方式收集所有路由后,项目可在开发环境正常运行,
但是在打包上线后出现白屏路由无法正常加载等情况*/
// 第一种解决方式采用router.addRoute 动态注册
import router from './router'
async function getRoutes() {
const modules = import.meta.glob('./modules/*.js')
const moduleList = [] // 使用具体类型代替 any 类型
for (const path in modules) {
moduleList.push(modules[path]().then(mod => mod.default))
}
const modulesRouteList = await Promise.all(moduleList)
const routeList = modulesRouteList.reduce((prev, curr) => prev.concat(curr), [])
return routeList
}
async function init() {
const modulesRoutes = await getRoutes()
for (let route of modulesRoutes) {
router.addRoute(route)
}
}
init()
export default routes
// 第二种
const modules = import.meta.glob('./modules/*.js', { eager: true })
function getModuleRoutes() {
const routerList = []
Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {}
const modList = Array.isArray(mod) ? [...mod] : [mod]
routerList.push(...modList)
})
return routerList
}
const modulesRoutes = getModuleRoutes()
const allRoutes = routes.concat(modulesRoutes)
export default allRoutes
(10)api以前老项目有将路由注册到windows全局调用情况,在这种情况下需要将接口导出,否则无法正常引用
// 不确定是否被注册到全局引用,在路由文件加一个导出
export const getDashboard = () => http.get(ApiPath.dashboard, {})
export const getModuleList = () => http.get(ApiPath.skillup_module_list)
export const getBanner = () => http.get(ApiPath.banner, {})
// 下面添加
export default {
getDashboard,
getModuleList,
getBanner,
}
(11)*as写法修改
// webpack
import * as resourceFeedback from './resourceFeedback'
// vite
import resourceFeedback from './resourceFeedback'
(12)vue.config.js文件改为vite.config.js文件
发现在不做构建优化的情况下,vite首屏加载速度要明显比webpack慢。
原因在于有些文件打包后比较大,vite首屏一次性加载所有文件,加快了启动速度,牺牲了首屏加载速度,所以在这种情况下,中大型项目需评估是否使用。
本项目优化后首屏加载速度基本与webpack持平。
export default () => {
return defineConfig({
plugins: [
createVuePlugin({
jsx: true,
}),
createSvgIconsPlugin({
// 指定图标文件夹,绝对路径(NODE代码)
iconDirs: [path.resolve(process.cwd(), 'src/assets/svg')],
symbolId: 'icon-[name]',
inject: 'body-last',
customDomId: '__svg__icons__dom__'
}),
compression({
algorithm: 'gzip', // 压缩算法
ext: '.gz', // 压缩文件后缀名
}),
],
resolve: {
extensions: [".vue", ".js", ".json"],
/** 添加alias规则 */
alias: [
{
find: '@/',
replacement: '/src/'
}
],
},
css: {
preprocessorOptions: {
scss: {
additionalData: `
@use "@/assets/scss/_variables.scss" as *;
@use "@/assets/scss/_mixin.scss" as *;
`,
},
},
},
build: {
target: 'esnext',
// 启用 Tree Shaking
treeShaking: true,
// 启用代码分割
codeSplit: true,
// 压缩输出文件大小限制,单位 KB
maxAssetSize: 500,
// 压缩输出文件数量限制
maxFileCount: 100,
terserOptions: {
compress: {
//生产环境时移除console
drop_console: true,
drop_debugger: true,
}
},
brotliSize: false,
outDir: 'dist',
rollupOptions: {
minify: 'terser',
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]',
manualChunks(id) {
if(id.includes('node_modules')){
return id.toString().split('node_modules/')[1].split('/')[0].toString()
}
}
},
},
},
})
}
(13)vite-plugin-vue2无法兼容渲染element-uitable组件,渲染空白,改为@vitejs/plugin-vue2、@vitejs/plugin-vue2-jsx
import vue from '@vitejs/plugin-vue2'
import vueJsx from '@vitejs/plugin-vue2-jsx'
export default () => {
return defineConfig({
plugins: [
vue(),
vueJsx(),
---
(14)ios低版本兼容性问题
// vite.config.js
// ios版本兼容15以上
build: {
target: 'esnext'
...
}
// 亲测ios版本兼容12以上,12以下没测过
build: {
target: 'es2015'
...
}
// 更低版本兼容 安装@vitejs/plugin-legacy
import { defineConfig } from 'vite';
import legacyPlugin from '@vitejs/plugin-legacy';
export default defineConfig({
plugins: [
legacyPlugin({
targets: ['defaults', 'not IE 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
renderLegacyChunks: true,
polyfills: [
'es.symbol',
'es.promise',
'es.promise.finally',
'es/map',
'es/set',
'es.array.filter',
'es.array.for-each',
'es.array.flat-map',
'es.object.define-properties',
'es.object.define-property',
'es.object.get-own-property-descriptor',
'es.object.get-own-property-descriptors',
'es.object.keys',
'es.object.to-string',
'web.dom-collections.for-each',
'esnext.global-this',
'esnext.string.match-all'
]
})
],
build: {
target: 'es2015',
}
});
三、总结
3.1升级效果:
a、启动时间约5s内,二次启动时间3s内:
b、热更新时间:vite最大优势,保存即编译,无感编译时间。
3.2技术经验:
本次vue2webpack构建转vite,通用于vue2webpack项目,适合中小型项目,可大幅度提升项目启动时间,编译时间,提升开发效率。
3.3有待提升:
1、vite生成构建包上,配置不如webpack灵活,生成上线包,拆包拆的过碎,首次加载大量文件,约700个文件,后期可分析进行文件合并。
2、项目上因为文件不是很多,采用直接修改的方式,如果文件较多可采用开发脚本方式进行修改。
3、本项目是直接修改webpack改成vite,目前最好是可以开发环境用vite生产环境仍然用webpack。
4、随着vite更新迭代,社区逐渐完善vite在不久的将来在打包上有不下于webpack优势。