一、优化工作前准备
二、配置打包环境,使用webpack4 自带的分包功能
三、路由懒加载,按需引入
四、CDN替换依赖包引入
五、查看首屏文件加载,细节分析
六、开启gzip压缩
七、开启图片压缩
--------------------------------------------------------------------------
首先,添加打包文件分析插件,后续相关信息都需要依赖于此进行对比查看
1. 添加插件
npm i -D webpack-bundle-analyzer
2. 修改 vue.config.js(文末有项目完整vue.config.js文件)
// 引入js分析插件
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
// 环境区分主要为开发环境与其他环境(其他:生产,uat,测试等等)
const isNotDevelopMentEnv = process.env.NODE_ENV !== 'development'
chainWebpack (config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// 配置启用打包文件分析
if (isNotDevelopMentEnv) {
// js文件包分析
if (process.env.npm_config_report) {
config
.plugin('webpack-bundle-analyzer')
.use(BundleAnalyzerPlugin)
.end()
}
}
}
3. 使用命令(我本地打包命令是npm run build,这里看各自项目对应命令,重点是在打包命令后面加上--report),打包完会自动打开浏览器显示项目中js文件情况,后续所做的优化效果,都将通过该视图进行查看对比
npm run build --report
4. 初始项目情况
1. js文件分析
2. 首屏加载情况(本地node服务器运行,模拟3G网络,后续一致)
1. 项目使用的是vue-element-admin,默认已经做了代码分包,在vue.config.js中可以看到配置。
chainWebpack (config) {
config
.when(isNotDevelopMentEnv,
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single')
}
)
}
}
但是因为本地新建了许多环境文件,其中没有设置这些环境文件的NODE_ENV为production,导致分包功能丢失(原有分包功能是判断NODE_ENV为production才会执行)
2. 环境文件添加NODE_ENV,对应环境打包就会使用分包功能
# 在以下环境中添加该句:表示当以下环境打包时使用分包
# .env.test (公司测试环境)
# .env.prd (公司生产环境)
# .env.pet (性能测试环境)
# .env.uat (用户测试环境)
NODE_ENV=production
3. 项目情况
1. js文件分析
2. 首屏加载情况
1. 路由引入:使用 () => import('文件名')进行懒加载
{
path: '/default/iconTool/Business',
component: () => import('@/views/default/iconTool/Business')
}
2. 需要额外考虑:项目会根据接口返回的菜单数据动态生成路由,使用此方式会根据页面路由各自生成对应文件,但在开发时候会因为热更新而频繁加载导致变慢,所以需要区分开发与生产环境分开
2.1 开发环境使用require一次性引入页面
2.2 生产环境使用import懒加载
const notFoundComponent = () => import('@/views/default/404.vue')
try {
if (process.env.NODE_ENV === 'development') {
// 开发环境一次性加载,避免多路由热更新缓慢
component = require(`@/views/${item.path}.vue`).default
} else {
// 打包环境,按照路由懒加载拆分页面
component = () => import(`@/views/${item.path}.vue`)
// 当上面路由找不到时会使用下面模块,这里不写到时404页面会出不来
// 由于import是使用懒加载,只有在使用的时候才会加载该页面,
// 因此import时会直接使用页面地址,即使页面地址不存在也不会报错,
// 也无法使用404替换当真正使用的时候就会找不到页面而报错,因此需要
// 再这里多添加404,找不到时会可以使用404页面
component = notFoundComponent
}
} catch (e) {
component = notFoundComponent
}
3. 注意:
3.1 按道理是全局的路由(包括已有的静态的路由 && 动态添加的路由)引入全部使用条件判断去决定require还是import引入,将这里的逻辑封转成函数,然后引入路由的地方统一使用为函数;但在实际测试中部分页面路由使用此种方式引入会异常,所以折中处理,静态路由import引入,动态路由根据环境对应引入。项目可自行测试,也许你们的可以直接使用此方式
3.2 使用 () => import('filePath')时,要求filePath必须是显示字符串,不能使用变量,但这里可以使用【模版字符串 + 部分已明确路径】实现动态路径
component = () => import(`@/views/${filePath}.vue`)
3.3 这里根据环境对应引入会有一个问题,就是必须将所有动态路由都预先定义在路由文件中,不然就会出现本地用require正常,但是生产环境上面使用import就会报错找不到页面
3.4 根据环境引入还有个问题,就是匹配不到404页面逻辑了。猜想:使用import是懒加载,只有在用到页面才会去加载,假如配置了某个路径页面是不存在的,因为懒加载所以不会执行检测到错误,也就不会跑进用404页面替换当前路径的逻辑,所以看第2小点中代码注释说明,我是在import后面还加了一句设置404页面,这样页面地址存在的时候就会使用页面,不存在就会展示404页面,也不会报错
4. 项目情况
4.1 js文件分析
4.2 首屏加载情况
5. 路由懒加载分组
可查看 vue-router 懒加载分组 webpackChunkName相关知识
{
path: '/login',
component: () => import(/* webpackChunkName: "login" */ '@/views/default/Login')
}
主要就是直接懒加载的话模块太多,请求数太多,可以考虑合并模块,减轻服务器压力,但在实际优化中发现速度没有多大变化,可能还没领悟到精髓,这里就不做过多描述,项目自行考虑是否采用此优化,贴两张图意思
1. 项目情况
2. 项目打包时会根据依赖关系自动打包压缩依赖文件,当依赖文件过大,会导致首屏加载变慢
3. 使用CDN: 简单来说就是可以使用CDN引入依赖库,减少依赖包体积,释放服务器压力, 它由距离最近的缓存服务器直接响应,提高加载速度
4. 进一步考虑:当我们在开发环境下,使用CDN引入会比我们直接引入依赖要慢,所以配置CDN需要只在生产环境
5. 配置步骤
5.1 修改vue.config.js
5.1.1 定义使用CDN的相关数据
// 环境区分主要为开发环境与其他环境(其他:生产,uat,测试等等)
const isNotDevelopMentEnv = process.env.NODE_ENV !== 'development'
const cdnData = {
css: [
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css'
],
js: [
'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js',
'https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js',
'https://cdn.bootcdn.net/ajax/libs/vuex/3.1.0/vuex.min.js',
'https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.6/vue-router.min.js',
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
'https://cdn.bootcdn.net/ajax/libs/jquery/1.12.1/jquery.min.js',
'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/vee-validate.min.js',
'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/locale/zh_CN.js'
],
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
'vuex': 'Vuex',
'axios': 'axios',
'vee-validate': 'VeeValidate',
'jQuery':"jquery",
'jquery': 'window.$'
}
}
5.1.2 在configureWebpack中添加externals
configureWebpack: {
externals: isNotDevelopMentEnv ? cdnData.externals : {}
}
5.1.3 在chainWepack中添加如下
if (isNotDevelopMentEnv) {
config.plugin('html')
.tap(args => {
args[0].cdn = cdnData
return args
})
}
5.2 修改 public/index.html(根据环境,开发环境不使用CDN,生产环境才开放)
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<% } %>
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<% } %>
5.3 注意: 采用CDN引入后,不需要删除原有依赖引入,因为在本地还是使用这些依赖进行调试的,打包后因为有CDN所以不会把这些依赖引入所以不用担心,import引入的不需要变更。例如main.js中使用import ElementUI from 'element-ui', 以上的代码已经实现在开发环境会设置不适用CDN,会使用依赖包文件;当发布到生产环境,因为我们已经在vue.config.js的externals中指代了element-ui,所以这个语句也是有效的可以直接使用CDN elementUI
6. 项目情况
6.1 js文件分析
6.2 首屏加载情况
1. elementUI css资源发现重复引入,因为项目的使用主题化的时候已经引入样式文件,所以main.js中可以不需要引入elementUI样式文件了
// 必须使用主题化,ui组件的样式的颜色才会同步,统一UI颜色风格
$--color-primary: $primary;
$--color-danger: $danger;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
具体看各自项目,自行优化,我这只是提提这边的优化
2. 关于elementUI的按需加载优化:因为项目修改了elementUI的主题色,如上图代码
@import "~element-ui/packages/theme-chalk/src/index";
这一句在按需加载后会失效,不知该如何实现按需加载后后的主题修改,所以暂时放弃按需加载,还是CDN直接处理
3. 项目情况
1. 安装插件
npm i -D compression-webpack-plugin
2. 配置vue.config.js
const CompressionPlugin = require('compression-webpack-plugin') // 引入gzip压缩插件
chainWebpack (config) {
// 配置启用打包文件分析
if (isNotDevelopMentEnv) {
// gzip压缩
config.plugin('compression').use(CompressionPlugin, [
{
algorithm: 'gzip',
test: new RegExp('\\.(js|css)$'),
threshold: 10240, //超过多少字节进行压缩
minRatio: 0.8 //至少压缩到原来体积的0.8,才会进行压缩
}
])
}
}
3. 需要服务器配合开启gzip
3.1 服务器为nginx,修改nginx.conf文件
server {
gzip on;
gzip_buffers 4 16K;
gzip_comp_level 5;
gzip_min_length 100k;
gzip_types text/plain application/x-javascript application/javascript application/json text/css application/xml text/javascript image/jpeg image/gif image/png;
gzip_vary on;
}
// gzip on|off; 是否开启gzip
// gzip_min_length 100k; 压缩的最小长度(再小就不要压缩了,意义不在)
// gzip_buffers 4 16k; 缓冲(压缩在内存中缓冲几块? 每块多大?)
// gzip_comp_level 5; 压缩级别(级别越高,压的越小,越浪费CPU计算资源)
// gzip_types text/plain; 对哪些类型的文件用压缩 如txt,xml,html,css,js等
// gzip_vary on|off; 是否传输gzip压缩标志
3.2 服务器为tomcat,修改server.xml文件
// compression="on" 打开压缩功能
// compressableMimeType="text/html,text/xml" 压缩类型
// useSendfile="false" 设置该属性将会压缩所有文件,不限阙值,不然可能按照阙值部分压缩
注意:在网上查阅文章的时候,发现有文章把【useSendfile】这个单词写错了,一直没效果,注意单词不要写错
4. 项目情况
4.1 js文件分析(注意切换到Gzipped模式查看,这个就是到时使用gzip后的文件大小)
4.2 首屏加载情况
1. 安装插件
npm install -D image-webpack-loader
2. vue.config.js
chainWebpack (config) {
if (isNotDevelopMentEnv) {
// 开启图片压缩-使用异常
config.module.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({ bypassOnDebug: true })
}
}
3. 图片变化
压缩前
压缩后
参考文章:
https://blog.csdn.net/sixam/article/details/106058083?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2
https://segmentfault.com/a/1190000021444697
https://blog.csdn.net/sinat_17775997/article/details/83023148
https://blog.csdn.net/Newbie___/article/details/104925587?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
https://juejin.im/post/5bd02f98e51d457a944b634f
'use strict'
const path = require('path')
const defaultSettings = require('./src/settings.js')
const webpack = require('webpack')
// 引入js分析插件
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const CompressionPlugin = require('compression-webpack-plugin') // 引入gzip压缩插件
function resolve(dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title || 'vue Admin Template' // page title
// If your port is set to 80,
// use administrator privileges to execute the command line.
// For example, Mac: sudo npm run
// You can change the port by the following methods:
// port = 9528 npm run dev OR npm run dev --port = 9528
const port = process.env.port || process.env.npm_config_port || 8000 // dev port
// 环境区分主要为开发环境与其他环境(其他:生产,uat,测试等等)
const isNotDevelopMentEnv = process.env.NODE_ENV !== 'development'
// 非正式环境:包括开发环境与测试环境
const isNotRegularEnv = process.env.VUE_APP_ENV === 'development' ||
process.env.VUE_APP_ENV === 'test'
const cdnData = {
css: [
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css'
],
js: [
'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js',
'https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js',
'https://cdn.bootcdn.net/ajax/libs/vuex/3.1.0/vuex.min.js',
'https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.6/vue-router.min.js',
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
'https://cdn.bootcdn.net/ajax/libs/jquery/1.12.1/jquery.min.js',
'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/vee-validate.min.js',
'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/locale/zh_CN.js'
],
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
'vuex': 'Vuex',
'axios': 'axios',
'vee-validate': 'VeeValidate',
'jQuery':"jquery",
'jquery': 'window.$'
}
}
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
/**
* You will need to set publicPath if you plan to deploy your site under a sub path,
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
publicPath: '/',
outputDir: 'ROOT',
assetsDir: 'static',
lintOnSave: !isNotDevelopMentEnv,
// TODO 是否启动问题源码追踪
productionSourceMap: isNotRegularEnv,
css: {
loaderOptions: {
sass: {
data: `@import "@/styles/global.scss";`
}
}
},
// 兼容ie浏览器
// entry: ['babel-polyfill', './app/js'],
devServer: {
port: port,
open: false,
compress: true,
overlay: {
warnings: false,
errors: true
},
// proxy: {
// // change xxx-api/login => mock/login
// // detail: https://cli.vuejs.org/config/#devserver-proxy
// [process.env.VUE_APP_BASE_API]: {
// target: `http://127.0.0.1:${port}/mock`,
// changeOrigin: true,
// pathRewrite: {
// ['^' + process.env.VUE_APP_BASE_API]: ''
// }
// }
// },
// after: require('./mock/mock-server.js')
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: name,
resolve: {
alias: {
'@': resolve('src')
}
},
plugins: [
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery",
"windows.jQuery":"jquery"
}),
],
// 性能提醒
performance: {
// 提醒方式
hints: "warning",
// 文件大小峰值控制(单位:kb)
maxAssetSize: 10000
},
externals: isNotDevelopMentEnv ? cdnData.externals : {}
},
chainWebpack (config) {
config.plugins.delete('preload') // TODO: need test
config.plugins.delete('prefetch') // TODO: need test
// 配置启用打包文件分析
if (isNotDevelopMentEnv) {
// 开启图片压缩-使用异常,暂时注释
// config.module.rule('images')
// .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
// .use('image-webpack-loader')
// .loader('image-webpack-loader')
// .options({ bypassOnDebug: true })
// js文件包分析
if (process.env.npm_config_report) {
config
.plugin('webpack-bundle-analyzer')
.use(BundleAnalyzerPlugin)
.end()
}
// 配置html文件引入变量
config.plugin('html')
.tap(args => {
args[0].cdn = cdnData
return args
})
// gzip压缩
config.plugin('compression').use(CompressionPlugin, [
{
algorithm: 'gzip',
test: new RegExp('\\.(js|css)$'),
threshold: 10240, //超过多少字节进行压缩
minRatio: 0.8 //至少压缩到原来体积的0.8,才会进行压缩
}
])
}
// set svg-sprite-loader
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
// set preserveWhitespace
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.compilerOptions.preserveWhitespace = true
return options
})
.end()
config
// https://webpack.js.org/configuration/devtool/#development
.when(!isNotDevelopMentEnv,
config => config.devtool('cheap-source-map')
)
config
.when(isNotDevelopMentEnv,
config => {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // only package third parties that are initially dependent
},
elementUI: {
name: 'chunk-elementUI', // split elementUI into a single package
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
},
commons: {
name: 'chunk-commons',
test: resolve('src/components'), // can customize your rules
minChunks: 3, // minimum common number
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk('single')
}
)
}
}