Webpack 4 核心配置剖析
Webpack 是现代
Web
应用程序的静态模块打包工具,它会递归的构建应用程序各个模块的依赖关系图,然后将所有模块打包成一个或多个bundle
。目前
Webpack 已经更新至 4.29.6
版本,增加了诸多打包和执行性能相关的支持,是目前应用最广泛、社区最活跃的
Web 前端代码打包方案。而更新版本的 Webpack 5 已经进入 Beta
发布阶段,未来将会带来更多构建性能的提升,本文依然以更为稳定的 Webpack 4
为讲解对象。
Webpack
提出了入口entry
、输出output
、加载器loader
、插件plugin
这四个核心概念,本文将会在简单介绍
Webpack
相关基础概念之后,对其原生实现的import
模块导入机制进行分析,以清晰的展现
Wepback
在底层所进行的工作;最后逐步备注笔者在开发、生产环境下使用到的各类插件和加载器,并分享在aves和rhino两个开源脚手架项目当中(分别基于
Vue2 和 React16)所使用到的最佳配置实践。
入口 entry
entry
属性用来指定 webpack
应该使用哪个模块作为构建工作的起点,进入起点后 webpack
将会寻找入口起点直接或间接的依赖,并最后输出到称为bundle
的文件。Webpack
中的entry
属性值可以是一个或多个字符串、一个对象或数组。
下面的示例代码当中只定义了一个路径字符串:
1 | module.exports = { |
而下面的示例代码则通过传入对象指明了多个入口点,实质上 Webpack
会使用CommonsChunkPlugin
从应用 bundle
中提取vendor
引用到vendor bundle
,并将引用vendor
的部分替换为__webpack_require__()
调用。
1 | const config = { |
Webpack 内置的
CommonsChunkPlugin
可以为每个页面的共享代码创建 bundle,使得即使在多页面应用下,也能够复用入口起点之间的大量代码和模块。
输出 output
Webpack 的 output 属性指定其所创建的 bundles 输出到何处以及如何命名,
1 | const path = require('path'); |
Webpack 中的chunk
([tʃʌŋk]
n.大块,厚块,数据块)是指一个独立的文件,如果创建了多个chunk
,则应该使用占位符去确保每个文件具有唯一的名称。
1 | const path = require('path'); |
output
属性的filename
拥有如下 5
个占位符:
占位符 | 描述 |
---|---|
[id] |
模块标识符。 |
[name] |
模块名称。 |
[hash] |
模块标识符的 hash 值。 |
[chunkhash] |
chunk 内容的 hash 值。 |
[query] |
模块的query ,例如文件名? 后的字符串。 |
加载器 loader
Webpack 的 loader 加载器机制用于处理非 JavaScript 文件,并将其转换为
Webpack 能够处理的有效模块。即将 JavaScript
之外的其它类型文件,转换为bundle
可以直接引用的模块;例如:将文件从
TypeScript 转换为 JavaScript,或者把内联图片转换为 data URL,甚至允许在
JavaScript 模块内引入 CSS 文件。
Webpack
配置文件中的module
属性主要用于加载各种模块,可以通过其内嵌的rules
属性指定加载某种类型文件时所需要使用到的loader
加载器。
1 | const path = require('path'); |
rules
下的use
属性指定了需要使用的目标加载器,而test
属性则用于标识需要加载器进行处理的文件。根据需要,开发人员还可以对同一类文件应用多个loader
进行处理。
1 | { |
loader
还可以在import
语句中内联进行使用,用于针对特定import
语句使用指定的加载器。
1 | import Styles from "style-loader!css-loader?modules!./styles.css"; |
加载器 loader 的解析是通过
resolver
库进行的,resolver
用于帮助 Webpack 找到bundle
中需要引入的模块代码,这些代码包含在每条require
/import
语句当中。Webpack 打包模块时会使用到自家的开源项目enhanced-resolve来解析引入的文件路径。
插件 plugin
如同上面所描述的那样,loader 用于转换某些类型的模块,而插件 plugin 得益于其丰富的接口,可以用来处理加载器 loader 无法实现的更加丰富的任务,例如:打包优化与压缩、重新定义环境变量等等。
Webpack 中通过plugins
属性来使用插件。
1 | module.exports = { |
Webpack 的插件 plugin 本质是一个拥有apply
属性的
JavaScript 对象,这个apply
属性会被 Webpack
的compiler
对象调用,而该对象可以在整个 Webpack
编译生命周期内进行访问。
1 | // LogOnBuildPlugin.js |
Webpack 模块
相比 NodeJS 的模块机制,Webpack 模块所涵盖的范围显然更加丰富:
- ES2015 的
import
语句 - CommonJS 的
require()
语句 - AMD 的
define
/require
语句 - css/sass/less 中的
@import
语句。 - CSS 文件中的
url(...)
或 HTML 文件中的<img src=...>
所指向的图片资源链接。
Webpack 通过enhanced-resolve可以解析三种文件路径:
- 绝对路径:文件在操作系统上的绝对路径。
1 | import "/home/common.js"; |
- 相对路径:相对于当前引入操作的文件的位置。
1 | import "./source/demo.js"; |
- 模块路径:在
resolve.modules
中指定的目录内搜索,默认是["node_modules"]
。
1 | import Vue from "vue"; |
打包策略
Webpack 构建的应用程序中,主要存在以下三种类型的代码:
- 开发人员编写的业务代码。
- 引入的第三方
vendor
库。 - Webpack
运行时与所有模块进行交互的
manifest
([ˈmænɪfest] n.清单)。
manifest
是浏览器运行时,Webpack 用来连接模块所需的加载和解析逻辑(无论import
还是require
模块语法,最后都会被 Webpack 转换为指向模块标识符的__webpack_require__
方法)。
为了有效规避缓存问题,并最大化浏览器渲染性能,可以考虑将上述三种类型代码单独打包到三类文件。
在 Webpack 3.0 可以通过CommonsChunkPlugin插件完成这件工作。该插件可以将公共的依赖模块提取为一个新的的chunk
文件,通过将公共模块分拆并且合成之后,便于应用进行缓存和后续使用,避免浏览器重复加载并运行同一段功能代码。
1 | new webpack.optimize.CommonsChunkPlugin({ |
1 | entry: { |
Webpack4 当中已经废弃了
CommonsChunkPlugin
的使用,转而采用更加简单明了的optimization.splitChunks
和optimization.runtimeChunk
属性完成类似工作,下面的章节马上会进行介绍。
optimization
Webpack4
开始提供一个依赖于mode
属性进行配置和优化的选项,代替过去CommonsChunkPlugin
、UglifyjsWebpackPlugin
等第三方插件的使用。
minimize
让 Webpack 使用UglifyjsWebpackPlugin进行最小化打包。Webpack
配置对象的mode
属性为production
时该属性默认为true
。
1 | module.exports = { |
minimizer
通过提供一个或多个不同的UglifyjsWebpackPlugin实例来指定一个新的压缩器。
1 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); |
splitChunks
Webpack4.0 为动态导入模块提供了一个新的通用代码文件打包策略,具体配置项可以参考本文中的SplitChunksPlugin章节。
runtimeChunk
设置该属性为true
时,可以添加额外的代码块至每个只在运行时包含的
Webpack 入口点。该属性可以通过提供一个字符串值来使用插件的预设模式:
single
: 建立一个所有代码块共享的运行时文件。
multiple
: 为多个通用代码块建立多个运行时文件。
设置该属性为对象时,它只可能去提供name
属性,为运行时代码块提供可替代的名称或命名工厂。
该属性默认值为
false
,表示每个入口代码块都嵌入到运行时。
1 | module.exports = { |
noEmitOnErrors
当编译阶段出现错误时,使用optimization.noEmitOnErrors
跳过emitting
阶段,从而确保不会有错误的资源被emitted
。stats
中的emitted
标志对于所有资源都是false
的。
1 | module.exports = { |
nodeEnv
告诉 Webpack
设置process.env.NODE_ENV
为指定的字符串,optimization.nodeEnv
底层使用了DefinePlugin
插件,除非设置为false
。如果
Webpack
对象设置了mode
属性,那么optimization.nodeEnv
默认为mode
属性的值,否则将会回退为"production"
。
- 任意字符串:需要设置到
process.env.NODE ENV
的值。 false
:不设置或修改process.env.NODE_ENV
的值。
SplitChunksPlugin
DefinePlugin
webpack.DefinePlugin
ModuleConcatenationPlugin
附上英文原文链接。
过去 webpack 打包的时候,每个 module
都会被包装到独立的函数闭包,这些包装函数会让 JavaScript
在浏览器中执行更缓慢。经过比较,Closure Compiler
和RollupJS
提升、连接全部模块作用域到一个闭包的方式,会让代码在浏览器中执行得更加迅速。因此,Webpack3
当中提供了如下 plugin 来开启类似特性。
1 | new webpack.optimize.ModuleConcatenationPlugin(); |
Webpack3 作用域提升的实现依赖于 ECMAScript 模块语法,因此 Webpack 会基于开发人员当前使用的模块系统回滚到过去的打包方式。
不使用 ModuleConcatenationPlugin 打包的文件
1 | ➜ bundles git:(master) ll |
使用 ModuleConcatenationPlugin 打包的文件
1 | ➜ bundles git:(master) ✗ ll |
结论:文件尺寸有一定程度上的缩小。
import 实现机制
Webpack 在 2.0 版本之后原生实现了import
,而不再需要
Babel 之类加载器进行转换。
最佳实践
Webpack 4 核心配置剖析