项目用的是vue 2.x + @vue/cli 4.x(webpack 4.x)。冷启动项目时耗时较长,达到了2分钟以上。热更新时间也达到了10s以上。严重影响了开发体验和效率。
解决问题的过程中遵循2个原则:
- 不对会影响原构建流程的代码进行改动,向原Webpack构建兼容。
- 只引入开发阶段的构建服务。
安装vite
vue需要2.7.X
vue-template-compiler需与vue版本一致
相关依赖
package.json对比
相关依赖
//package.json
"scripts":{
"serve:vite": "IS_IN_VITE=true vite",
},
"dependencies": {
"vue": "2.7.16",
},
"devDependencies": {
"@vitejs/plugin-vue2": "^2.3.1",
"@vitejs/plugin-vue2-jsx": "^1.1.1",
"speed-measure-webpack-plugin": "^1.5.0",
"unplugin-auto-import": "^0.18.0",
"vite": "^5.3.3",
"vite-plugin-antdv-fix": "^1.0.3",
"vite-plugin-commonjs": "^0.10.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-lang-jsx": "^1.5.3",
"vite-plugin-require-transform": "^1.0.21",
"vue-template-compiler": "2.7.16",
"webpack-strip-block": "^0.3.0",
}
新建vite.config.js
主要处理vue-cli的
//vite.config.js
import path from 'node:path'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue2'
import { createHtmlPlugin } from 'vite-plugin-html'
import vueJsx from '@vitejs/plugin-vue2-jsx'
import langJsx from 'vite-plugin-lang-jsx'
import commonjs from 'vite-plugin-commonjs'
import antdvFix from 'vite-plugin-antdv-fix'
const assetsCDN = {
// webpack build externals
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
'./cptable': 'var cptable',
axios: 'axios'
},
css: [],
js: [
'/js/vue.min.js',
'/js/vue-router.min.js',
'/js/vuex.min.js',
'/js/axios.min.js'
]
}
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd())
const isPro = mode === 'production'
function getEnv(key) {
return loadEnv(mode, process.cwd(), '')[key]
}
return {
// 部署生产环境和开发环境下的URL。
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
base: '/',
define: {
'process.env': process.env,
IS_IN_VITE: true,
},
plugins: [
langJsx(),
vue(),
vueJsx(),
antdvFix(),//解决ant-design-vue的date-picker组件在使用时报错
createHtmlPlugin({
minify: true,
/**
* 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除
* @default
*/
entry: '/src/main.js',
/**
* 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置
* @default index.html
*/
template: 'public/index.html',
inject: {
data: {
htmlWebpackPlugin: {
options: {}
}
}
},
}),
commonjs({
filter(id) {
// `node_modules` is exclude by default, so we need to include it explicitly
// https://github.com/vite-plugin/vite-plugin-commonjs/blob/v0.7.0/src/index.ts#L125-L127
if (
id.includes('node_modules/@jiaminghi') ||
id.includes('node_modules/viser') ||
id.includes('node_modules/@antv')
// id.includes('node_modules/@babel/runtime')
) {
return true
}
},
dynamic: {
loose: false,
}
}),
],
optimizeDeps: {
// include: ['viser-vue'],
// exclude: ['@jiaminghi/c-render'],
},
resolve: {
// https://cn.vitejs.dev/config/#resolve-alias
alias: [
// 设置别名
{ find: /^~(?!@)/, replacement: '' }, // 将 ~ 替换为空字符串,以支持直接从 node_modules 导入
{ find: /^~@/, replacement: path.resolve(__dirname, './src') }, // 将 ~@ 替换为项目中的 src 目录
{ find: '@', replacement: path.resolve(__dirname, './src') }, // 将 @ 替换为项目中的 src 目录
],
// https://cn.vitejs.dev/config/#resolve-extensions
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},
// vite 相关配置 代理
server: {
port: 9001,
host: true,
open: true,
proxy: {
'/api': {
target:getEnv('VUE_APP_API_BASE_URL'),
ws: false,
changeOrigin: true,
pathRewrite: {
// 需要rewrite的path
}
},
'/epros': {
target: 'http://dopeprostest.pipechina.com.cn:30080/api',
ws: false,
changeOrigin: true,
pathRewrite: {
'^/epros': ''
// 需要rewrite的path
}
}
}
},
// fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
css: {
preprocessorOptions: {
less: {
modifyVars: {
'primary-color': '#1890FF',
'layout-color': '#1890FF',
'border-radius-base': '2px'
},
// DO NOT REMOVE THIS LINE
javascriptEnabled: true
}
},
postcss: {
plugins: [
{
postcssPlugin: 'internal:charset-removal',
AtRule: {
charset: (atRule) => {
if (atRule.name === 'charset') { atRule.remove() }
},
},
},
],
},
},
}
})
vue 动态路由vite处理
let route_modules = null
/* develblock:start */
if (window.IS_IN_VITE) {
route_modules = import.meta.glob('@/views/**/*.vue')
let _routeModules = {}
Object.keys(route_modules).forEach(key => {
const dir = key.split('views/')[1].split('.vue')[0];
_routeModules[dir] = route_modules[key]
})
route_modules = _routeModules
}
/* develblock:end */
/**
* 将后台返回的动态路由配置组件字符串,转换为动态导入语句
* 用于本地开发环境使用Vite打包的动态路由
*/
export const loadView = (view) => {
let res;
for (const path in route_modules) {
// const dir = path.split('views/')[1].split('.vue')[0];
if (path === view) {
res = () => route_modules[path]();
}
}
return res;
}
// 格式化菜单数据
export const generator = (routerMap, parent) => {
return routerMap.map(item => {
// eslint-disable-next-line no-unused-vars
const { title, show, hideChildren, hiddenHeaderContent, target, icon, link } = item.meta || {}
const menuComponent = item.component
const currentRouter = {
path: item.path || `${(parent && parent.path) || ''}/${item.key}`,
// 路由名称,建议唯一
name: item.name || item.key || '',
// 该路由对应页面的 组件 :方案2 (动态加载)
component: constantRouterComponents[item.component || item.key] || (route_modules ? loadView(item.component) : (() => import(`@/views/${item.component}`))),
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
meta: {
title: title,
icon: icon || undefined,
// hiddenHeaderContent: hiddenHeaderContent,
target: target,
link: link
}
}
....
由于在Webpack编译时import.meta.glob语法无效会导致编译出错,所以引入webpack-strip-block,在Webpack编译时删除掉模块中指定注释块中的代码,避免编译错误。
//vue.config.js
chainWebpack: config => {
config.module.rule('strip-block')
.test(/\.js$/)
.pre()
.exclude.add(/(node_modules|bower_components|\.spec\.js)/)
.end()
.use('webpack-strip-block')
.loader('webpack-strip-block')
....
}
~@assets处理
<img src="~@/assets/index/refresh.svg">
改为
<img src="@/assets/index/refresh.svg">
诡异报错
报错1
解决方法:
报错2
解决方法:
@import "../index";
运行
vite和vue/cli均支持,页面正常加载