开局不多说,先放两张图
重构前
重构后
起因:项目之前是jeecg boot的一个开源前端模板二开的,我也搞不明白为什么一个体量不大的项目使用这么重的模板,后来问了那个负责的同事,随便找了一个!!!
然后这个模板打包出来的代码很大,刚开始能达到20MB !!!,然后主管安排我来优化,包括后续的开发重构等也是我来负责,算是我成了这个项目的负责人了,本着一码归一码的
原则,我还是对这个项目进行了一些力所能及的优化,但是性能依旧很差劲,经过webpack打包工具结合控制台分析之后,发现该项目有很严重的性能问题,主要有如下几个方面:
1. jeecg boot 模板自身耦合度特别高,很笨重,而且集成了Antdesign vue
2. 同事全局引入了 element ui 和 各种 我不知道用途的插件
3. JavaScript 部分严重冗余,很多代码明明用不上,但是还是在入口文件中导入了,导致入口文件特别大
4. 滥用动态导入 () => import()
5. 完全没有进行代码拆分
然后为了让用户用的放心,用的开心 (违心话语...)
我有必要把这个项目当成自己的项目来打造,于是,开始了以下的过程
要想性能优化,肯定得舍弃掉他原来的模板,所以第一步就是:自己手写一个简单但功能相当的“外壳”,于是有了如下代码:
<template> <el-container> <el-aside :width="collapse ? '60px' : 'var(--menu-width)'"> <Menu></Menu> </el-aside> <el-container> <el-header> <Navbar></Navbar> </el-header> <el-main> <Navigation></Navigation> <AppMain></AppMain> </el-main> </el-container> </el-container> </template>
//此处省略若干代码片段....
路由权限校验缓存部分全部重写
import router from '@/router/index' import storage from '@/helpers/storage' import store from '@/store/index' router.beforeEach((to, from, next) => { //此处省略若干代码片段... if (storage.userInfo.find()) { insertMenuToStore() insertUserinfoToStore() insertNavigationToStore() if (to.name === 'login') { return next({ name: from.name, }) } } next() }) function insertMenuToStore() { const userInfo = JSON.parse(storage.userInfo.find()) if (Array.isArray(userInfo.roleDTO.list)) { store.commit('set_menu', userInfo.roleDTO.list) } } function insertUserinfoToStore() { const userInfo = JSON.parse(storage.userInfo.find()) store.commit('set_userinfo', userInfo) } function insertNavigationToStore() { const navigations = storage.navigations.find() if (navigations) { store.commit('set_navigations', JSON.parse(navigations)) } }
构造好壳子和路由部分之后,原来项目中我们基本上复写了所有代码,再由于,路由部分的逻辑还是按照原来的逻辑来写的,所以代码可以直接拿过来
第二步,就是 去掉Antdesign Vue,因为在原来项目中,我发现只有菜单和快捷导航部分和布局部分. 也就是说壳子部分是Antdv来写的,他自己又全局引入了Elementui
所以我在第一步用Elementui实现了壳子,此时Antdv已经完全没用了,删掉即可
第三步,我就进行了入口文件提纯,因为原来的项目入口文件中充斥了各种各样的组件,包,代码,大部分都是jeecg带的,还有一部分我也不知道是干嘛的,
然后我基于项目业务对main.js中的内容进行不断对比删除,最终只留下必要的代码和导入
第四步,就是router.config.js中路由规则很多,所有的路由规则全部采用的是 动态导入,打包之后发现有很多 10kb 以下的 js 和 css文件,因为动态导入的.vue文件会被编译成独立的.js和.css文件来引入,所以一个这种小的文件,明明没占多少空间,却白白浪费了两次http请求去加载文件,我首先把一些小文件的动态导入全部改成直接导入,然后再把我项目中的config文件夹下的代码单独分割成一个文件,控制分割代码的生成最小尺寸为30 * 1000 kb,具体配置接下来会说的
第五步,就是项目构建优化部分,分析如下:
项目用了很多elementui的组件,没有意义再去按需加载,项目用了lodash,项目用了echarts,代码合理分割,减小入口文件体积,
所以基于以上分析,我决定对 elementui echarts vue 采用 cdn的方式引入,然后代码分割,将入口文件代码中的大文件,分割出来,让浏览器并行加载
代码如下:
// vue.config.js
const { defineConfig } = require('@vue/cli-service') const LodashModuleReplacementPlugin = require('lodash-webpack-plugin') const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') module.exports = defineConfig({ productionSourceMap: false, transpileDependencies: true, chainWebpack: (config) => { config.externals({ echarts: 'echarts', vue: 'Vue', 'element-ui': 'ELEMENT', }) config.plugin('loadshReplace').use( new LodashModuleReplacementPlugin({ shorthands: true, }) ) if (process.env.NODE_ENV === 'development') config.plugin().use(new BundleAnalyzerPlugin()) config.output.chunkFilename('[name].[hash:4].js') config.optimization.splitChunks({ minSize: 30 * 1000, cacheGroups: { srcConfig: { test: /[\\/]src[\\/]config[\\/]/, name: 'srcConfig', chunks: 'all', priority: 5, }, vendors: { test: /[\\/]node_modules[\\/]/, name: 'verdors', chunks: 'all', priority: 5, }, }, }) }, })
<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title>*********</title> <script src="https://cdn.bootcss.com/echarts/4.7.0/echarts-en.common.min.js"></script> <script src="//cdn.staticfile.org/vue/2.6.11/vue.min.js" defer></script> <script defer crossorigin="anonymous" integrity="sha512-BFTa+pOeQxEAgwQyr6fiiE0obFmW6DxY5lRV+yamYVJVgbfQKK5ZYs9wpprA51If77YXnqPvbmXNMmStfctlqA==" src="//lib.baomitu.com/element-ui/2.15.5/index.js" ></script> <link crossorigin="anonymous" integrity="sha512-D0p8FADInHcCyZ+ZKKz2A3xHSWnzJZWdmNfQIVOETyJa/ohn5FpD8/AqD46mwDeSPl9hfFHKDKxhogTiWQyv3g==" href="//lib.baomitu.com/element-ui/2.15.5/theme-chalk/index.min.css" rel="stylesheet" media="bogus" /> </head> <body> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div id="app"></div> <!-- built files will be auto injected --> <link crossorigin="anonymous" integrity="sha512-D0p8FADInHcCyZ+ZKKz2A3xHSWnzJZWdmNfQIVOETyJa/ohn5FpD8/AqD46mwDeSPl9hfFHKDKxhogTiWQyv3g==" href="//lib.baomitu.com/element-ui/2.15.5/theme-chalk/index.min.css" rel="stylesheet" /> </body> </html>
注意:这里ElementUI的css cdn 引入方式,为什么要这样写,因为是想在页面渲染之前跳过 css 的 render
Meanwhile, way up in the <head>
, browsers insist on downloading stylesheets with media
attributes they could never fulfill, like media="print"
without a printer connected
意思就是:这个css其实就会异步的去下载,并应用,不会引起 render blocking,然后我们这里指定的media是 bogus,所以浏览器不会进行 render blocking
然后我们在body属性再插入一个相同的但是没有 media属性的link标签,这样就可以等DOM渲染完成再去 执行css文件了,又因为在head中css文件已经被下载好了,所以这里直接就能用,不会去再次请求该资源
以上就是我对该项目进行优化的方式
22:13:36