🗒️webpack 简介
来自官网 https://webpack.js.org/concepts/
webpack 是一个静态模块打包工具(static module bundler)。 它从一个或多个入口(entry points)开始,递归构建一个依赖图(dependency graph), 然后将项目所需的所有模块(modules)打包成一个或多个 bundles。
对于 HTTP/1.1,构建的 bundle 非常强大,因为它最大限度地减少了应用程序在浏览器发起新请求时的必须等待的次数
对于 HTTP/2,还可以使用代码分割(code splitting)进行进一步优化
从 v4.0.0 开始,webpack 不再要求必须配置文件了,更多配置详见 configuration
webpack 5 需要 Node.js v10.13.0+
1. entry
entry 是 webpack 开始构建 bundle 的地方
值可以是字符串、字符串数组、对象
默认是
./src/index.js
context(上下文)是包含 entry 文件的基础目录的绝对路径
默认是 Node.js 的当前工作目录
1.1 三类值
1.2 入口描述
当 entry 是对象时,可以使用以下属性:
import
:启动时需要加载的模块以下两个属性不能同时使用
dependOn
:当前入口所依赖的入口(不能循环引用)runtime
:运行时 chunk 的名字(值不能是已存在的 entry 名)如果设置了就会创建一个新的运行时 chunk
在 webpack 5.43.0 之后可将其设为
false
output 相关
filename
:output file 的名字library
:指定 library 选项,为当前 entry 构建一个 library选项同
output.library
publicPath
:为该入口的 output files 指定一个公共 URL 地址,以便在浏览器中引用同
output.publicPath
1.3 多入口场景
将 vendor(第三方库)和 app 模块分开
多页面应用时,拆分资源,提高复用
2. output
output 告诉 webpack 如何将编译好的文件(bundles)写到磁盘,比如位置和命名。
位置默认是
./dist
文件夹main output file 默认是
./dist/main.js
2.1 filename
2.1.1 输出一个 bundle
2.1.2 输出多个 bundles
当输出多个 chunks/bundles(比如多入口/代码拆分/各种插件)时,output 选项始终只有一个。
要确保输出的每个文件名唯一,可以使用占位符(substitutions):
[name]
使用 entry 名字[id]
使用内部 chunk id[contenthash]
使用生成内容的 hash多个占位符的组合,比如
[name].[contenthash].bundle.js
2.1.3 更多形式
2.1.4 说明
对于最初加载的输出文件,用
output.filename
对于按需加载 chunk 的输出文件,用
output.chunkFilename
对于 loader 创建的文件,则须使用 loader 的特定选项
查看更多 output 选项。
3. loaders
webpack 只能理解 JavaScript 和 JSON 文件(这是它开箱即用的功能),而 loader 则赋予了 webpack 处理其它类型文件的能力。
loader 会将这些文件转换为有效的 module 以便应用程序使用,它们也会被添加到模块的依赖图中。loader 旨在对 module 的源代码进行转换,它允许我们在 import
或 load 文件之前对其进行预处理,比如:
将文件从不同语言(比如 TypeScript)转换为 JavaScript 语言,比如
ts-loader
将内联 image 加载为 data URL
直接从 JavaScript 模块
import
CSS 文件,比如css-loader
在这点上,loader 就类似于其它构建工具中的 tasks,它提供了一种强大的方法来处理前端的构建步骤。
3.1 配置
在 high level 上,loader 有两个属性:
test
识别要转换的文件use
定义转换时要用的 loader
以上配置告诉 webpack compiler:
当在
require()/import
语句中碰到路径是.txt
的文件时,在把它添加到 bundle(即被打包)之前先用raw-loader
转换下为每个
.ts
文件使用ts-loader
为每个
.css
文件使用多个 loaders,执行顺序是从右到左(或从下到上)先执行
sass-loader
再执行
css-loader
最后执行
style-loader
注意,test
的正则表达式不带引号。
/\.txt$/
会匹配所有以 .txt 结尾的文件'/\.txt$/'
或"/\.txt$/"
则会匹配具有绝对路径 '.txt' 的单个文件
3.2 特性
loader 具有以下特性:
loader 支持链式调用。执行顺序是相反的,链中的每个 loader 将转换应用于已处理的资源
loader 可以是同步的,也可以是异步的
loader 运行在 Node.js 中,可以做任何可能的事情
其它
普通 module 可以导出 loader
plugin 可以为 loader 带来更多特性
loader 可以产生额外的任意文件
loader 可以通过其预处理函数来自定义 output,所以用户可以非常灵活地处理细粒度逻辑,比如压缩、打包、语言翻译(或编译)等。更多可查阅常用 loaders。
3.3 使用 loader
两种方式:
配置(推荐):在
webpack.config.js
文件中指定module.rules
字段内联:在每个
import
语句中显式指定 loader从 CLI 使用:在 webpack v5 中已被弃用
3.4 解析 loader
loader 遵循标准的 module resolution。在多数情况下,它将从 module path 开始加载(想想 npm install
, node_modules
)。
一个 loader module 应该导出一个函数,并用兼容 JavaScript 的 Node.js 编写。通常使用 npm 管理 loader,但也可以将应用程序中的文件作为自定义 loader。按照约定,loader 通常被命名为 xxx-loader,详见编写 loader。
4. plugin
loader 用于转换特定类型的 module,而 plugin 则用于执行范围更广的任务,比如打包优化、资源管理、环境变量注入,它可以扩展 webpack 的功能。
plugin 是 webpack 的支柱功能,webpack 本身也是构建于相同的插件系统之上的。
webpack plugin 是一个具有 apply
方法的 JavaScript 对象,该方法会被 webpack compiler 调用。
说明:如果在 plugin 中使用了 webpack-sources
package,请使用 require('webpack').sources
替代 require('webpack-sources')
,以避免持久缓存的版本冲突。
4.1 使用
要使用一个插件,就需要 require()
它并将其添加到 plugins
数组中。
大多数插件都可以通过 options 选项进行自定义。我们也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过 new
来创建一个插件实例。
在上面的示例中:
ProgressPlugin
可以自定义编译过程中的进度报告html-webpack-plugin
为应用程序生成一个 HTML 文件,并自动将所有生成的 bundles 注入到该文件中,通过<script>
引入
除了配置文件,我们也可以通过 Node API 来使用 plugin。比如:
4.2 内置插件
webpack 拥有丰富的 plugin 接口,它本身的大部分功能都使用了这些 plugin 接口,这使得 webpack 非常灵活。
plugin 旨在解决 loader 没法做到的事,webpack 提供了很多开箱即用的 plugin。如下:
modules 相关
IgnorePlugin
从 bundles 中排除某些 modulesContextReplacementPlugin
覆盖require
表达式的推断上下文ProvidePlugin
使用 modules 而无需使用import/require
NormalModuleReplacementPlugin
替换与正则表达式匹配的资源
chunks 相关
CommonsChunkPlugin
提取 chunks 间共享的公共 modulesLimitChunkCountPlugin
设置 chunk 的最小/最大限制MinChunkSizePlugin
保持 chunk 大小高于指定限制BannerPlugin
在每个生成的 chunk 顶部添加一个 banner
bundles 相关
HtmlWebpackPlugin
创建 HTML 文件,为 bundles 提供服务MiniCssExtractPlugin
为每个 JS 文件创建一个 CSS 文件DllPlugin
分包(split bundles)以缩短 build 时间
开发和编译相关
开发过程
HotModuleReplacementPlugin
开启 Hot Module Replacement,HMRNpmInstallWebpackPlugin
在开发过程中自动安装缺少的依赖项
编译相关
ProgressPlugin
报告编译进度NoEmitOnErrorsPlugin
不 emit 编译错误DefinePlugin
允许在编译时配置全局常量EnvironmentPlugin
在process.env
上使用DefinePlugin
的简写
压缩相关
CompressionWebpackPlugin
准备 assets 的压缩版本TerserPlugin
使用 Terser 来压缩项目中的 JS
source map
SourceMapDevToolPlugin
对 source map 进行更细粒度的控制EvalSourceMapDevToolPlugin
对 eval source map 进行更细粒度的控制
常用
EslintWebpackPlugin
webpack 的 ESLint 插件CopyWebpackPlugin
将单个文件或整个目录复制到 build 目录
更多第三方插件,可查看 awesome-webpack。
5. mode
mode 的值可以是以下三个,以启用 webpack 内置在相应环境下的优化。
development
:将
DefinePlugin
的process.env.NODE_ENV
设置为development
为 modules 和 chunks 启用有用的名称
production
(默认值)将
DefinePlugin
的process.env.NODE_ENV
设置为production
为 modules 和 chunks 启用确定性的混淆名称
none
:不使用任何默认优化选项
还可以导出一个函数(而不是一个对象),以根据 mode 变量的值来改变行为。比如:
扩展阅读 webpack default options (source code)
6. 浏览器兼容性
webpack 支持所有兼容 ES5 的浏览器(不支持 IE8 及以下版本),因为它需要用 promise 来实现动态 import,webpack 支持两种类似技术:
import()
(推荐):符合 ECMAScript 动态导入提案require.ensure()
:遗留的、webpack-specific
如果想支持旧版本浏览器,则需要在使用这些表达式之前加载一个 polyfill。
7. target
target 选项配置 webpack 的部署目标,因为服务器和浏览器都能使用 JavaScript。
target 的取值可以是 web
(默认), node
, electron
, 还可以是 webworker
, browserslist
, esX
等等。每个 target
都包含各种 deployment/environment 特定的附加项,以满足其需求。详见 Configuration/Target。
比如下面的代码,webpack 会在类似 Node.js 的环境中编译代码,比如使用 Node.js 的 require
加载 chunk,比如不加载任何内置模块(fs
, path
)等。
虽然 webpack 不支持向 target 选项传入多个字符串,但可以创建一个同构库,通过捆绑两个独立的配置。如下:
8. manifest
在使用 webpack 构建的典型 application 和 site 中,主要有三种代码类型:
source code,源码
源码所依赖的第三方库或 vendor 代码
webpack 的 runtime 和 manifest,用来管理所有 modules 的交互
8.1 runtime 和 manifest
在浏览器中运行时,webpack 用来“连接模块化应用程序”的所有代码就是 runtime 和 manifest 数据。
runtime 包含 loading(加载)和 resolving(解析)的逻辑,包括 connecting modules 逻辑(已经加载到浏览器中)和 lazy-load 逻辑(尚未加载到浏览器中)。
当 webpack compiler 开始执行, resolve(解析), map out(映射) 应用程序时,它会保留所有 modules 的详细信息,这个数据集合就称为 manifest(清单)。manifest 数据告诉 webpack 如何管理所需 modules 之间的交互,通常是 /src
目录下的资源如何 bundled(打包), minified(压缩), split chunks(分包-以懒加载) ,以便 webpack optimization。
runtime 会通过 manifest 来 resolve 和 load 模块。无论我们选择哪种 module syntax,那些 import
或 require
语句都会变成 __webpack_require__
方法,该方法会指向 module identifier(模块标识符)。通过使用 manifest 中的数据,runtime 将能检索出这些标识符所对应的模块。
8.2 用途
了解 webpack 在幕后工作,可以让我们更好地使用浏览器缓存(以提高项目性能)。比如:使用 content hash 作为 bundle 文件的名称,旨在告诉浏览器如果文件的内容变了就让缓存失效。然而,有时候即使内容没变但某些 hash 还是会改变,这是由 runtime 和 manifest 的注入引起的,因为它会 change 每个 build。
Guides/Caching:与 long term caching 相关
manifest 数据里保存了 webpack 跟踪的所有 modules 是如何映射到输出 bundles 的数据。我们可以使用插件 WebpackManifestPlugin
,它可以将 manifest 数据提取成 json 文件,这样我们就能知道 webpack 及其 plugin 是如何知道正在生成哪些文件。如果想用其它方式管理 webpack 输出,manifest 会是一个很好的切入点。
9. 模块热替换
模块热替换(Hot Module Replacement,HMR)是在 application 正在运行中的时候,交换、添加或删除 modules,而不用完全 reload。
HMR 可以显著加快开发的速度:
保留 application 的状态(这在完全 reload 时会丢失)
只更新变化的内容
修改源代码中的 CSS/JS 时,浏览器中的会立即生效(这几乎相当于是直接在浏览器的 devtools 里修改)
在开发环境,HMR 可以作为 LiveReload 的替代方案。webpack-dev-server
支持 hot 模式,在这种模式下,它会在 reload 整个页面之前尝试使用 HMR 来进行更新。详见 Guides/Hot Module Replacement。
那么,模块热替换是如何工作的呢?
9.1 在 application 中
在 application 中,会置换(swap in and out,换入和换出)modules,通过以下步骤:
应用程序要求 HMR runtime 检查 updates
runtime 异步下载 updates,并通知应用程序
应用程序要求 runtime 应用 updates
runtime 同步应用 updates
9.2 在 compiler 中
compiler 会发出 "update",以将以前的版本更新到新版本。"update" 由两部分组成:
更新后的 manifest (JSON)。manifest 包括新编译的 hash 和所有 updated chunk 列表
一个或多个 updated chunk (JavaScript)。每个 chunk 都包含着所有 updated modules 的新代码(或表示此 module 已删除的 flag)
compiler 会确保在这些 builds 之间的 module IDs 和 chunk IDs 保持一致。通常是将这些 IDs 存储在内存中(比如 webpack-dev-server
),也可能是将它们存储在 JSON 文件中。
9.3 在 module 中
HMR 是个可选功能,它只会影响包含 HMR 代码的 module。比如为了给 style 追加补丁 style-loader
实现了 HMR 接口,当它通过 HMR 接收到 update 时就会用新样式替换旧样式。当在 module 中实现 HMR 接口时,我们可以描述当 module 被更新时应该发生什么。
然而,在大多数情况下,并不是必须在每个 module 中都编写 HMR 代码。如果 module 没有 HMR 处理程序,那么 update 就会冒泡。这就意味着一个 HMR 处理程序就可以更新整个 module 树。当树中的单个 module 被更新了,整个依赖模块都会被 reloaded。
有关 module.hot 接口的详细信息,可查看 HMR API 页面。
9.4 在 runtime 中
对于 module system runtime,会发出额外的代码来跟踪模块的 parents 和 children。在管理方面,runtime 支持两个方法:check
和 apply
。
check
方法会给 update manifest 发送一个 HTTP 请求。如果请求失败,则说明没有可用更新。如果请求成功,会将 updated chunk 列表与当前的 loaded chunk 列表进行比较。每个 loaded chunk 都会下载相应的 updated chunk。所有 module updates 都存储在 runtime 中。当所有 update chunks 都已下载完毕并准备好应用时,runtime 就会切换到 ready
状态。
apply
方法会将所有 updated module 标记为无效。对于每个无效的 module,都需要有一个 update 处理程序,可以在本模块中也可以在父模块中。否则无效标志会冒泡,即它的父模块们会被标记为无效。直到冒泡到有 update 处理程序的 module,或者是应用程序的 entry point。
之后,所有无效 module 都会被处理(通过处理程序)并卸载,然后更新当前 hash,并调用所有 accept
处理程序。runtime 切换回 idle
状态,一切照常继续。
Last updated