跳到主要内容

性能优化

  • 加快打包构建速度

    • 预构建
    • 持久化缓存
  • 代码拆分、分包,避免重复依赖打包

  • 静态资源优化

  • 减少打包文件的体积

    • 引入 webpack-bundle-analyzer 分析打包后的文件,判断哪些包还可以拆分和优化
  • tree-shaking

  • dll动态链接库:一些第三方包可以配置external,通过CDN或者外部js引入包,而不用打包

    • 如echarts, 可通过自定义构建echarts,减少echarts体积,而不用使用npm来下载,通过外部js文件来引入。

dll动态链接库​

项目中,框架vue、react、lodash等公共库,体积大,构建慢,较稳定,不经常升级版本

  • 只需要一次构建就好,不需要每次都构建,完全没必要,会导致构建速度慢
  • DLLPlugin 是 webpack 官方提供的一个插件,也是用来分离代码的
    • DllPlugin -> 打包出dll文件
  • dll是针对项目的性能优化的
  • 使用 DLLPlugin 在打包的时候生成一个 manifest 文件,通过dll分包, webpack 会根据这个 manifest 文件的信息,分析出哪些模块无需打包

webpack.dll.config.js:

js
module.exports = {
name: 'vendor',
// entry: ['lodash','axios','react','react-dom','mobx'], // 作为公共类库
entry:{
// 将React相关模块放在一个单独的文件里 (动态链接库)
react:['react','react-dom']
},
output: {
path: path.resolve(__dirname, "dist/dll"),
// filename: "vendor.js",
filename:'[name].dll.js' // name对应entry的key
library: "_dll_[name]" // 打包后对外暴露的类库名称
},

plugins: [
new webpack.DllPlugin({
name: '_dll_[name]',
path: path.resolve(__dirname, "[name].manifest.json"), // 使用 DLLPlugin 在打包的时候生成一个 manifest 文件
})
// 告诉webpack使用了哪些动态链接库
new DllReferencePlugin({
manifest:path.resolve(__dirname, "react.manifest.json")
})
],
}

你会发现构建结果的应用代码中不包含 lodash,react,react-dom等公共类库 的代码内容,这一部分代码内容会放在 vendor.js 这个文件中,而你的应用要正常使用的话,需要在 HTML 文件中按顺序引用这两个代码文件,如:

text
<script src="vendor.js"></script>
<script src="react.dll.js"></script>
<script src="main.js"></script>

在构建的时候,我们需要优先使用 webpack.dll.config.js 来打包,如 webpack -c webpack.dll.config.js --mode production,构建后生成公共代码模块的文件 vendor.js 和 manifest.json,然后再进行应用代码的构建

DLLPlugin 构建出来的内容无需每次都重新构建,后续应用代码部分变更时,你不用再执行配置为 webpack.dll.config.js 这一部分的构建,沿用原本的构建结果即可,所以相比 optimization.splitChunks,使用 DLLPlugin 时,构建速度是会有显著提高的

注意:​

  • 要使用dll分包,需要优先使用webpack.dll.config.js 来打包生成对应的manifest.json和vendor.js
  • DLLPlugin 构建出来的内容无需每次都重新构建
  • 当你升级 lodash(即你的公共部分代码的内容变更)时,要重新去执行 webpack.dll.config.js 这一部分的构建,不然沿用的依旧是旧的构建结果

DLLPlugin/DllReferencePlugin​

DLLPlugin :作用就是把第三方库和代码分开,vendor.dll.js文件里包含的就是第三方库的文件。-

  • DLLPlugin: 打包出dll文件

DllReferencePlugin:用于在Webpack配置中引用这些预编译的DLL文件。生成动态链接库

  • DllReferencePlugin: 使用dll文件

注意:webpack.dll.config.js打包生成的两个文件,一个vendor.dll.js,另一个是vendor-manifest.json。前一个是保存的第三方库文件,后一个是库文件的索引。DllReferencePlugin插件读取vendor-manifest.json文件,把库文件映射到相关的依赖上去。

代码分割/代码拆分​

为什么CSS 文件分离出来,而不是直接一起打包在 JS 中?

  • 主要的原因是我们希望更好地利用缓存

1、假设静态资源都打包成一个js文件,加载页面时虽然只需要加载一个 JS 文件,但是我们的代码一旦改变了,用户访问新的页面时就需要重新加载一个新的 JS 文件。有些情况下,我们只是单独修改了样式,这样也要重新加载整个应用的 JS 文件,相当不划算

2、还有一种情况是我们有多个页面,它们都可以共用一部分样式(这是很常见的,CSS Reset、基础组件样式等基本都是跨页面通用),如果每个页面都单独打包一个 JS 文件,那么每次访问页面都会重复加载原本可以共享的那些 CSS 代码

如果分离开来,第二个页面就有了 CSS 文件的缓存,访问速度自然会加快。虽然对第一个页面来说多了一个请求,但是对随后的页面来说,缓存带来的速度提升相对更加可观

  • optimization.splitChunks

    • 公共引用的模块(库)帮单独提出来做下代码分割
    • 自动生成了一个verdor~main.js文件
    js
    optimization: {
    splitChunks: {
    chunks: "all" //所有的 chunks 代码公共的部分分离出来成为一个单独的文件
    }
    }

我们需要在 HTML 中引用两个构建出来的 JS 文件,并且 commons.js 需要在入口代码之前。下面是个简单的例子:

text
<script src="commons.js" charset="utf-8"></script>
<script src="entry.bundle.js" charset="utf-8"></script>

使用了 html-webpack-plugin,那么对应需要的 JS 文件都会在 HTML 文件中正确引用

将公共使用的第三方类库配置为公共的部分

不应该由 webpack 自己去判断处理。因为公共的第三方类库通常升级频率相对低一些,这样可以避免因公共 chunk 的频繁变更而导致缓存失效

js
module.exports = {
entry: {
vendor: ["react", "lodash", "vue", ...], // 指定公共使用的第三方类库
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "initial",
test: "vendor",
name: "vendor", // 使用 vendor 入口作为公共部分
enforce: true,
},
},
},
},
// ... 其他配置
}

// 或者
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /react|angluar|lodash/, // 直接使用 test 来做路径匹配
chunks: "initial",
name: "vendor",
enforce: true,
},
},
},
},
}
// 或者
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: "initial",
test: path.resolve(__dirname, "node_modules") // 路径在 node_modules 目录下的都作为公共部分
name: "vendor", // 使用 vendor 入口作为公共部分
enforce: true,
},
},
},
},
}

上述第一种做法是显示指定哪些类库作为公共部分,第二种做法实现的功能差不多,只是利用了 test 来做模块路径的匹配,第三种做法是把所有在 node_modules 下的模块,即作为依赖安装的,都作为公共部分。你可以针对项目情况,选择最合适的做法。

理解chunks​

chunks 用以告诉 splitChunks 的作用对象,其可选值有 async、 initial 和 all。默认值是 async,也就是默认只选取异步加载的chunk进行代码拆分

  • chunks 值为 all 时,会比较强大,一般设置为all即可,处理范围包括了初始chunk和异步chunk两种场景

CSS分离​

  • mini-css-extract-plugin插件:将CSS分离成单独的文件

总结​

  • MiniCssExtractPlugin插件来将CSS分离成单独的文件,而splitChunks选项用于提取公共模块
  • splitChunks 移除了重复的依赖模块, 减轻了大小。 【如lodash】

tree-shaking​

webpack2.x开始⽀支持 tree shaking概念,顾名思义,"摇树

  • 用于描述移除 JavaScript 上下文中的未引用代码
  • 摇树 可以理解为 把没有用到的代码给移除下去,消除无用代码,不进行打包

sideEffects​

  • webpack 4之后可支持通过 package.json 的 "sideEffects" 属性作为标记,可以指定项目中的副作用
json
"sideEffects": false

如果所有代码都不包含副作用,可以直接设置该属性标记为 false,来告知 webpack 它可以安全地删除未用到的export

  • 可指定项目中文件模块中副作用,比如像全局的less文件导入,会影响到全局作用域,不应该被tree-shaking摇掉
js
"sideEffects": [
"*.less"
],

esbuild-loader提升构建速度​

  • esbuild-loader 是一个 Webpack 的 loader,可以使用 esbuild 作为 JavaScript 构建工具来加速构建速度
  • esbuild 是一个高性能的 JavaScript 构建工具,可以快速地处理 JavaScript 和 TypeScript 代码
  • 可以使用esbuild-loader替换babel-loader、ts-loader

可处理tsx/typescript/jsx/javascript 代码

esbuild-loader 可以自动检测 tsconfig.json文件

js
module.exports = {
module: {
rules:[
test: /\.js$/, // /\.tsx?$/、/\.js$/
loader: 'esbuild-loader',
options: {
loader: 'jsx', // tsx、jsx
target: 'es2015'
}
]
}
}

持久化缓存提高构建速度​

持续化构建过程来说,第一次构建是一次全量构建,然后它会将相关产物序列化缓存在磁盘中(serialize)。后续构建具体流程可以依赖于上一次的缓存进行:读取磁盘缓存 -> 校验模块 -> 解封模块内容

持久缓存会将缓存存储到磁盘

小图片 base64编码​

针对小图片/小图标的场景,比如小于5kb的可以进行base64格式产出

  • 减少http请求,提高页面加载速度

Base64编码限制

  • Base64编码会将图片转换为文本格式,增加了文件体积,对于大图片不适合使用。
  • Base64编码后的文本会比原始图片文件稍微大一些,因为Base64编码使用了一些额外的字符表示二进制数据。
  • Base64编码后的文本文件无法进行图片优化压缩,如gzip压缩,因为文本文件已经是经过编码的。
js
rules: [
{
test: /\.(png|jpg|gif)$/i, // 匹配图片文件
use: [
{
loader: 'url-loader',
options: {
limit: 5 * 1024, // 小于 5KB 的图片将转为 base64 编码
name: '[name].[hash:5].[ext]', // 输出文件的命名规则
outputPath: 'images', // 输出路径
},
},
],
},
...
]

可以使用url-loader和file-loader来将小图片转换为Base64编码。这两个加载器都是Webpack的一部分,url-loader是file-loader的上层封装

bundle加hash​

根据文件的内容生成hash

  • 文件内容变了才会生成hash,如果文件内容没有发生变化,webpack生成hash值是一样,根据浏览器的缓存机制,那么在加载资源会从缓存中获取,就不用重新加载一遍新资源
js
output:{
filename:'[name].[contentHash:8].js'
}

需要注意的是,对于不同的文件类型,Webpack提供了多种hash选项,如[hash]、[chunkhash]、[contenthash]等。其中,[hash]适用于整个构建过程,而[chunkhash]适用于每个独立的代码块,[contenthash]适用于每个文件的内容。使用[contenthash]可以确保只有文件内容变化时,hash值才会发生变化,从而更好地利用缓存。

利用了浏览器什么缓存机制?

利用了浏览器的强缓存和协商缓存机制。

  • 当浏览器发起资源请求时,首先检查强缓存是否命中。如果强缓存命中,浏览器直接从缓存中获取资源,不发送请求。如果强缓存未命中,则发送请求,服务器会根据协商缓存机制判断资源是否有更新。如果资源没有更新,则服务器返回304 Not Modified状态码,浏览器接收到这个响应后,从缓存中获取资源。如果资源有更新,则服务器返回新的资源内容

强缓存:响应头中设置Cache-Control和Expires

协商缓存 响应头:Last-Modified和ETag

使用CDN​

  • 配置CDN域名前缀,通过pubic
  • 需要将打包后的静态文件上次到CDN中
js
output:{
publicPath:'https//cdn.com' // 将静态文件url的前缀替换成cdn域名
}
  • 也可以单独设置图片的cdn地址

如果设置了output的cdn域名,里面图片就不需要另外设置cdn地址了

js
use: [
{
loader: 'url-loader',
options: {
limit: 5 * 1024, // 小于 5KB 的图片将转为 base64 编码
name: '[name].[hash:5].[ext]', // 输出文件的命名规则
outputPath: 'images', // 输出路径
publicPath:'https//cdn.com' // 设置图片的cdn地址
},
},
],

Scope Hosting 【webpack已内置】​

Scope Hosting是Webpack 4及以上版本的默认优化特性,不需要额外的配置来开启

可以减少生成的代码的模块和创建新的函数作用域,从而减少了在浏览器中运行时的内存消耗和性能开销

在传统的Webpack打包中,每个模块都会被包裹在一个函数作用域中,这意味着每个模块都有自己的作用域,并且模块之间的变量不会相互影响。这样的好处是模块之间的变量不会相互污染,但同时也带来了一些性能开销,因为需要在运行时维护多个函数作用域。

  • Scope Hosting的目标是优化这一过程,将具有相同作用域的模块合并在一起,避免创建额外的函数作用域, 相同作用域内的变量可以共享,减少了不必要的闭包和内存占用。
  • Scope Hosting的实现依赖于ES6模块系统(使用import和export语法)

原理

  • 当Webpack检测到某个模块没有被其他模块引用或被引用的次数为1时,它将会将这些模块合并在一起,并在同一个函数作用域内运行它们。

moduleA.js

text
jsCopy code
export const x = 10;

moduleB.js

text
jsCopy codeimport { x } from './moduleA';
console.log(x);

在传统的Webpack打包中,moduleA和moduleB都会被包裹在自己的函数作用域中。但是,如果启用了Scope Hosting,Webpack会将它们合并在同一个作用域中,从而减少函数的创建和内存占用。

external​

1、通过CDN或者外部js引入包,而不用打包,提高打包优化速度

2、通过CDN引入包,这样可以减少bundle打包后的体积,也可以利用浏览器缓存,对于不经常变化的文件进行缓存,从缓存获取资源

  • 指出应将哪些模块视为外部模块,外部模块不会被打包
    • 防止将某些 import 的包(package)打包到 bundle 中
  • external 接受一个模块名称的数组或一个接受模块名称的函数,如果它被视为外部引用(externals)则返回 true

用法:

  • 在index.html引入的脚本,如从CDN引入,或者已经打包好的js,这不需要重新打包
  • 比如在inxex.html引入了lodash,external配置了lodash外部模块
    • 则lodash不会被打包,可以在组件通过import引入lodash模块

按需加载、懒加载配置​

路由懒加载

  • import()语法实现动态导入
  • vue、react异步组件
  • 结合vue-router 、react-router异步加载路由
    • 是基于import语法实现

合理配置source-map​

合理配置source-map, 在定位代码错误时使用,而不影响打包后的包体积大小

  • 在webpack的devtool配置,有几个选项可开启source-map,通过配置