【慕课】前端性能优化企业级解决方案(下)

244 阅读4分钟

最近重新整理 慕课(前端性能优化企业级解决方案 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 的模块化,生产模式自动开启作用域提升。

  1. 案例
/****************** 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 转码 都会生成一个辅助函数。

image.png

但是这个辅助函数是无差异的,可以复用。

所以配置辅助函数的按需引入,实际上是对辅助函数的复用。

  • babel.config.js
plugins: [
  "@babel/plugin-transform-runtime",
]

目标浏览器

设置目标浏览器按需转换代码。

Browserslist

  • babel.config.js
                "targets": {
                    "browsers": [">0.25%"]
                },

webpack 的依赖优化

打包本身的提速。

noParse

不解析,提高构建速度。

直接通知webpack 忽略较大的库。

被忽略的库不能有import,require,define 等的引入方式。

module: {
  noParse: /lodash/
} 

DllPlugin【Dev环境】

开发环境中使用。

经常使用的重复的库,可以提取出来变成引入的方式。

不用每次都对这些库进行重新构建,可以大大加快构建的过程。

避免打包时对不变的库进行重复构建

案例:把 react、react-dom 提取成DllPlugin

  1. 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')
    })
  ]
}
  1. 执行 webpack --config webpack.dll.config.js

生成 动态链接文件dll/react.dll.js 和描述文件dll/react.manifest.json

image.png

  1. webpack.config.js引用动态链接库
  plugins: [
    /* 动态链接库引用 */
    new DllReferencePlugin({
      manifest: require(`${__dirname}/dll/react.manifest.json`)
    })
  ]

代码拆分【按需引入】

  1. 把单个bundle文件拆分成若干小bundles/chunks

  2. 缩短首屏加载时间。

  3. 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

进行按需加载的文件,被拆出来的包的命名规则。

image.png

监测与分析

Stats 分析与可视化图

  1. webpack Chart

在线的分析工具。

Webpack Chart (alexkuz.github.io)

image.png

  1. source-map-explorer

基于source-map 的分析工具。

  • 安装
$ npm i source-map-explorer
  • webpack 开启 source-map
devtool: 'hidden-source-map'
  • 执行
$ source-map-explorer 'build/*.js'

image.png

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
  },
  ...
})

image.png

传输优化

GZIP

传输阶段压缩 的技术,对传输资源进行体积压缩,可高达90%。

  1. nginx 配置gzip 压缩
gzip no;

gzip_min_length 1k; # 资源最小多大才进行压缩

gzip_comp_level 6; # 压缩比例,1-9

image.png

启用Keep Alive

一个持久的TCP连接,节省了连接创建时间。

Tcp 连接进行复用。

Http1.1 默认是开启的。

keepalive_timeout 65; # 超时时间,65s。  0 代表不启用keepalive。

keepalive_requests 100; # 利用这个tcp 连接,一共可以发起多少个请求。超过就会重新建立一个TCP 连接。

HTTP 缓存

提高重复访问时资源加载的速度。

image.png

Service workers

加速重复访问。

离线支持

需缓存:静态资源全都进行缓存。

不缓存:比较大的图片、视频

  • 原理

image.png

  • 注意

延长了首屏时间,但页面总加载时间减少

兼容性

只能在localhost 或 https 下使用。

HTTP2

http2 优势:

  1. 二进制传输

  2. 请求响应多路复用

image.png

  1. 服务端推送

nginx 配置服务端推送。

优化掉TTFB 的时间。

image.png

  1. nginx 配置http2
listen 843 ssl http2;
  1. 使用场景

较高的请求量。

SSR

  1. 加速首频加载

  2. 更好的SEO

  • 是否使用

架构 - 大型,动态页面,面向公众用户

搜索引擎排名很重要

客户端渲染 VS 服务端渲染?

image.png

前沿优化解决方案

拯救移动端图标SVG

  1. 从PNG 到 IconFont

多个图标 ---> 一套字体,减少获取是的请求数量和体积。

矢量图形,可伸缩。

直接通过CSS修改样式(颜色,大小等)。

  1. 拯救移动端图标 - SVG
  • 安装
$ npm install -D @svgr/webpack
  • webpack.config.js
{
  test: /\.svg$/,
  use: ['@svgr/webpack']
}
  • react 中使用

svg 文件,导入后 直接当做组件使用。

  1. 从IconFont 到SVG

保持了图片能力,支持多色彩。

独立的矢量图形。

XML语法,搜索引擎SEO和无障碍读屏软件读取。

使用Flexbox 优化布局

  1. Flexbox 的优势

更高性能的实现方案。

容器有能力决定子元素的大小,顺序,对齐,间隔等。

双向布局。

优化资源的加载顺序

调整网络资源加载的优先级。

  1. 浏览器默认安排资源加载优先级

  2. preloadprefetch 调整优先级

  • preload

提前加载较晚出现,但对当前页面非常重要的资源。

as 加载之后,作为什么类型。

image.png image.png

  • 注意

preload 字体需要设置 crossorign: anonymous

image.png

  • Prefetch

提前加载后继路由需要的资源,优先级低。

  1. 动态加载

image.png

  1. webpack 使用PrefetchPreload

预渲染页面

SSR的主要问题?

牺牲TTFB来补救 First Paint; 实现复杂

预渲染的作用:

  1. 大型单页应用的性能瓶颈:JS下载 + 解析 + 执行

  2. 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(样式闪动)

image.png

Windowing 提高列表性能

什么是Windowing(窗口化)?

image.png

  1. windowing 的作用

加载大列表、大表单的每一行严重影响性能,Lazy loading 仍然会让DOM变得过大。

windowing 只渲染可见得行,渲染和滚动的性能都会提升。

  • 使用react-window

bvaughn/react-window

$ npm i react-window
  • 案例:配置一个一维列表 List

始终只加载能看到的内容,随着滚动,会删除以前的元素和预加载即将渲染的元素。

SrfQLwqa - 码上掘金 (juejin.cn)

image.png

  • 案例:配置一个二维列表 Grid

横向、纵向都可以滚动。

DVnAdKuW - 码上掘金 (juejin.cn)

使用骨架组件减少布局移动

论占位置的重要性。

  1. Skeleton/Placeholder 的作用
  • 占位
  • 提升用户感知性能
  1. 使用react-placeholder 解决布局移动的问题
$ npm i react-placeholder -D 
  • 预置的placeholders

  • 自定义placeholder

性能优化面试题

从输入URL到页面加载显示完成都发生了什么?

  1. UI线程接受用户输入,进行处理。

关键词 还是 站点的判断。

image.png

  1. Network thread

UI 线程通知网络线程 进行网络请求。

DNS查找:确认 域名对应的IP,和服务器建立 连接。(https 需要建立tls 的安全链接)

image.png

  1. Render Process

image.png

image.png

首屏加载优化

  1. 用户加载体验的3个关键时刻

image.png

  • 解答

image.png

image.png

Javascript 内存管理

  1. 变量创建时自动分配内存,不使用时自动 释放内存 -- GC

image.png

  • 什么是垃圾回收?

内存释放的主要问题是如何确定不再需要使用的内存。

所有的GC都是近视实现,只能通过判断变量是否能再次访问到。

局部变量,函数执行完,没有闭包引用,就会被标记回收。

全局变量,直至浏览器卸载页面时释放。

image.png