# DLL
# 认识 DLL 库
- DLL 全称是动态链接库(Dynamic Link Library),是为了软件在 Windows 中实现共享函数库的一种实现方式
- webpack 中也有内置 DLL 的功能,它指的是我们可以将可以共享,并且不经常改变的代码,抽取成一个共享的库
- 这个库在之后编译的过程中,会被引入到其它的项目的代码中
DLL 库的使用分为两步:
- 第一步:打包一个 DLL 库
- 第二步:项目中引入 DLL 库
注意:在升级到 webpack4 之后,React 和 Vue 脚手架都移除了 DLL 库,尤大认为 webpack4 已经能够提供很好的性能,不需要去专门维护一个 DLL 库了
# 打包一个 DLL 库
创建一个新的项目,例如:我们打包一个带有react
和react-dom
的 DLL
webpack 帮助我们内置了一个 DllPlugin 可以帮助我们打包一个 DLL 的库文件
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: {
react: ["react", "react-dom"],
},
output: {
path: path.resolve(__dirname, "./dll"),
filename: "dll_[name].js",
library: "dll_[name]",
},
plugins: [
new webpack.DllPlugin({
name: "dll_[name]",
path: path.resolve(__dirname, "./dll/[name].manifest.json"), // 需要生成一个manifest文件,用来对应查找dll
}),
],
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
yarn build 以后就会生成一个 dll 文件夹,我们将打包后的 dll 文件运用到其他项目
# 使用打包的 DLL 库
如果我们在代码中使用到了 react 和 react-dom,我们有配置 splitChunks 的情况下,他们会进行分包,打包到一个独立的 chunk 中,但是我们有了 dll_react,就不再需要单独打包它们,可以直接取引用 dll_react
具体步骤如下:
- 通过 DllReferencePlugin 插件告知要使用的 DLL 库
- 通过 AddAssetHtmlPlugin 插件,将我们打包的 DLL 库引入到 Html 模块中
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
context: resolveApp("./"), // 告知在哪里查找dll
manifest: resolveApp("./dll/react.manifest.json"), // 告知manifest的位置
}),
new AddAssetHtmlWebpackPlugin({
// 该插件自动在html中注入dll_react引用,并在build中添加dll_react.js文件
outputPath: "./auto",
filepath: resolveApp("./dll/dll_react.js"),
}),
],
};
2
3
4
5
6
7
8
9
10
11
12
13
# Terser
# Terser 介绍和安装
# 介绍
Terser
是一个 JavaScript 的解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)的工具集- 早期我们会使用
uglify-js
来压缩、丑化我们的 JS 代码,但是目前已经不再维护,并且不支持 ES6+的语法 Terser
是从uglify-es
fork 过来的,并且保留它原来的大部分 API 以及适配uglify-es
和uglify-js@3
等- 也就是说,
Terser
可以帮助我们压缩、丑化我们的代码,让我们的 bundle 变得更小
# 安装
因为Terser
是一个独立的工具,所以它可以单独安装
# 全局安装
yarn global add terser
# npm install terser -g
# 局部安装
yarn add terser
# npm install terser
2
3
4
5
6
7
# 命令行使用 Terser
terser [input files] [options]
# 举例
# terser 输入文件 -o 输出文件名 -c(compress,后面可以配置参数,如:arrows=true,arguments=true) -m(mangle,同-c)
# 不传则使用默认配置,如果不配-c -m则只会压缩空格和回车
terser js/file1.js -o foo.min.js -c -m
terser js/file1.js -o foo.min.js -c arrows=true,arguments=true -m keep_classnames=true
2
3
4
5
6
关于 Terser 的详细配置可以查看文档compress option (opens new window)和mangle option (opens new window)
# Compress 和 Mangle 的部分 options
# Compress option
- arrows:class 或者 object 中的函数,转换成箭头函数
- arguments:将函数中使用 arguments[index]装成对应的形参名称
- dead_code:移除不可达的代码(tree shaking)
- 其他属性可以查看文档
# Mangle option
- toplevel:默认值是 false,顶层作用域中的变量名称,进行丑化(转换)
- keep_classnames:默认值是 false,是否保持依赖的类名称
- keep_fnames:默认值是 false,是否保持原来的函数名称
- 其他属性可以查看文档
npx terser ./src/abc.js -o abc.min.js -c arrows,arguments=true,dead_code -m toplevel=true,keep_classnames=true,keep_fnames=true
# Terser 在 webpack 中配置
真实开发中,我们不需要手动的通过 terser 来处理我们的代码,我们可以直接通过 webpack 处理
- 在 webpack 中有一个 minimizer 属性,在 production 模式下,默认就是使用
TerserPlugin
来处理我们的代码(当然还有其他的工具) - 如果我们对默认的配置不满意,也可以自己来创建 TerserPlugin 的实例,并且覆盖相关的配置
具体操作如下
- 首先,我们需要打开 minimize,让其对我们的代码进行压缩(默认 production 模式下已经打开了)
- 其次,我们可以在 minimizer 创建一个 TerserPlugin:
- extractComments:默认值为 true,表示会将注释抽取到一个单独的文件中
- 在开发中,我们不希望保留这个注释时,可以设置为 false
- parallel:使用多进程并发运行提高构建速度,默认值是 true,并发运行的默认数量:os.cpus().length - 1(就是,cpus 的线程数-1)
- 我们也可以设置自己的格式,但是使用默认值即可
- terserOptions:设置我们的 terser 相关的配置
- compress:设置压缩相关的选项
- mangle:设置丑化相关的详细,可以直接设置为 true
- toplevel:底层变量是否进行转换
- keep_classnames:保留类的名称
- keep_fnames:保留函数的名称
- 其他相关参数可以查看上面的文档
- extractComments:默认值为 true,表示会将注释抽取到一个单独的文件中
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
extractComments: false,
terserOptions: {
compress: {
arguments: false,
dead_code: true,
},
mangle: true,
toplevel: true,
keep_classnames: true,
keep_fnames: true,
},
}),
],
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# CSS 的压缩
另一个代码的压缩是 css
- css 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等
- css 的压缩我们可以使用另外一个插件:
css-minimizer-webpack-plugin
css-minimizer-webpack-plugin
是使用 cssnano 工具来优化、压缩 CSS(也可以单独使用)
安装css-minimizer-webpack-plugin
yarn add css-minimizer-webpack-plugin -D
# npm install css-minimizer-webpack-plugin -D
2
在 optimization.minimizer 中配置
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({}),
new CssMinimizerPlugin({
parallel: true,
}),
],
},
};
2
3
4
5
6
7
8
9
10
# Scope Hoisting
Scope Hoisting 从 webpack3 开始增加的一个新功能,功能是对作用域进行提升,并且让 webpack 打包后的代码更小、运行更快
默认情况下 webpack 打包会有很多的函数作用域,包括一些(比如最外层的)IIFE
- 无论是从最开始的代码运行,还是加载一个模块,都需要执行一系列的函数
- Scope Hoisting 可以将函数合并到一个模块中来运行
使用 Scope Hoisting 非常的简单,webpack 已经内置了对应的模块
- 在 production 模式下,默认这个模块就会启用
- 在 development 模式下,我们需要自己来打开该模块
module.exports = {
plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
};
2
3
# Tree Shaking
# 认识 Tree Shaking
- Tree Shaking 是一个术语,在计算机中表示消除死代码(dead_code)
- 最早的想法起源于 LISP,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求我们再进行函数式编程时,尽量使用纯函数的原因之一)
- 后来 Tree Shaking 也被应用于其他的语言,比如 JavaScript
javaScript 的 Tree Shaking
- 对 JavaScript 进行 Tree Shanking 是源自打包工具 rollup
- 这是因为 Tree shaking 依赖于 ES Module 的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)
- webpack2 正式内置支持了 ES2015 模块,和检测未使用模块的能力
- 在 webpack4 正式扩展了这个能力,并且通过 package.json 的
sideEffects
属性作为标记,告知 webpack 在编译时,哪里文件可以安全的删除掉 - webpack5 中,也提供了对部分 CommonJS 的 tree shaking 的支持,点击查看 (opens new window)
# webpack 实现 Tree Shaking
事实上 webpack 实现 Tree Shaking 采用了两种不同的方案
- usedExports:通过标记某些函数是否被使用,之后通过 Terser 来进行优化的
- sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用
# usedExports
- 将 mode 设置为 development 模式:
- 为了可以看到 usedExports 带来的效果,我们暂时将环境设置为 development 模式
- 因为在 production 模式下,webpack 默认的一些优化会带来很大的影响
- 设置
usedExports
为 true 和 false 对比打包后的代码- 在 usedExports 设置为 true 时,会有一段注释:
unused harmony export mul
- 这段注释会告知 Terser,让其在优化时,可以删除掉这段代码
- 我们会发现,在 minimize 设置为 true 时
- sedExports 设置为 false 时,没有用到的函数没有被移除掉
- 而 sedExports 设置为 true 时,没有用到的函数有被移除掉
- 在 usedExports 设置为 true 时,会有一段注释:
所以usedExports
实现 Tree shaking 是结合 Terser 来完成的,在 production 模式下,usedExports 是默认为 true 的,不需要我们去设置
# sideEffects
- sideEffects 用于告知 webpack compiler 哪些模块是有副作用的
- 副作用的意思是这里的代码有执行一些特殊的任务,不能仅仅通过 export 来判断这段代码的意义
- 在 package.json 中设置 sideEffects 的值
- 如果我们将 sideEffects 设置为 false,就是告知 webpack 可以安全的删除未用到的 exports
- 例如我们有一个 format.js、style.css 文件
- 该文件在导入时没有使用任何的变量来接受,那么打包后的文件,不会保留 format.js、style.css 相关的任何代码
- 如果有一些我们希望保留,可以设置为数组
- 如果我们将 sideEffects 设置为 false,就是告知 webpack 可以安全的删除未用到的 exports
// package.json
{
"sideEffects": ["./src/util/format.js", "*.css"]
}
2
3
4
一般情况下我们的模块都是没有副作用的,所以我们可以只对 css 文件进行处理
// package.json
{
"sideEffects": false
}
2
3
4
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css/.
use: [],
sideEffects: true // 设置这个属性就不会删除css文件
}
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
建议
如何在项目中对 JavaScript 的代码进行 TreeShaking 呢(生成环境)?
- 在 optimization 中配置 usedExports 为 true,来帮助 Terser 进行优化;
- 在 package.json 中配置 sideEffects,直接对模块进行优化;
# CSS 实现 Tree Shaking
CSS 的 Tree Shaking 需要借助一些其他的插件
- 早期的时候,我们会使用 PurifyCss 插件来完成 Css 的 Tree Shaking,但是目前改库已经不再维护了(最新更新也是 4 年前了)
- 目前我们可以使用
PurgeCSS
来完成 Css 的 Tree Shaking,也就是帮助我们删除未使用的 CSS 工具
安装 PurgeCss 的 webpack 插件
yarn add purgecss-webpack-plugin -D
# npm install purgecss-webpack-plugin -D
2
配置 PurgeCss,一般在生产环境
- paths: 表示要检测哪些目录下的内容需要被分析,这里我们可以使用 glob
- 默认情况下,Purgecss 会将我们的 html 标签的样式移除掉,如果我们希望保留,可以添加一个 safelist 的属性
module.exports = {
// ...
plugins: [
new PurgecssPlugin({
paths: glob.sync(`${resolveAp*p("./src")}/**/*`, { nodir: true })
safelist: function() {
return {
standard: ["body", "html"]
}
}
})
]
}
2
3
4
5
6
7
8
9
10
11
12
13
- purgecss 也可以对 less 文件进行处理(所以它是对打包后的 css 进行 tree shaking 操作)
# 其他
# HTTP 压缩
HTTP 压缩是一种内置在服务器和客户端之间的,以改进传输速度和带宽利用率的方式
HTTP 压缩的流程是
- 第一步:HTTP 数据在服务器发送钱就已经被压缩了(可以在 webpack 中完成)
- 第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式
GET /encrypted-area HTTP/1.1
Host: www.exaple.com
Accpet-Encoding: gip, deflate
2
3
- 第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器
HTTP/1.1 200 OK
Date: Tue, 10 Aug 2021 06:03:16 GMT
Last-Modified: wed, 08 Jan 2021 23:11:55 GMT
Accept-Ranges: bytes
Content-Length: 438
Connect-Type: text/html; charset=UTF-8
Content-Encoding: gzip
2
3
4
5
6
7
目前的压缩合适非常的多
- compress:UNIX 的"compress"程序的方法(历史性原因,不推荐大多数应用使用,应该使用 gip 或者 deflate)
- deflate:基于 deflate 算法(定义与 RFC 1951)的压缩,使用 zlib 数据格式封装
- gzip:GUN zip 格式(定义与 RFC 1952),是目前使用比较广泛的压缩算法
- br:一种新的开源压缩算法,专为 HTTP 内容的编码而设计
Webpack 实现对文件的压缩
webpack 中相当于是实现了 HTTP 压缩的第一步操作,我们可以使用 CompressionPlugin
第一步,安装 CompressionWebpackPlugin
yarn add compression-webpack-plugin -D
# npm install compression-webpack-plugin -D
2
- 使用 CompressionWebpackPlugin 即可
new CompressionPlugin()({
test: /\.(css|js)$/, // 匹配哪些文件需要压缩
// threshold: 500, // 设置文件从多大开始压缩
minRatio: 0.7, // 至少的压缩比例
algorithm: "gzip", // 采用的压缩算法
});
2
3
4
5
6
# HTML 文件中代码的压缩
我们之前使用了 HtmlWebpackPlugin 插件来生成 HTML 的模板,事实上它还有一些其他的配置
- inject:设置打包的资源插入的位置
- true、false:表示插入或不插入
- body、head:表示插入到 body 或者 head 中
- cache:设置为 true,只有当文件改变时,才会生成新的文件(默认值是 true)
- minify:默认会使用一个插件
html-minifier-terser
new HtmlWebpackPlugin({
inject: "body",
cache: true,
minify: isProduction
? {
removeComments: true, // 移除注释
collapseWhitespace: false, // 折叠空格
removeRedundantAttributes: false, // 移除多余的属性 例如type=text
useShortDoctype: true, // 比如我们的模板是html4,那么会转换成html5的文档
removeStyleLinkTypeAttributes: true, // 比如link中的type="text/css"
keepClosingSlash: true, // 是否保存单元素的尾部
minifyCSS: false, // 是否压缩css
minifyJS: {
mangle: {
toplevel: true,
},
},
}
: false,
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# InlineChunkHtmlPlugin
InlineChunkHtmlPlugin 可以辅助将一些 chunk 出来的模块,内联到 html 中,例如:runtime 的代码,代码量不大,但是必须加装的,那么我们可以内联到 html 中
这个插件是在`reac-dev-utils1 中实现的,所以我们可以安装它
yarn add react-dev-utils -D
# npm install react-dev-utils -D
2
在生成环境的 plugins 中进行配置
在 production 的 plugins 中进行配置
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...
plugins: [
// ...
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.+\.js/]),
],
};
2
3
4
5
6
7
8
9
10