04 【优化】
1.Public Path(公共路径)
Section titled “1.Public Path(公共路径)”webpack 提供一个非常有用的配置,该配置能帮助你为项目中的所有资源指定一个基础路径。它被称为公共路径(publicPath)。
在开发模式中,我们通常有一个 assets/ 文件夹,它往往存放在和首页一个级别的目录下。这样是挺方便;但是如果在生产环境下,你想把这些静态文件统一使用CDN加载,那该怎么办?
publicPath 配置公共路径,所有文件的引用将自动添加公共路径的绝对地址。
2.环境变量 Environment variable
Section titled “2.环境变量 Environment variable”想要消除 webpack.config.js 在 开发环境 和 生产环境 之间的差异,你可能需要环境变量(environment variable)。
webpack 命令行 环 境配置 的 —env 参数,可以允许你传入任意数量的环境变量。而在 webpack.config.js 中可以访问到这些环境变量。例如, \--env production 或\--env goal=local 。
对于我们的 webpack 配置,有一个必须要修改之处。通常, module.exports 指向配置对象。要使用 env 变量,你必须将 module.exports 转换成一个函数:
3.配置文件优化
Section titled “3.配置文件优化”开发环境(/doc/webpack-guides-development)和生产环境(production)的构建目标差异很大。
在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。
而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置 。
虽然,以上我们将生产环境和开发环境做了略微区分,但是,请注意,我们还是会遵循不重复原则(Don’t repeat yourself - DRY),保留一个“通用”配置。为了将这些配置合并在一起,我们将使用一个名为 webpack-merge 的工具。通过“通用”配置,我们不必在环境特定(environment-specific)的配置中重复代码。
分别对 development 和 production 两种模式优化。
1、首先新建 webpack-config 文件夹,在文件夹中添加三个文件,分别为通用的配置文件、开发模式的配置文件以及生产模式的配置文件。
webpack.config.common.js
webpack.config.dev.js
webpack.config.prod.js
于是可以知道,这三个文件其实就是三个对象。第一个对象存放共有的属性和值,dev的对象则存放开发环境的对象,pro的则存放生产环境的对象。 当我们想打开发的包时,只需要使用公用的对象和开发的对象的并集即可。也就是这两者要进行合并。 并且是深层的合并,取并集。webpack提供了一个插件来帮助我们完成这个步骤。
2、使用 webpack-merge 将文件进行合并。安装 webpack-merge
3、添加一个合并文件 webpack.config.js
webpack.config.js
4、修改 package.json 文件
如果是当前目录安装了包,可以在脚本中直接使用webpack命令
5、使用命令运行

4.webpack模块与解析原理
Section titled “4.webpack模块与解析原理”4.1 在webpack中何为模块
Section titled “4.1 在webpack中何为模块”当我们在代码中引入使用的这些东西,都可以成为webpack的模块。

4.2 模块的引入
Section titled “4.2 模块的引入”resolver 是一个帮助寻找模块绝对路径的库。 一个模块可以作为另一个模块的依赖模块,然后被后者引用,如下:
所依赖的模块可以是来自应用程序的代码或第三方库。 resolver 帮助 webpack 从每个 require/import 语句中,找到需要引入到 bundle 中的模块代码。 当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径。
webpack能够解析三种常见的模块引入方法。
4.2.1 相对路径
Section titled “4.2.1 相对路径”
这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录。 在import/require 中给定的相对路径,enhanced-resolve 会拼接此上下文路径,来生成模块的绝对路径(path.resolve(__dirname, RelativePath)) 。 这也是我们在写代码时最常用的方式之一,另一种最常用的方式则是模块路径。
4.2.2 绝对路径
Section titled “4.2.2 绝对路径”绝对路径就是从项目的根目录开始的。这个”/“就是指代的项目根目录:

由于已经获得文件的绝对路径,因此不需要再做进一步解析。
4.2.3 第三方模块的引入
Section titled “4.2.3 第三方模块的引入”webpack会自动从node_module中识别并引入对应的第三方库。
也就是在 resolve.modules 中指定的所有目录检索模块(node_modules里的模块已经被默认配置了)。 你可以通过配置别名的方式来替换初始模块路径, 具体请参照下面 resolve.alias 配置选项。
4.3 resolve配置
Section titled “4.3 resolve配置”路径别名
当我们在写业务代码的时候,经常需要引入组件,如果一个组件隐藏的太深,就很麻烦。为了简化路径的写法,于是就有了路径别名:

默认识别文件后缀顺序
我们发现,只需要“import add from '@/utils/add'”, webpack就可以帮我们找到add.js。 事实上,这与import add from '@/utils/add.js' 的效果是一致的。 为 什么会这样? 原来webpack的内置解析器已经默认定义好了一些 文件/目录 的路径 解析规则。 比如当我们
utils是一个文件目录而不是模块(文件),但webpack在这种情况下默认帮我们添加了 后缀“/index.js”,从而将此相对路径指向到utils里的index.js。 这是webpack解析器 默认内置好的规则。 那么现在有一个问题: 当utils文件夹下同时拥有add.js add.json时,“@utils/add”会指向谁呢?
@/utils/add.json
我们发现仍然指向到add.js。 当我们删掉add.js,会发现此时的引入的add变成了一个 json对象。 上述现象似乎表明了这是一个默认配置的优先级的问题。 而webpack对 外暴露了配置属性: resolve.extentions , 它的用法形如:
webpack会按照数组顺序去解析这些后缀名,对于同名的文件,webpack总是会先 解析列在数组首位的后缀名的文件。
4.4 外部扩展
Section titled “4.4 外部扩展”怎么理解呢,意思是如果需要引用一个库,但是又不想让webpack打包(减少打包的时间),并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用(一般都以import方式引用使用),那就可以通过配置externals。
这样做的目的就是将不怎么需要更新的第三方库脱离webpack打包,不被打入bundle中,从而减少打包时间,但又不影响运用第三方库的方式,例如import方式等。
有时候,我们为了减小bundle的体积,会把一些不变的第三方库用cdn的形式引入进来。比如jQuery,我们会在html文件中通过script中引入使用。但是我们更希望webpack帮助我们做这件事情。
externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。相反,所创建的 bundle 依赖于那些存在于用户环境(consumer’s environment)中的依赖。此功能通常对 library 开发人员来说是最有用的,然而也会有各种各样的应用程序用到它。
例如:
在index.html中
webpack.config.js配置如下:
其中 jquery 是暴露给你内部代码使用的模块名;jQuery 是外部环境存在的模块名
这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:
这样不仅之前对第三方库的用法方式不变,还把第三方库剥离出webpack的打包中,从而加速webpack的打包速度。
但是写在index.html页面很麻烦
这样之后,在代码中引入jquery,会暴露为一个$的对象,可以直接使用。并且它的引入,是首屏资源引入之后,执行到这部分代码时才会引入,这样可以减少首屏的js加载时间。

4.5 依赖图
Section titled “4.5 依赖图”每当一个文件依赖另一个文件时,webpack 都会将文件视为直接存在 *依赖关系 *。这使得 webpack 可以获取非代码资源,如 images 或 web 字体 等。并会把它们作为 *依赖 * 提供给应用程序。
当 webpack 处理应用程序时,它会根据命令行参数中或配置文件中定义的模块列表开始处理。 从 入口 开始,webpack 会递归的构建一个 依赖关系图,这个依赖图包含着应用程序中所需的每个模块,然后将所有模块打包为少量的 bundle —— 通常只有一个 —— 可由浏览器加载。
单纯讲似乎很抽象,我们更期望能够可视化打包产物的依赖图,下边列示了一些 bundle分析工具。
bundle 分析(bundle analysis) 工具:
官方是一个不错的开始。还有一些其他社区支持的可选项:
webpack-chart: webpack stats 可交互饼图。
webpack-visualizer: 可视化并分析你的 bundle,检查哪些模块占用空间,哪些 可能是重复使用的。
webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展示为 一个便捷的、交互式、可缩放的树状图形式。
webpack bundle optimize helper:这个工具会分析你的 bundle,并提供可操 作的改进措施,以减少 bundle 的大小。
bundle-stats:生成一个 bundle 报告(bundle 大小、资源、模块),并比较不 同构建之间的结果。
我们来使用 webpack-bundle-analyzer 实现。
安装
然后我们配置它:
这时我们执行打包命令
我们在浏览器中打开http://127.0.0.1:8888,我们成功可视化了打包产物依赖图!

每个文件的占用面积越大则该文件越大,所以找到相关文件优化即可
注意: 对于 HTTP/1.1 的应用程序来说,由 webpack 构建的 bundle 非常强大。当 浏览器发起请求时,它能最大程度的减少应用的等待时间。 而对于 HTTP/2 来说, 我们还可以使用代码分割进行进一步优化。(开发环境观测的话需要在DevServer里 进行配置{http2:true, https:false})。
options
5.postcss处理css3兼容性前缀
Section titled “5.postcss处理css3兼容性前缀”5.1 概述
Section titled “5.1 概述”postcss是一个用js工具和插件转换css代码的工具。比如说可以使用Autoprefixer插件自动获取浏览器流行度和能够支持的属性,并且根据这些数据帮助我们自动地为css规则添加前缀,将最新的css语法转化成大多数浏览器能够理解的语法。
5.2 具体的应用
Section titled “5.2 具体的应用”5.2.1 安装
Section titled “5.2.1 安装”这里需要解释一下,mini-css-extract-plugin插件是替换style-loader插件的(都是把css-loader解析后的代码添加到html上,不同的是style-loader直接添加到html上,而mini-css-extract-plugin是采用link资源的方式添加。所以这里直接使用mini-css-extract-plugin)
5.2.2 配置
Section titled “5.2.2 配置”注意这里的use数组中的配置,是有顺序要求的,比如我这里是使用scss预处理器,还安装了sass-loader。 那么写出来的style.scss文件,先是经过sass-loader编译处理成常规的css,然后,再经由postcss-loader添加浏览器兼容前缀。再经过css-loader解析成webpack能够理解的模块。最后经由MiniCssExtractPlugin来通过link链接到index.html上。 写如下的css代码,会发现并没有添加兼容浏览器的 css3前缀,这是因为还需要安装一个插件来实现这一个功能。

5.2.3 配置autoprefixer
Section titled “5.2.3 配置autoprefixer”插件 autoprefixer 提供自动给样式加前缀去兼容浏览器,
安装
然后再在项目根目录下创建一个文件:postcss.config.js,这个文件是专门用来配置css的插件的。
最后,在 package.json 内增加如下实例内容:
last 2 versions: 每个浏览器中最新的两个版本。1% or >= 1%: 全球浏览器使用率大于1%或大于等于1%。
尽量不要用browserslist最好放在.browserslistrc或者package.json内 postcss.config.js内 overrideBrowserslist 的优先级高于 package.json的browserslist,且不能为空,否则会不生效。
于是重启项目,则会看到前缀了:

6.使用TypeScript
Section titled “6.使用TypeScript”在前端生态里,TS扮演着越来越重要的角色。 我们直入正题,讲下如何在webpack工程化环境中集成TS。 首先,当然是安装我们的ts和对应的loader。
接下来我们需要在项目根目录下添加一个ts的配置文件————tsconfig.json,我们 可以用ts自带的工具来自动化生成它。
我们发现生成了一个tsconfig.json,里面注释掉了绝大多数配置。 现在,根据我们想要的效果来打开对应的配置。
好了,接下来我们新增一个src/index.ts,内置一些内容。 然后我们别忘了更改我们的entry及配置对应的loder。 当然,还有resolve.extensions,将.ts放在.js之前,这样它会先找.ts。 注意,如果我们使用了sourceMap,一定记得和上面的ts配置一样,设置 sourcemap为true。
webpack.config.js
在从 npm 上安装第三方库时,一定要记得同时安装这个库的类型声明文件(typing definition)。 我们可以从 TypeSearch中找到并安装这些第三方库的类型声明文件(https://www.ty pescriptlang.org/dt/search?search=) 。 举个例子,如果想安装 lodash 类型声明文件,我们可以运行下面的命令:
index.ts
7.多页面应用
Section titled “7.多页面应用”尽管单页面应用很流行,但是我们并不总是需要它。 在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 optimization.splitChunks 为页面间共享的应用程序代码创建 bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。
7.1 entry 配置
Section titled “7.1 entry 配置”7.1.1 单个入口(简写)语法
Section titled “7.1.1 单个入口(简写)语法”用法: entry: string | [string]
webpack.config.js
entry 属性的单个入口语法,参考下面的简写:
我们也可以将一个文件路径数组传递给 entry 属性,这将创建一个所谓的 “multimain entry”。在你想要一次注入多个依赖文件,并且将它们的依赖关系绘制在一个 “chunk” 中时,这种方式就很有用。
【优点】当通过一个入口为应用程序快速配置webpack时,单一入口的语法是很好的选择。
【缺点】使用这种语法方式它的扩展性、配置灵活性不大。
7.1.2 对象语法
Section titled “7.1.2 对象语法”用法: entry: { string | [string] } | {}
对象语法会比较繁琐。然而,这是应用程序中定义入口的最可扩展的方式。
用于描述入口的对象。可以使用如下属性:
-
dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。 -
filename: 指定要输出的文件名称。 -
import: 启动时需加载的模块。 -
library: 指定 library 选项,为当前 entry 构建一个 library。 -
runtime: 运行时 chunk 的名字。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为false以避免一个新的运行时 chunk。 -
publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址。请查看 output.publicPath。
“webpack 配置的可扩展” 是指,这些配置可以重复使用,并且可以与其他配置组合使用。这是一种流行的技术,用于将关注点从环境(environment)、构建目标(build target)、运行时(runtime)中分离。然后使用专门的工具(如 webpack-merge)将它们合并起来。
Tip
runtime和dependOn不能在同一个入口上同时使用- 确保
runtime不能指向已存在的入口名称dependOn不能循环引用
示例:
webpack.config.js :
runtime 和 dependOn 不应在同一个入口上同时使用,所以如下配置无效,并且会抛出错误:
确保 runtime 不能指向已存在的入口名称,例如下面配置会抛出一个错误:
另外 dependOn 不能是循环引用的,下面的例子也会出现错误:
7.1.3 使用场景
Section titled “7.1.3 使用场景”1.分离 app(应用程序) 和 vendor(第三方库) 入口
webpack.config.js
这是告诉 webpack 要配置 2 个单独的入口点。
webpack.prod.js :
webpack.dev.js :
这样就可以在 vendor.js 中存入未做修改的必要 library 或文件(例如 Bootstrap, jQuery, 图片等),然后将它们打包在一起成为单独的 chunk。
内容保持不变,这使浏览器可以独立地缓存它们,从而减少了加载时间。
在 webpack < 4 的版本中,通常将 vendor 作为一个单独的入口起点添加到 entry 选项中,以将其编译为一个单独的文件(与 CommonsChunkPlugin 结合使用)。
在 webpack 4 中不鼓励这样做。而是使用 optimization.splitChunks选项,将 vendor 和 app(应用程序) 模块分开,并为其创建一个单独的文件。
不要为 vendor 或其他不是执行起点创建 entry。
2.多页面应用程序
这是告诉 webpack 需要三个独立分离的依赖图。
在多页面应用程序中,server 会拉取一个新的 HTML 文档给客户端。
页面重新加载此新文档,并且资源被重新下载。
然而,这提供了特殊的机会去做很多事,例如使用 optimization.splitChunks为页面间共享的应用程序代码创建 bundle。
由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。
7.2 配置 index.html 模板
Section titled “7.2 配置 index.html 模板”
1.生成多个HTML文件
要生成多个HTML文件,请在plugins数组中多次声明插件
webpack.config.js
2.编写自己的模板
如果默认生成的HTML不能满足您的需要,您可以提供自己的模板。最简单的方法是使用template选项并传递一个自定义的HTML文件。html-webpack-plugin将自动将所有必需的CSS、JS、manifest和favicon文件注入到标记中。
其他模板加载程序的详细信息在这里进行了记录。
index.html
8.tree shaking
Section titled “8.tree shaking”什么是treeShaking?
- treeShaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。
treeShaking有什么用?
- 至于说有什么用呢?它的作用就是将程序中没有用到的代码在打包编译的时候都删除掉,这样能减少打包后包的体积大小,减少程序执行的时长
(https://webpack.js.org/guides/tree-shaking/)
注意:Tree Shaking只支持ES Module语法(即通过import引入 export导出).
项目结构
mian.js
需要将 mode 配置设置成development,以确定 bundle 不会被压缩:
index.js中引入
从上面看到,我们引用并使用了math.add 函数,没有使用 math.subtract 函数
执行 npx webpack 可以看到,打包结果中 math 模块的两个函数都被打包了

注意:在mode为development模式下是不起作用的,应该在开发过程中是要不断调试代码的;
执行 npx webpack 可以看到,只有 已经使用的 add 函数被暴露出去

在生产环境看以下效果
执行 npx webpack 可以看到,只有已经使用的 add 函数的执行结果, subtract 函数就是所谓的“未引用代码(dead code)”,也就是说,应该删除掉未被引用的 export。并且代码已经被 webpack 优化精简了

可以得出结论,*tree shaking * 会将通过使用 ES module 方式引用 的模块中未使用的代码 删除掉
tree shaking 两个关键词:1. 使用 ES module 模块方案; 2. 未使用的代码
src/index.js
从上面看到,我们引用了 add, subtract 但只使用了math.add 函数,没有使用 math.subtract 函数
执行 npx webpack 可以看到,打包结果中依旧只有 add 函数被打包了,未使用过的 subtract 函数被删除了

出于好奇,webpack是如何完美的避开没有使用的代码的呢?
很简单:就是 Webpack 没看到你使用的代码。Webpack 跟踪整个应用程序的 import/export 语句,因此,如果它看到导入的东西最终没有被使用,它会认为那 是未引用代码(或叫做“死代码”—— dead-code ),并会对其进行 tree-shaking 。
死代码并不总是那么明确的。下面是一些死代码和“活”代码的例子:
9.sideEffects
Section titled “9.sideEffects”注意 Webpack 不能百分百安全地进行 tree-shaking。有些模块导入,只要被引入,就会对应用程序产生重要的影响。一个很好的例子就是 全局样式 文件,或者 全局JS 文件。
Webpack 认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking,因为这将破坏整个应用程序。Webpack 的设计者清楚地认识到不知道哪些文件有副作用的情况下打包代码的风险,因此webpack4默认地将所有代码视为有副作用。这可以保护你免于删除必要的文件,但这意味着 Webpack 的默认行为实际上是不进行 tree-shaking。值得注意的是webpack5默认会进行 tree-shaking。
src/style.css
src/todo.global.js
src/index.js
webpack.config.js
执行 npx webpack serve ,你会发现 style.css、 todo.global.js 都生效了,这是因为这两个文件不是使用的 ESmodules 方式将模块导出(export)的。
Webpack 认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking,因为这将破坏整个应用程序。
Webpack 的设计者清楚地认识到不知道哪些文件有副作用的情况下打包代码的风险,因此webpack 4默认地将所有代码视为有副作用。
这可以保护你免于删除必要的文件,但这意味着 Webpack 的默认行为实际上是不进行 tree-shaking。值得注意的是 webpack 5 默认会进行 tree-shaking。如何告诉 Webpack 你的代码无副作用,可以通过 package.json 有一个特殊的属性sideEffects,就是为此而存在的。
如何告诉 Webpack 你的代码无副作用,可以通过 package.json 有一个特殊的属性 sideEffects ,就是为此而存在的。
sideEffects 有三个可能的值:
true:(默认值)这意味着所有的文件都有副作用,也就是没有一个文件可以 tree-shaking。
false:告诉 Webpack 没有文件有副作用,所有文件都可以 tree-shaking。
数组:是文件路径数组。它告诉 webpack,除了数组中包含的文件外,你的任何文件都没有副作用。因此,除了指定的文件之外,其他文件都可以安全地进行 tree-shaking。
Tip
“side effect(副作用)” 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
package.json
执行 npx webpack serve 可以发现并无变化,因为这是默认的
执行 npx webpack serve 可以发现上面示例中的 style.css、 todo.global.js 都被 tree-shaking 了
显然 sideEffects 设置为 true 或则 false 显得有些鲁莽极端,你可以使用数组的方式配置文件
执行 npx webpack serve 可以发现上面示例中的 style.css、 todo.global.js 都被 tree-shaking 了
webpack 4 曾经不进行对 CommonJs 导出和 require() 调用时的导出使用分析。
webpack 5 增加了对一些 CommonJs 构造的支持,允许消除未使用的 CommonJs 导出,并从 require() 调用中跟踪引用的导出名称。
解释 tree shaking 和 sideEffects
sideEffects 和 usedExports(更多被认为是 tree shaking)是两种不同的优化方式。
sideEffects 更为有效 是因为它允许跳过整个模块/文件和整个文件子树。
usedExports 依赖于 terser 去检测语句中的副作用。它是一个 JavaScript 任务而且没有像 sideEffects 一样简单直接。而且它不能跳转子树/依赖由于细则中说副作用需要被评估。
结论
我们学到为了利用 tree shaking 的优势, 你必须…