如何实现前端项目的性能优化
在实习期间,有幸参与了项目的优化。
目前的系统存在的问题:项目打包的时间较长,打包后的体积较大;访问页面发现首次内容绘制时间、首次交互延时、页面完全加载耗时等较长,影响用户的体验。
预期收益:减少打包时间以及打包后的项目体积,提高页面的访问速度,优化用户的体验。
(本文所用的代码都是经过脱敏处理的)
1 准备工作
1.1Vue CLI
在Vue CLI 有这样的描述:
vue-cli-service build
在dist/
目录中生成一个生产就绪包,缩小 JS/CSS/HTML 和自动供应商块分割以获得更好的缓存。块清单被内联到 HTML 中。
--report
并将--report-json
根据您的构建统计生成报告,帮助您分析包中包含的模块的大小。vue-cli-service build –report
项目 build 之后可以看到 dist 文件夹下有一个 report.html,打开这个文件,可以清楚地看到每个模块的分析。
1.2 Lighthouse
我们可以通过在Chrome的Lighthouse中进行性能分析,同时还会提供一些可优化项的tips
2 优化策略
该策略主要围绕webpack
做相关处理,同时也是接入最普遍的性能优化策略
。
性能优化
从结果趋势来看,可分为时间层面和体积层面,减小打包时间和打包体积。
2.1 减小打包时间
2.1.1 配置resolve
配置resolve提高文件的搜索速度,好处是定向指定必须文件路径
。若某些第三方库以常规形式引入可能报错或希望程序自动索引特定类型文件都可通过该方式解决。
alias
映射模块路径,extensions
表明文件后缀,noParse
过滤无依赖文件。通常配置alias
和extensions
就足够。
module.exports = {
configureWebpack: {
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
vue$: 'vue/dist/vue.esm.js',
'@': resolve('src'),
components: resolve('src/components'),
config: resolve('src/config'),
const: resolve('src/const'),
utils: resolve('src/utils'),
pages: resolve('src/pages'),
mixins: resolve('src/mixins'),
},
},
}
}
2.1.2 配置externals
防止将某些 import
的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取 这些扩展依赖。外部扩展(Externals) | webpack 中文文档
module.exports = {
configureWebpack: {
externals: {
'vue-echarts': 'VueECharts',
lodash: {
commonjs: 'lodash',
amd: 'lodash',
root: '_', // indicates global variable
},
},
}
}
然后我们可以在public/index.html中引入相应的script标签。
2.2 减小打包体积
2.2.1 代码分割
分割各个模块代码,提取相同部分代码,好处是减少重复代码的出现频率。
SplitChunksPlugin | webpack 中文文档
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
}
}
}
2.2.2 按需加载
Moment.js 是一个 JavaScript 日期处理类库,用于解析、检验、操作、以及显示日期。常用的日期处理类库Moment.js
居然有669.09KB
,见下图:
它如此巨大的原因是因为有很多语言资源文件,用于转化能成多国时间格式,见GitHub issue。大多数情况下,我们并不需要用到这么多的格式,你拍一销售端目前也无国际化的需求,因此我们需要借助IgnorePlugin。
在项目中的vue.config.js 文件中添加如下代码
const webpack = require('webpack')
module.exports = {
configureWebpack: {
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
}
}
然后我们就可以按需引入组件包
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
之后我们再看他的大小就变为了169.83KB
在.babel.config.js中添加如下代码(可参照ElementUI官方文档来编写代码)
module.exports = {
presets: ['@vue/app', ['@babel/preset-env', { modules: false }]],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk',
},
],
],
};
如果我们只希望引入部分文件,比如 Select、Option、Cascader,我们需要在 main.js 中添加如下代码
import { Select, Option, Cascader } from 'element-ui';
Vue.use(Select);
Vue.use(Option);
Vue.use(Cascader);
项目打包后 element-ui 文件的体积只有367.23KB
在项目中发现并没有按需加载ivew组件库,在分析报告中发现iview竟然有1.68MB
因此项目目前已经写完成了,如果修改为按需加载,需要去统计整个项目中有使用哪些iview组件,较耗时耗力,并且统计过程中极有可能出现错误。
考虑到项目采用 monorepo 的架构,所引用的 JS 库来源于最外层的 packages,如果因为这个系统去做按需加载而修改公共的文件,会导致其他项目中使用的文件也会发生改变,得不偿失。
2.2.3 采用Gzip压缩
采用Gzip压缩效率非常高,一般可以达到50%的压缩率。
const CompressionPlugin = require("compression-webpack-plugin")
const productionGzipExtensions = ['js', 'css'];
module.exports = {
configureWebpack: {
plugins: [
new CompressionPlugin({
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8,
}),
]
}
}
如下图为部分使用Gzip压缩后的文件信息,一个 3.4MB 大小的文件可以被压缩到 886KB !!!
2.2.4 去除.map文件
sourceMap是一个JSON文件,存储着转换后代码和转化前代码的位置对应,以及转换前代码的信息。当执行出错或debugger时,相关工具(比如google的开发者工具)可以根据sourceMap中的信息直接显示原始代码并定位到出错点。
项目开发完成后可去除这些文件,达到减小打包项目体积的目的。
module.exports = {
productionSourceMap: false,
}
去除.map文件后,项目大小变为了11.6MB,明显减少了23.1MB
并且打包的时间也会减少,减少了一半。
2.2.5 Tree-Shaking
通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。
项目中有时候引用了一些资源,但是后面并没有使用,这些没使用的资源会占用打包的体积。可以使用Tree-Shaking来移除未引用的代码。
const webpackDeepScopePlugin = require('webpack-deep-scope-plugin').default
module.exports = {
plugins: [
new webpackDeepScopePlugin()
]
}
2.2.6 删除无用的CSS
在我们的项目的CSS文件中可能会有大量的样式未被使用,我们可以使用 PurgeCSS 删除这些样式,减小文件体积。
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const glob = require('glob')
const PATHS = {
src: path.join(__dirname, 'src')
}
module.exports = {
plugins: [
new PurgeCSSPlugin({
paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
}),
]
}
通过对比,可以发现 CSS 文件的体积至少减少了50%
2.2.7 UglifyJsPlugin
UglifyJS Webpack Plugin
插件用来缩小(压缩优化)js文件
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
plugins: [
new UglifyJsPlugin({ // 压缩js
uglifyOptions: {
mangle: true,
warnings: false,
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
},
output: {
comments: false,// 去除注释
},
},
}),
]
}
2.2.8 optimize-css-assets-webpack-plugin
它将在 Webpack 构建期间搜索 CSS 资源并优化最小化 CSS
optimize-css-assets-webpack-plugin
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
plugins: [
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorOptions: { safe: true, discardComments: { removeAll: true } },
canPrint: true,
}),
]
}
在使用如上两个插件对JS以及CSS进行优化压缩后,项目体积减小
3 优化结果
3.1 打包体积前后对比
打包前 | 打包后 |
---|---|
34.7MB | 11.7MB |
3.2 统计资源前后对比
操作 | 打包前 | 打包后 |
---|---|---|
moment按需加载 | 669.09KB | 169.83KB |
Gzip压缩 | 达到50%的压缩率 | |
去除.map文件 | 34.7MB | 11.6MB |
Tree-Shaking | ||
删除无用CSS | CSS 文件体积至少减少了50% | |
压缩JS和CSS |
3.3 打包时间减少
3.4 Slardar对比
Slardar平台是字节跳动移动基础技术团队搭建的性能和体验保障的端监控平台,包含了App端、组件、browser、网络、Hybrid、Electron等端到端的监控能力。
可以明显的看到项目优化后性能的提升,主要体现在页面的FCP、Load、FMP、FP的时间减少。
4 其他优化策略
4.1 减少网络请求
4.2 页面渲染方面
4.3 懒加载
- 本文作者: étoile
- 版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!