首先第一步通过浏览器看首次加载的问题大小,时间跨度等方面入手
1. Coverage观察
Coverage是chrome开发者工具的一个新功能,从字面意思上可以知道它是可以用来检测代码在网站运行时有哪些js和css是已经在运行,而哪些js和css是还没有用到的,如图,这是我在打开csdn网页时,所显示的已运行和尚未运行的代码情况。
最右边显示的是我们加载的css和js文件数量,红色区域表示已运行的代码,而青色表示已加载但未运行的代码。可用来发现页面中尚未用到的js 和 css代码,你可以为用户只提供必要的代码,这样就可以提升页面的性能。这对于找出可以进行拆分的脚本以及延迟加载非关键脚本来说非常有用。
上面录制的数据中,最大的文件是 vendor.js,如果某个文件覆盖率低(即未使用代码比例很高),通常意味着用户加载了太多不必要的代码(要么真的是无用代码,要么是当前时点还没执行到的代码),有性能常识的同学不难推断出,这会导致页面的完全加载时间、或单页应用的启动时间变慢,在慢速网络下的性能损耗会尤其明显;此外,更多代码的解析、编译也就意味着更多的硬件资源消耗,在低端设备上也会存在明显的性能问题。
以 Coverage 数据为参考,我们能了解页面重无用代码的比例到底有多大。现实世界中,很多工程师可能是在遗留代码库上工作,并且遗留代码库存在的时间还很长,那么很可能这个代码库中存在大量的无用代码,但是谁也不敢删除他们,因为 JS 这门语言的动态性,你不能粗暴的把哪些看起来“没有被使用”的代码直接删掉,除非你很清楚所有的代码执行路径,很显然这对于大型应用或者遗留代码库来说是不现实的。
怎么移除死代码呢?我们可以依赖打包工具,比如 UglifyJS 在压缩代码时支持直接删除死代码的配置项。而 Webpack 2 中引入了 Tree Shaking 的特性,能够自动把项目中没有用到的代码从打包中去掉,但是这种优化仅限于被 export 的代码。总而言之,死代码要尽可能想办法去掉,Coverage 工具能提供一个判断基准。
2. webpack-bundle-analyzer:查看资源树
cnpm i webpack-bundle-analyzer
chainWebpack: config => {
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
}
基本上可以看到整个项目包结构,接下来针对我们的项目开始进行瘦身减肥操作
1. productionSourceMap:false
如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建,如果特殊情况,生产环境上面报错了,为了方便定位问题,可以设置为true,他可以打包出一份源码出来,方便我们定位生产环境上面的问题。不过一般情况下是false
productionSourceMap: process.env.NODE_ENV !== 'production',
2. 路由懒加载
import ShowBlogs from '@/components/ShowBlogs'
routes:[ path: 'Blogs', name: 'ShowBlogs', component: ShowBlogs ]
routes:[ path: 'Blogs',name: 'ShowBlogs',component: () => import('./components/ShowBlogs.vue')
3. 将打包的文件进行切割分包,方便加载的时候能多路加载
// 源(asset)和入口起点超过指定文件限制 会有警告 打包时可以 去掉这个提示
config.performance = {
// 入口起点的最大体积
maxEntrypointSize: 5000000,
// 生成文件的最大体积
maxAssetSize: 3000000,
// hints: 'warning', // 超出大小之后的警告
hints: false
}
4. webpack ContextReplacementPlugin插件
如果项目里面涉及到momen locale zh-cn等可以通过webpack ContextReplacementPlugin插件
webpack 打包momentjs时会把所有语言包都打包,这样会使打包文件很大。此插件可以帮助我们只打包需要的语言包,大大减小打包文件大小。
config.plugins.push(new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/))
5.开启缓存,多线程编译
config.optimization.minimizer.push(
new UglifyJsPlugin({
uglifyOptions: {
warnings: false,
cache: true, // 开启缓存
parallel: true, // 开启多线程编译
compress: {
drop_console: true,
pure_funcs: ['console.log']
}
}
})
)
6. 对打包文件进行切割,控制加载文件个数,缓存已经打包的文件,避免重复打包
config.optimization.splitChunks({
chunks: 'all', // async异步代码分割 initial同步代码分割 all同步异步分割都开启
minSize: 30000, // 字节引入的文件大于30kb才进行分割
// maxSize: 50000, //50kb,尝试将大于50kb的文件拆分成n个50kb的文件
minChunks: 1, // 模块至少使用次数
maxAsyncRequests: 5, // 同时加载的模块数量最多是5个,只分割出同时引入的前5个文件
maxInitialRequests: 3, // 首页加载的时候引入的文件最多3个
automaticNameDelimiter: '~', // 缓存组和生成文件名称之间的连接符
name: true, // 缓存组里面的filename生效,覆盖默认命名
cacheGroups: {
// 缓存组,将所有加载模块放在缓存里面一起分割打包
common: {
// 默认打包模块
priority: 1,
reuseExistingChunk: true // 模块嵌套引入时,判断是否复用已经被打包的模块
// filename: 'common.js'
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: 2
// filename: 'vendors.js'
}
}
})
7. 关闭Prefetch
因为vuecli 3默认开启prefetch(预先加载模块),提前获取用户未来可能会访问的内容
在首屏会把这十几个路由文件,都一口气下载了
所以我们要关闭这个功能,在vue.config.js中设置
// 移除 preload 插件 (去除默认预加载)
config.plugins.delete('preload')
config.plugins.delete('prefetch')
8. 针对图片进行优化,大于10kb的正常加载,小于10kb的进行baseurl转换,可以提升浏览器初始化加载的效率
// 编译图片
config.module
.rule('images')
.test(/\.(png|jpeg|gif|jpg)$/)
.use('url-loader')
.loader('url-loader')
.options({
limit: 1024 * 10, // 小于10k的图片采用baseurl,大于和等于10k的就正常打包成图片
name: 'static/[name].[ext]'
})
9. 针对css是否开始source map
sourceMap: process.env.NODE_ENV === 'production', // 是否为 CSS 开启 source map。设置为 true 之后可能会影响构建的性能
10. 配置babel.config.js文件进行按需加载
plugins: [
"@babel/plugin-proposal-optional-chaining", // 可选链 ?.
'@babel/plugin-proposal-nullish-coalescing-operator', //空值合并 ??
[
"import",
{
libraryName: ["ant-design-vue"],
libraryDirectory: "es",
style: "css"
},
]
]
11. 对发布生产环境的时候开启gzip压缩
if (process.env.NODE_ENV === 'production') {
config.plugin('compressionPlugin').use(
new CompressionPlugin({
test: /\.(js|css|less|html)$/, // 匹配文件名
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false, // 不删除源文件
algorithm: 'gzip',
minRatio: 0.8
})
)
}
下面是整个vue.config.js的配置代码
const path = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const webpack = require('webpack')
// const { options } = require('less')
function resolve(dir) {
return path.join(__dirname, dir)
}
module.exports = {
outputDir: 'dist', // 目录的内容在构建之前会被清除
// qiankuan打包时放开
// 多入口配置
// pages: {
// index: {
// entry: 'src/main.js',
// template: 'public/index.html',
// filename: 'index.html',
// }
// },
publicPath: '/', //根路径 cli3.0以上使用publicPath替代baseUrzl,解决build后找不到静态资源的问题
productionSourceMap: process.env.NODE_ENV !== 'production', // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
lintOnSave: process.env.NODE_ENV !== 'production', //生产构建时禁用 eslint-loader,它的有效值为 'warning' | 'default' | 'error'
assetsDir: 'assets', // 放置生成静态资源的目录
configureWebpack: config => {
// console.log(process.env.NODE_ENV, '--------')
config.plugins.push(new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/))
// 生产环境取消 console.log
if (process.env.NODE_ENV === 'production') {
// config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
// 为生产环境修改配置
config.mode = 'production'
// 源(asset)和入口起点超过指定文件限制 会有警告 打包时可以 去掉这个提示
config.performance = {
// 入口起点的最大体积
maxEntrypointSize: 5000000,
// 生成文件的最大体积
maxAssetSize: 3000000,
// hints: 'warning', // 超出大小之后的警告
hints: false
}
config.plugins.push(new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/))
config.optimization.minimizer.push(
new UglifyJsPlugin({
uglifyOptions: {
warnings: false,
cache: true, // 开启缓存
parallel: true, // 开启多线程编译
compress: {
drop_console: true,
pure_funcs: ['console.log']
}
}
})
)
// config.output = {}
}
},
chainWebpack: config => {
// 移除prefetch插件,此插件是用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容
//因为vuecli 3默认开启prefetch(预先加载模块),提前获取用户未来可能会访问的内容在首屏会把这十几个路由文件,都一口气下载了
config.plugins.delete('prefetch')
config.resolve.alias
.set('@$', resolve('src'))
.set('@api', resolve('src/api'))
.set('@assets', resolve('src/assets'))
.set('@comp', resolve('src/components'))
.set('@views', resolve('src/views'))
// .set('@ant-design/icons/lib/dist$', resolve('src/icons.js'))
config.optimization.splitChunks({
chunks: 'all', // async异步代码分割 initial同步代码分割 all同步异步分割都开启
minSize: 30000, // 字节引入的文件大于30kb才进行分割
// maxSize: 50000, //50kb,尝试将大于50kb的文件拆分成n个50kb的文件
minChunks: 1, // 模块至少使用次数
maxAsyncRequests: 5, // 同时加载的模块数量最多是5个,只分割出同时引入的前5个文件
maxInitialRequests: 3, // 首页加载的时候引入的文件最多3个
automaticNameDelimiter: '~', // 缓存组和生成文件名称之间的连接符
name: true, // 缓存组里面的filename生效,覆盖默认命名
cacheGroups: {
// 缓存组,将所有加载模块放在缓存里面一起分割打包
common: {
// 默认打包模块
priority: 1,
reuseExistingChunk: true // 模块嵌套引入时,判断是否复用已经被打包的模块
// filename: 'common.js'
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: 2
// filename: 'vendors.js'
}
}
})
// 移除 preload 插件 (去除默认预加载)
config.plugins.delete('preload')
// 移除 prefetch 插件 (去除默认预加载)
config.plugins.delete('prefetch')
// 生产环境,开启js\css压缩
if (process.env.NODE_ENV === 'production') {
// 查看线上打包文件大小
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
config.plugin('compressionPlugin').use(
new CompressionPlugin({
test: /\.(js|css|less)$/, // 匹配文件名
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false, // 不删除源文件
algorithm: 'gzip',
minRatio: 0.8
})
)
}
// 更改配置
// 配置 webpack 识别 markdown 为普通的文件
config.module
.rule('markdown')
.test(/\.md$/)
.use()
.loader('file-loader')
.options({
name: 'img/[name].[hash:8].[ext]',
esModule: false // 这里设置为false
})
.end()
// 编译vxe-table包里的es6代码,解决IE11兼容问题
config.module
.rule('vxe')
.test(/\.js$/)
.include.add(resolve('node_modules/vxe-table'))
.add(resolve('node_modules/vxe-table-plugin-antd'))
.end()
.use()
.loader('babel-loader')
.end()
// 编译图片
config.module
.rule('images')
.test(/\.(png|jpeg|gif|jpg)$/)
.use('url-loader')
.loader('url-loader')
.options({
limit: 1024 * 10, // 小于10k的图片采用baseurl,大于和等于10k的就正常打包成图片
name: 'static/[name].[ext]'
})
},
css: {
sourceMap: process.env.NODE_ENV === 'production', // 是否为 CSS 开启 source map。设置为 true 之后可能会影响构建的性能
// extract: {
// ignoreOrder: true //process.env.NODE_ENV === 'production' // 在生产环境下默认就是true的,在开发环境下为false,而且如果在开发环境下改为true的话,热更新就不好使了
// },
loaderOptions: {
less: {
modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */
'primary-color': '#F5222D',
'link-color': '#F5222D',
'border-radius-base': '4px'
},
javascriptEnabled: true
}
}
},
devServer: {
// 设置让浏览器 overlay 同时显示警告和错误
overlay: {
warnings: true,
errors: true
},
port: 10086,
proxy: {
/* 注意:jeecgboot前端做了改造,此处不需要配置跨域和后台接口(只需要改.env相关配置文件即可)issues/3462 很多人此处做了配置,导致刷新前端404问题,请一定注意 */
[process.env.VUE_API_FIX]: {
// target: process.env.VUE_APP_API_BASE_URL,
target: 'http://172.16.1.211:10081',
ws: false,
changeOrigin: true
}
}
}
}