最近重新整理 慕课(前端性能优化企业级解决方案 6大角度+大厂视野)。
所以打算从 0 - 1 记录下来如何使用性能优化工具、如何分析前端性能优化,以及主流的前端性能优化方案。
全程采用案例分析的方式,一步一步敲开前端性能分析和优化的大门。
这是 前端性能优化企业级解决方案 的第三篇 构建优化、传输优化、前沿优化解决方案。
构建优化
webpack 优化配置
Tree-shaking
上下文未用到的代码。
基于ES6 的 ESM 模块化。
- 排除
tree-shaking的文件
package.json中配置sideEffects,不希望Tree-shaking 的文件。
"sideEffects": [
"*.css"
]
- 注意:Babel默认配置的影响
module.exports = {
presets: [
[
'@babel/preset-env',
{
modules: false, // 设置为false,保留ES6语法,让Tree-shaking 保持作用
"targets": {
"browsers": [">0.25%"]
},
"useBuiltIns": "usage",
"bugfixes": true
}
],
'@babel/preset-react'
],
plugins: [
'@babel/plugin-proposal-class-properties',
"@babel/plugin-transform-runtime",
]
};
JS 压缩
terser-webpack-plugin。
减少JS文件体积。
作用域提升
未开启作用域提升时?
会把模块打成单独的模块。
启用作用域提升时?
启用了作用域提升,当发生依赖的调用时,会把依赖进行合并。
基于ESM 的模块化,生产模式自动开启作用域提升。
- 案例
/****************** util.js ******************/
export default 'Hello,Webpack';
/**************** index.jsx ********************/
import str from './util';
console.log(str);
未开启作用域提升时,打包。
先把两个模块打成单独的模块。
当
index.jsx依赖到utils.js时,把模块__webpack_require__进来,然后通过__webpack_require__进来的模块,进行调用。
/***************** 没有 scope hoisting, webpack 打包后 *******************/
[
(function (module, __webpack_exports__, __webpack_require__) {
var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1);
console.log(__WEBPACK_IMPORTED_MODULE_0__util_js__["a"]);
}),
(function (module, __webpack_exports__, __webpack_require__) {
__webpack_exports__["a"] = ('Hello,Webpack');
})
]
/************************************/
开启作用域提升时,打包。
把依赖进行
合并,打包到一个函数。
/***************** 有 scope hoisting, webpack 打包后 *******************/
[
(function (module, __webpack_exports__, __webpack_require__) {
var util = ('Hello,Webpack');
console.log(util);
})
]
/************************************/
Babel7 优化配置
polyfill
在需要的地方引入 polyfill(兼容旧浏览器)。
"useBuiltIns": "usage",
辅助函数按需引入
辅助函数按需引入(对辅助函数的复用)。
每当我们定义一个新的类的时候, babel 转码 都会生成一个辅助函数。
但是这个辅助函数是无差异的,可以复用。
所以配置辅助函数的按需引入,实际上是对辅助函数的复用。
babel.config.js
plugins: [
"@babel/plugin-transform-runtime",
]
目标浏览器
设置目标浏览器按需转换代码。
babel.config.js
"targets": {
"browsers": [">0.25%"]
},
webpack 的依赖优化
打包本身的提速。
noParse
不解析,提高构建速度。
直接通知webpack 忽略较大的库。
被忽略的库不能有import,require,define 等的引入方式。
module: {
noParse: /lodash/
}
DllPlugin【Dev环境】
开发环境中使用。
经常使用的重复的库,可以提取出来变成引入的方式。
不用每次都对这些库进行重新构建,可以大大加快构建的过程。
避免打包时对不变的库进行重复构建。
案例:把 react、react-dom 提取成DllPlugin
webpack.dll.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
react: ['react', 'react-dom']
},
output: {
filename: '[name].dll.js',
path: path.resolve(__dirname, 'dll'),
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, 'dll/[name].manifest.json')
})
]
}
- 执行
webpack --config webpack.dll.config.js
生成 动态链接文件
dll/react.dll.js和描述文件dll/react.manifest.json。
webpack.config.js中引用动态链接库
plugins: [
/* 动态链接库引用 */
new DllReferencePlugin({
manifest: require(`${__dirname}/dll/react.manifest.json`)
})
]
代码拆分【按需引入】
-
把单个bundle文件拆分成
若干小bundles/chunks。 -
缩短
首屏加载时间。 -
splitChunks 提取
共有代码,拆分业务代码与第三方库。
optimization: {
splitChunks: {
// 分组
cacheGroups: {
// 第三方库
vendor: {
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
minSize: 0,
minChunks: 1,
priority: 10,
chunks: 'initial' // initial:同步加载
},
// 公共代码
common: {
name: 'common',
test: /[\\/]src[\\/]/,
chunks: 'all', // all:静态引入和动态引入,async:异步加载(动态引入)
minSize: 0,
minChunks: 2
}
}
}
},
资源压缩
Terser 压缩JS
mini-css-extract-plugin CSS提取。 optimize-css-assets-plugin CSS压缩。
HtmlWebpackPlugin-minify 压缩HTML。
资源持久化缓存
每个打包的资源文件有唯一的hash值。
修改后只有受影响的文件hash 变化。
充分利用浏览器缓存。
- filename
没有进行按需加载的文件,的命名规则。
文件名 + 整个应用的hash 值。
- chunkFilename
进行按需加载的文件,被拆出来的包的命名规则。
监测与分析
Stats 分析与可视化图
webpack Chart
在线的分析工具。
source-map-explorer
基于
source-map的分析工具。
- 安装
$ npm i source-map-explorer
- webpack 开启 source-map
devtool: 'hidden-source-map'
- 执行
$ source-map-explorer 'build/*.js'
webpack-bundle-analyzer 体积分析
speed-measure-webpack-plugin 速度分析
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
module.exports = smp.wrap({
mode: 'production',
devtool: 'source-map',
devServer: {
port: 3000,
historyApiFallback: true
},
...
})
传输优化
GZIP
传输阶段压缩 的技术,对传输资源进行体积压缩,可高达90%。
nginx配置gzip压缩
gzip no;
gzip_min_length 1k; # 资源最小多大才进行压缩
gzip_comp_level 6; # 压缩比例,1-9
启用Keep Alive
一个持久的TCP连接,节省了连接创建时间。
Tcp 连接进行复用。
Http1.1 默认是开启的。
keepalive_timeout 65; # 超时时间,65s。 0 代表不启用keepalive。
keepalive_requests 100; # 利用这个tcp 连接,一共可以发起多少个请求。超过就会重新建立一个TCP 连接。
HTTP 缓存
提高重复访问时资源加载的速度。
Service workers
加速重复访问。
离线支持。
需缓存:静态资源全都进行缓存。
不缓存:比较大的图片、视频
- 原理
- 注意
延长了首屏时间,但页面总加载时间减少
兼容性
只能在localhost 或 https 下使用。
HTTP2
http2 优势:
-
二进制传输
-
请求响应多路复用
- 服务端推送
nginx 配置服务端推送。
优化掉TTFB 的时间。
- nginx 配置http2
listen 843 ssl http2;
- 使用场景
较高的请求量。
SSR
-
加速首频加载
-
更好的SEO
- 是否使用
架构 - 大型,动态页面,面向公众用户
搜索引擎排名很重要
客户端渲染 VS 服务端渲染?
前沿优化解决方案
拯救移动端图标SVG
- 从PNG 到
IconFont
多个图标 ---> 一套字体,减少获取是的请求数量和体积。
矢量图形,可伸缩。
直接通过CSS修改样式(颜色,大小等)。
- 拯救移动端图标 -
SVG
- 安装
$ npm install -D @svgr/webpack
webpack.config.js
{
test: /\.svg$/,
use: ['@svgr/webpack']
}
- react 中使用
svg文件,导入后 直接当做组件使用。
- 从IconFont 到
SVG
保持了图片能力,支持多色彩。
独立的矢量图形。
XML语法,搜索引擎SEO和无障碍读屏软件读取。
使用Flexbox 优化布局
- Flexbox 的优势
更高性能的实现方案。
容器有能力决定子元素的大小,顺序,对齐,间隔等。
双向布局。
优化资源的加载顺序
调整网络资源加载的优先级。
-
浏览器默认安排资源加载优先级
-
preload,prefetch调整优先级
preload
提前加载较晚出现,但对
当前页面非常重要的资源。as 加载之后,作为什么类型。
- 注意
preload字体需要设置crossorign: anonymous。
Prefetch
提前加载
后继路由需要的资源,优先级低。
- 动态加载
- webpack 使用
Prefetch,Preload
预渲染页面
SSR的主要问题?
牺牲TTFB来补救 First Paint; 实现复杂
预渲染的作用:
-
大型单页应用的性能瓶颈:
JS下载 + 解析 + 执行 -
Pre-rendering
打包时 提前渲染页面,没有服务端参与。
- React-Snap
$ npm install -D react-snap
package.json配置postbuild
build命令执行完后,会自动的执行postbuild。
"scripts" : {
"postbuild": "react-snap"
}
index.jsx
判断已经预渲染的页面,使用
ReactDOM.hydrate()。
let root = document.getElementById('main');
if(root.hasChildNodes()) {
ReactDOM.hydrate(<App/>, root);
} else {
ReactDOM.render(<App/>, root);
}
- 内联样式,避免明显的FOUC(样式闪动)
Windowing 提高列表性能
什么是Windowing(窗口化)?
- windowing 的作用
加载
大列表、大表单的每一行严重影响性能,Lazy loading 仍然会让DOM变得过大。
windowing只渲染可见得行,渲染和滚动的性能都会提升。
- 使用
react-window
$ npm i react-window
- 案例:配置一个
一维列表List
始终只加载
能看到的内容,随着滚动,会删除以前的元素和预加载即将渲染的元素。
- 案例:配置一个
二维列表Grid
横向、纵向都可以滚动。
使用骨架组件减少布局移动
论占位置的重要性。
Skeleton/Placeholder的作用
- 占位
- 提升用户感知性能
- 使用
react-placeholder解决布局移动的问题
$ npm i react-placeholder -D
-
预置的placeholders
-
自定义placeholder
性能优化面试题
从输入URL到页面加载显示完成都发生了什么?
UI线程接受用户输入,进行处理。
关键词 还是 站点的判断。
Network thread
UI 线程通知网络线程 进行网络请求。
DNS查找:确认 域名对应的IP,和服务器建立 连接。(https 需要建立tls 的安全链接)
Render Process
首屏加载优化
- 用户加载体验的3个关键时刻
- 解答
Javascript 内存管理
- 变量创建时自动分配内存,不使用时
自动释放内存 --GC。
- 什么是垃圾回收?
内存释放的主要问题是如何确定不再需要使用的内存。
所有的GC都是近视实现,只能通过判断变量是否能再次访问到。
局部变量,函数执行完,没有闭包引用,就会被标记回收。
全局变量,直至浏览器卸载页面时释放。