为 webpack 做出贡献的群体,通常热衷于开源项目、关心用户体验和关注软件生态系统,对这些人来说,更具有意义的事情是,获得共同推动着 web 向前发展的成就感。由于我们使用 Open Collective 资金模型,公开透明地积累和管理资金,所以我们能够通过贡献人员、依赖项目和核心团队获得支持和资金。想要捐款,只需点击下面的按钮……
但是,投入资金的回报情况如何呢?
我们希望能为开发人员提供最大程度的愉悦开发体验。开发人员可以通过如下方式为我们提供帮助,例如,贡献丰富有趣的文档、发布 pull request、帮助我们覆盖小众用例,并辅助维护 webpack。
任何人都可以通过以下方式来帮助 webpack:
可以要求你的雇主通过使用 webpack 来改进工作流程:webpack 是用于字体、图像、图像优化和 json 的 all-in-one 全特性工具。向他们解释 webpack 试图将代码和资源打包为最小文件体积,从而使网站和应用程序更加快速。
向 webpack 贡献代码,并不只是对专属 club 的贡献。你作为开发人员正在为下游项目的整体健康做出贡献。数百个、甚至数千个项目依赖于 webpack,你的贡献将使所有用户的生态系统更加完善。
本章节的其余部分,是专门针对那些「希望成为我们持续增长社区的一部分」的开发人员编写:
CTO, VP 和业主也可以向我们提供帮助!
webpack 是打包代码的全特性工具。它可以在由社区驱动的 plugin 和 loader 的帮助下处理字体、图像、数据等。将你的所有资源交付一个工具进行处理,是非常有效地资源管理方案,这样你或你的团队可以花更少的时间,来确保具有许多移动部件的机器正常工作,并且有更多时间构建你的产品。
除了资金援助外,公司还可以通过以下方式支持 webpack:
你还可以鼓励开发人员,向 webpack 生态系统贡献开源的 loader, plugin 和其他工具。最后,如上所述,非常感谢帮助我们添加 CI/CD 基础设施的那些公司。
对于那些对我们的使命感兴趣的人 - 例如风险投资者、政府机构、数字营销公司等 - 我们真诚希望你能够与我们展开合作,通过 webpack 这个最好的 npm package,改善你的产品!所以请毫不犹豫地向我们提出你需要解决的问题。
随着 webpack 的发展,webpack 特性(features)和改动(changes)的文档会同步更新。webpack 集成了自动创建 issue 的机制,在过去几年被证明是行之有效的。 当一个特性被合并后,在我们的仓库(repository)中会创建一个文档要求的 issue ,我们希望该 issue 能及时解决。这意味着有特性,改动和重要改动(breaking changes)等着文档提交、检查、发布。尽管如此,如果 Pull Request 作者 30 天还未完成,我们会把这种 Pull Request 标记为失效(stale)。我们会接管未完成的部分,并完成该任务。 如果 Pull Request 作者把 fork 的仓库的写权限授权给 webpack 文档团队,我们将直接提交到你的分支来完成任务。否则,我们不得不自己重头开始,或者委托给有意愿的社区成员。这样你的 Pull Request 就是多余的,可能会被清理程序(cleanup process)关闭。
The following sections contain all you need to know about editing and formatting the content within this site. Make sure to do some research before starting your edits or additions. Sometimes the toughest part is finding where the content should live and determining whether or not it already exists.
edit
and expand on the structure.Each article contains a small section at the top written in YAML Frontmatter:
---
title: My Article
group: My Sub-Section
sort: 3
contributors:
- [github username]
related:
- title: Title of Related Article
url: [url of related article]
---
Let's break these down:
title
: The name of the article.group
: The name of the sub-sectionsort
: The order of the article within its section (or) sub-section if it is present.contributors
: A list of GitHub usernames who have contributed to this article.related
: Any related reading or useful examples.Note that related
will generate a Further Reading section at the bottom of the page and contributors
will yield a Contributors section below it. If you edit an article and would like recognition, don't hesitate to add your GitHub username to the contributors
list.
css-loader
, ts-loader
, …BannerPlugin
, NpmInstallWebpackPlugin
, …Syntax: ```javascript … ```
function foo() {
return 'bar';
}
foo();
Use single quotes in code snippets and project files (.jsx
, .scss
etc):
- import webpack from "webpack";
+ import webpack from 'webpack';
And in inline backticks:
correct
Set value to 'index.md'
...
incorrect
Set value to "index.md"
...
Lists should be ordered alphabetically.
Parameter | Explanation | Input Type | Default Value |
---|---|---|---|
--debug | Switch loaders to debug mode | boolean | false |
--devtool | Define source map type for the bundled resources | string | - |
--progress | Print compilation progress in percentage | boolean | false |
Tables should also be ordered alphabetically.
The configuration properties should be ordered alphabetically as well:
devServer.compress
devServer.hot
devServer.static
Syntax: >
This is a blockquote.
Syntax: T>
Syntax: W>
Syntax: ?>
Do not make assumptions when writing the documentation.
- You might already know how to optimize bundle for production...
+ As we've learned in [production guide](/guides/production/)...
Please do not assume things are simple. Avoid words like 'just', 'simply'.
- Simply run command...
+ Run the `command-name` command...
Always provide types and defaults to all of the documentation options in order to keep the documentation accessible and well-written. We are adding types and defaults after entitling the documented option:
configuration.example.option
string = 'none'
Where = 'none'
means that the default value is 'none'
for the given option.
string = 'none': 'none' | 'development' | 'production'
Where : 'none' | 'development' | 'production'
enumerates the possible type values, in this case, three strings are acceptable: 'none'
, 'development'
, and 'production'
.
Use space between types to list all available types for the given option:
string = 'none': 'none' | 'development' | 'production'
boolean
To mark an array, use square brackets:
string
[string]
If multiple types are allowed in array
, use comma:
string
[string, RegExp, function(arg) => string]
To mark a function, also list arguments when they are available:
function (compilation, module, path) => boolean
Where (compilation, module, path)
lists the arguments that the provided function will receive and => boolean
means that the return value of the function must be a boolean
.
To mark a Plugin as an available option value type, use the camel case title of the Plugin
:
TerserPlugin
[TerserPlugin]
Which means that the option expects one or few TerserPlugin
instances.
To mark a number, use number
:
number = 15: 5, 15, 30
To mark an object, use object
:
object = { prop1 string = 'none': 'none' | 'development' | 'production', prop2 boolean = false, prop3 function (module) => string }
When object's key can have multiple types, use |
to list them. Here is an example, where prop1
can be both a string and an array of strings:
object = { prop1 string = 'none': 'none' | 'development' | 'production' | [string]}
This allows us to display the defaults, enumeration and other information.
If the object's key is dynamic, user-defined, use <key>
to describe it:
object = { <key> string }
Sometimes, we want to describe certain properties of objects and functions in lists. When applicable add typing directly to the list where properties are enlisted:
madeUp
(boolean = true
): short descriptionshortText
(string = 'i am text'
): another short descriptionAn example can be found on the options
section of the EvalSourceMapDevToolPlugin's page.
Please use relative URLs (such as /concepts/mode/
) to link our own content instead of absolute URLs (such as https://webpack.js.org/concepts/mode/
).
loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 Loader API,并通过 this
上下文访问。
在深入研究不同 loader 以及他们的用法和例子之前,我们先看三种本地开发测试的方法。
匹配(test)单个 loader,你可以通过在 rule 对象使用 path.resolve
指定一个本地文件:
webpack.config.js
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {
/* ... */
},
},
],
},
],
},
};
匹配(test)多个 loaders,你可以使用 resolveLoader.modules
配置,webpack 将会从这些目录中搜索这些 loaders。例如,如果你的项目中有一个 /loaders
本地目录:
webpack.config.js
const path = require('path');
module.exports = {
//...
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')],
},
};
顺便提一下,如果你已经为 loader 创建了独立的库和包,你可以使用 npm link
来将其链接到你要测试的项目。
当一个 loader 在资源中使用,这个 loader 只能传入一个参数 - 一个包含资源文件内容的字符串。
同步 loader 可以 return
一个代表已转换模块(transformed module)的单一值。在更复杂的情况下,loader 也可以通过使用 this.callback(err, values...)
函数,返回任意数量的值。错误要么传递给这个 this.callback
函数,要么抛给(thrown in)同步 loader 。
loader 会返回一个或者两个值。第一个值的类型是 JavaScript 代码的字符串或者 buffer。第二个可选值是 SourceMap,它是个 JavaScript 对象。
当链式调用多个 loader 的时候,请记住它们是反方向执行的。取决于数组写法格式,从右向左或者从下向上执行。
在下例中,foo-loader
被传入原始资源,bar-loader
将接收 foo-loader
的产出,返回最终转化后的模块和一个 source map(可选)
webpack.config.js
module.exports = {
//...
module: {
rules: [
{
test: /\.js/,
use: ['bar-loader', 'foo-loader'],
},
],
},
};
编写 loader 时应该遵循以下准则。它们按重要程度排序,有些仅适用于某些场景,请阅读下面详细的章节以获得更多信息。
loaders 应该只做单一任务。这不仅使每个 loader 易维护,也可以在更多场景链式调用。
利用 loader 可以链式调用的优势。写五个简单的 loader 实现五项任务,而不是一个 loader 实现五项任务。功能隔离不仅使 loader 更简单,可能还可以将它们用于你原先没有想到的功能。
以通过 loader 选项或者查询参数得到的数据渲染模板为例。可以把源代码编译为模板,执行并输出包含 HTML 代码的字符串写到一个 loader 中。但是根据用法准则,已经存在这样一个 apply-loader
,可以将它和其他开源 loader 串联在一起调用。
pug-loader
: 导出一个函数,把模板转换为模块。apply-loader
: 根据 loader 选项执行函数,返回原生 HTML。html-loader
: 接收 HTML,输出一个合法的 JS 模块。保证输出模块化。loader 生成的模块与普通模块遵循相同的设计原则。
确保 loader 在不同模块转换之间不保存状态。每次运行都应该独立于其他编译模块以及相同模块之前的编译结果。
充分利用 loader-utils
包。它提供了许多有用的工具,但最常用的一种工具是获取传递给 loader 的选项。schema-utils
包配合 loader-utils
,用于保证 loader 选项,进行与 JSON Schema 结构一致的校验。这里有一个简单使用两者的例子:
loader.js
import { urlToRequest } from 'loader-utils';
import { validate } from 'schema-utils';
const schema = {
type: 'object',
properties: {
test: {
type: 'string',
},
},
};
export default function (source) {
const options = this.getOptions();
validate(schema, options, {
name: 'Example Loader',
baseDataPath: 'options',
});
console.log('The request path', urlToRequest(this.resourcePath));
// 对资源应用一些转换……
return `export default ${JSON.stringify(source)}`;
}
如果一个 loader 使用外部资源(例如,从文件系统读取),必须声明它。这些信息用于使缓存 loaders 无效,以及在观察模式(watch mode)下重编译。下面是一个简单示例,说明如何使用 addDependency
方法实现上述声明:
loader.js
import path from 'path';
export default function (source) {
var callback = this.async();
var headerPath = path.resolve('header.js');
this.addDependency(headerPath);
fs.readFile(headerPath, 'utf-8', function (err, header) {
if (err) return callback(err);
callback(null, header + '\n' + source);
});
}
根据模块类型,可能会有不同的模式指定依赖关系。例如在 CSS 中,使用 @import
和 url(...)
语句来声明依赖。这些依赖关系应该由模块系统解析。
可以通过以下两种方式中的一种来实现:
require
语句。this.resolve
函数解析路径。css-loader
是第一种方式的一个例子。它将 @import
语句替换为 require
其他样式文件,将 url(...)
替换为 require
引用文件,从而实现将依赖关系转化为 require
声明。
对于 less-loader
,无法将每个 @import
转化为 require
,因为所有 .less
的文件中的变量和混合跟踪必须一次编译。因此,less-loader
将 less
编译器进行了扩展,自定义路径解析逻辑。然后,利用第二种方式,通过 webpack 的 this.resolve
解析依赖。
避免在 loader 处理的每个模块中生成通用代码。相反,你应该在 loader 中创建一个运行时文件,并生成 require
语句以引用该共享模块:
src/loader-runtime.js
const { someOtherModule } = require('./some-other-module');
module.exports = function runtime(params) {
const x = params.y * 2;
return someOtherModule(params, x);
};
src/loader.js
import runtime from './loader-runtime.js';
export default function loader(source) {
// 自定义的 loader 逻辑
return `${runtime({
source,
y: Math.random(),
})}`;
}
不要在模块代码中插入绝对路径,因为当项目根路径变化时,文件绝对路径也会变化。loader-utils
中的 stringifyRequest
方法,可以将绝对路径转化为相对路径。
如果你的 loader 简单包裹另外一个包,你应该把这个包作为一个 peerDependency
引入。这种方式允许应用程序开发者在必要情况下,在 package.json
中指定所需的确定版本。
例如,sass-loader
指定 node-sass
作为同等依赖,引用如下:
{
"peerDependencies": {
"node-sass": "^4.0.0"
}
}
当你遵循上面的用法准则编写了一个 loader,并且可以在本地运行。下一步该做什么呢?让我们用一个简单的单元测试,来保证 loader 能够按照我们预期的方式正确运行。我们将使用 Jest 框架。然后还需要安装 babel-jest
和允许我们使用 import
/ export
和 async
/ await
的一些预设环境(presets)。让我们开始安装,并且将这些依赖保存为 devDependencies
:
npm install --save-dev jest babel-jest @babel/core @babel/preset-env
babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
};
我们的 loader 将会处理 .txt
文件,并且将任何实例中的 [name]
直接替换为 loader 选项中设置的 name
。然后返回包含默认导出文本的 JavaScript 模块:
src/loader.js
export default function loader(source) {
const options = this.getOptions();
source = source.replace(/\[name\]/g, options.name);
return `export default ${JSON.stringify(source)}`;
}
我们将会使用这个 loader 处理以下文件:
test/example.txt
Hey [name]!
请注意留心接下来的步骤,我们将会使用 Node.js API 和 memfs
去执行 webpack。这让我们避免向磁盘产生 输出文件
,并允许我们访问获取转换模块的统计数据 stats
:
npm install --save-dev webpack memfs
test/compiler.js
import path from 'path';
import webpack from 'webpack';
import { createFsFromVolume, Volume } from 'memfs';
export default (fixture, options = {}) => {
const compiler = webpack({
context: __dirname,
entry: `./${fixture}`,
output: {
path: path.resolve(__dirname),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.txt$/,
use: {
loader: path.resolve(__dirname, '../src/loader.js'),
options,
},
},
],
},
});
compiler.outputFileSystem = createFsFromVolume(new Volume());
compiler.outputFileSystem.join = path.join.bind(path);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (err) reject(err);
if (stats.hasErrors()) reject(stats.toJson().errors);
resolve(stats);
});
});
};
最后,我们来编写测试,并且添加 npm script 运行它:
test/loader.test.js
/**
* @jest-environment node
*/
import compiler from './compiler.js';
test('Inserts name and outputs JavaScript', async () => {
const stats = await compiler('example.txt', { name: 'Alice' });
const output = stats.toJson({ source: true }).modules[0].source;
expect(output).toBe('export default "Hey Alice!\\n"');
});
package.json
{
"scripts": {
"test": "jest"
},
"jest": {
"testEnvironment": "node"
}
}
准备就绪后,我们可以运行它,然后看新的 loader 是否能通过测试:
PASS test/loader.test.js
✓ Inserts name and outputs JavaScript (229ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.853s, estimated 2s
Ran all test suites.
一切正常!现在,你应该准备开始开发、测试、部署你的 loaders 了。我们希望你可以在社区分享你的 loader!
插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以在 webpack 构建流程中引入自定义的行为。创建插件比创建 loader 更加高级,因为你需要理解 webpack 底层的特性来处理相应的钩子,所以请做好阅读源码的准备!
webpack 插件由以下组成:
apply
方法。// 一个 JavaScript 类
class MyExampleWebpackPlugin {
// 在插件函数的 prototype 上定义一个 `apply` 方法,以 compiler 为参数。
apply(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子。
compiler.hooks.emit.tapAsync(
'MyExampleWebpackPlugin',
(compilation, callback) => {
console.log('这是一个示例插件!');
console.log(
'这里表示了资源的单次构建的 `compilation` 对象:',
compilation
);
// 用 webpack 提供的插件 API 处理构建过程
compilation.addModule(/* ... */);
callback();
}
);
}
}
插件是由「具有 apply
方法的 prototype 对象」所实例化出来的。这个 apply
方法在安装插件时,会被 webpack compiler 调用一次。apply
方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。一个插件结构如下:
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap(
'Hello World Plugin',
(
stats /* 绑定 done 钩子后,stats 会作为参数传入。 */
) => {
console.log('Hello World!');
}
);
}
}
module.exports = HelloWorldPlugin;
然后,要安装这个插件,只需要在你的 webpack 配置的 plugin 数组中添加一个实例:
// webpack.config.js
var HelloWorldPlugin = require('hello-world');
module.exports = {
// ... 这里是其他配置 ...
plugins: [new HelloWorldPlugin({ options: true })],
};
使用 schema-utils
来校验传入插件的选项。这里是个例子:
import { validate } from 'schema-utils';
// 选项对象的 schema
const schema = {
type: 'object',
properties: {
test: {
type: 'string',
},
},
};
export default class HelloWorldPlugin {
constructor(options = {}) {
validate(schema, options, {
name: 'Hello World Plugin',
baseDataPath: 'options',
});
}
apply(compiler) {}
}
在插件开发中最重要的两个资源就是 compiler
和 compilation
对象。理解它们的角色是扩展 webpack 引擎重要的第一步。
class HelloCompilationPlugin {
apply(compiler) {
// 指定一个挂载到 compilation 的钩子,回调函数的参数为 compilation 。
compiler.hooks.compilation.tap('HelloCompilationPlugin', (compilation) => {
// 现在可以通过 compilation 对象绑定各种钩子
compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
console.log('资源已经优化完毕。');
});
});
}
}
module.exports = HelloCompilationPlugin;
compiler
和 compilation
以及其他重要对象提供的钩子清单,请查看 plugins API 文档。
有些插件钩子是异步的。我们可以像同步方式一样用 tap
方法来绑定,也可以用 tapAsync
或 tapPromise
这两个异步方法来绑定。
当我们用 tapAsync
方法来绑定插件时,_必须_调用函数的最后一个参数 callback
指定的回调函数。
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync(
'HelloAsyncPlugin',
(compilation, callback) => {
// 执行某些异步操作...
setTimeout(function () {
console.log('异步任务完成...');
callback();
}, 1000);
}
);
}
}
module.exports = HelloAsyncPlugin;
当我们用 tapPromise
方法来绑定插件时,_必须_返回一个 pormise ,异步任务完成后 resolve 。
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapPromise('HelloAsyncPlugin', (compilation) => {
// 返回一个 pormise ,异步任务完成后 resolve
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log('异步任务完成...');
resolve();
}, 1000);
});
});
}
}
module.exports = HelloAsyncPlugin;
一旦我们可以深入理解 webpack compiler 和每个独立的 compilation,我们依赖 webpack 引擎将有无限多的事可以做。我们可以重新格式化已有的文件,创建衍生的文件,或者制作全新的生成文件。
让我们来写一个简单的示例插件,生成一个叫做 assets.md
的新文件;文件内容是所有构建生成的文件的列表。这个插件大概像下面这样:
class FileListPlugin {
static defaultOptions = {
outputFile: 'assets.md',
};
// 需要传入自定义插件构造函数的任意选项
//(这是自定义插件的公开API)
constructor(options = {}) {
// 在应用默认选项前,先应用用户指定选项
// 合并后的选项暴露给插件方法
// 记得在这里校验所有选项
this.options = { ...FileListPlugin.defaultOptions, ...options };
}
apply(compiler) {
const pluginName = FileListPlugin.name;
// webpack 模块实例,可以通过 compiler 对象访问,
// 这样确保使用的是模块的正确版本
// (不要直接 require/import webpack)
const { webpack } = compiler;
// Compilation 对象提供了对一些有用常量的访问。
const { Compilation } = webpack;
// RawSource 是其中一种 “源码”("sources") 类型,
// 用来在 compilation 中表示资源的源码
const { RawSource } = webpack.sources;
// 绑定到 “thisCompilation” 钩子,
// 以便进一步绑定到 compilation 过程更早期的阶段
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
// 绑定到资源处理流水线(assets processing pipeline)
compilation.hooks.processAssets.tap(
{
name: pluginName,
// 用某个靠后的资源处理阶段,
// 确保所有资源已被插件添加到 compilation
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
(assets) => {
// "assets" 是一个包含 compilation 中所有资源(assets)的对象。
// 该对象的键是资源的路径,
// 值是文件的源码
// 遍历所有资源,
// 生成 Markdown 文件的内容
const content =
'# In this build:\n\n' +
Object.keys(assets)
.map((filename) => `- ${filename}`)
.join('\n');
// 向 compilation 添加新的资源,
// 这样 webpack 就会自动生成并输出到 output 目录
compilation.emitAsset(
this.options.outputFile,
new RawSource(content)
);
}
);
});
}
}
module.exports = { FileListPlugin };
webpack.config.js
const { FileListPlugin } = require('./file-list-plugin.js');
// 在 webpack 配置中使用自定义的插件:
module.exports = {
// …
plugins: [
// 添加插件,使用默认选项
new FileListPlugin(),
// 或者:
// 使用任意支持的选项
new FileListPlugin({
outputFile: 'my-assets.md',
}),
],
};
这样就生成了一个制定名称的 markdown 文件:
# In this build:
- main.css
- main.js
- index.html
webpack 插件可以按照它所注册的事件分成不同的类型。每一个事件钩子都预先定义为同步、异步、瀑布或并行钩子,钩子在内部用 call/callAsync 方法调用。支持的钩子清单或可被绑定的钩子清单,通常在 this.hooks
属性指定。
例如:
this.hooks = {
shouldEmit: new SyncBailHook(['compilation']),
};
表示唯一支持的钩子是 shouldEmit
,这是一个 SyncBailHook
类型的钩子,传入插件的唯一参数是 compilation
。
支持的各类型钩子:
SyncHook
new SyncHook([params])
定义。tap
方法绑定。call(...params)
方法调用。Bail(保险) Hooks
new SyncBailHook([params])
定义。tap
方法绑定。call(...params)
方法调用。Bail类型的钩子,其插件回调函数是串行调用,任意一个插件回调函数返回非 undefined 值,则停止执行插件,该值作为钩子的返回值。optimizeChunks
,optimizeChunkModules
等很有用的事件都是 SyncBailHooks 。
Waterfall(瀑布) Hooks
new SyncWaterfallHook([params])
定义。tap
方法绑定。call(...params)
方法调用。该类型的插件是一个接一个串行执行,前一个的返回值作为后一个的入参。插件需要考虑执行的顺序,因为后一个插件必须接受前一个插件执行的结果作为入参。第一个插件的参数为 init
。
所以 waterfall 钩子至少要制定一个参数。这种模式用于和 ModuleTemplate
,ChunkTemplate
等 webpack 模板相关的 Tapable 实例。
Async Series(异步串联) Hook
new AsyncSeriesHook([params])
定义。tap
/tapAsync
/tapPromise
方法绑定。callAsync(...params)
方法调用。插件处理函数(handler functions)的参数为所有参数,以及一个签名为 (err?: Error) -> void
的 callback 函数,callback 函数的处理函数按注册顺序执行。callback
在所有处理函数执行完后调用。
这是 emit
和 run
事件的常见使用模式。
Async waterfall(异步瀑布) 插件会用 waterfall 方式异步应用
new AsyncWaterfallHook([params])
定义。tap
/tapAsync
/tapPromise
方法绑定。callAsync(...params)
方法调用。插件处理函数的参数为当前值,以及一个签名为 (err: Error, nextValue: any) -> void
的 callback 函数。调用时 nextValue
是下一个处理函数的当前值。第一个处理函数的当前是只 init
。当所有 handler 都执行后,callback执行,参数为最后一个值。任一个 handler 传入 err
值,停止调用 handler 且 callback 被调用。
这种模式在 before-resolve
和 after-resolve
事件中常见。
Async Series Bail
new AsyncSeriesBailHook([params])
定义。tap
/tapAsync
/tapPromise
方法绑定。callAsync(...params)
方法调用。Async Parallel
new AsyncParallelHook([params])
定义。tap
/tapAsync
/tapPromise
方法绑定。callAsync(...params)
方法调用。Webpack 先应用插件的默认配置,再应用自身的默认配置。这样可以让插件拥有自己的默认配置,同时支持实现创建预设配置的插件。
Plugins grant unlimited opportunity to perform customizations within the webpack build system. This allows you to create custom asset types, perform unique build modifications, or even enhance the webpack runtime while using middleware. The following are some features of webpack that become useful while writing plugins.
After a compilation is sealed, all structures within the compilation may be traversed.
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// Explore each chunk (build output):
compilation.chunks.forEach((chunk) => {
// Explore each module within the chunk (built inputs):
chunk.getModules().forEach((module) => {
// Explore each source file path that was included into the module:
module.buildInfo &&
module.buildInfo.fileDependencies &&
module.buildInfo.fileDependencies.forEach((filepath) => {
// we've learned a lot about the source structure now...
});
});
// Explore each asset filename generated by the chunk:
chunk.files.forEach((filename) => {
// Get the asset source for each file generated by the chunk:
var source = compilation.assets[filename].source();
});
});
callback();
});
}
}
module.exports = MyPlugin;
compilation.modules
: A set of modules (built inputs) in the compilation. Each module manages the build of a raw file from your source library.module.fileDependencies
: An array of source file paths included into a module. This includes the source JavaScript file itself (ex: index.js
), and all dependency asset files (stylesheets, images, etc) that it has required. Reviewing dependencies is useful for seeing what source files belong to a module.compilation.chunks
: A set of chunks (build outputs) in the compilation. Each chunk manages the composition of a final rendered assets.chunk.getModules()
: An array of modules that are included into a chunk. By extension, you may look through each module's dependencies to see what raw source files fed into a chunk.chunk.files
: A Set of output filenames generated by the chunk. You may access these asset sources from the compilation.assets
table.While running webpack middleware, each compilation includes a fileDependencies
Set
(what files are being watched) and a fileTimestamps
Map
that maps watched file paths to a timestamp. These are extremely useful for detecting what files have changed within the compilation:
class MyPlugin {
constructor() {
this.startTime = Date.now();
this.prevTimestamps = new Map();
}
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
const changedFiles = Array.from(compilation.fileTimestamps.keys()).filter(
(watchfile) => {
return (
(this.prevTimestamps.get(watchfile) || this.startTime) <
(compilation.fileTimestamps.get(watchfile) || Infinity)
);
}
);
this.prevTimestamps = compilation.fileTimestamps;
callback();
});
}
}
module.exports = MyPlugin;
You may also feed new file paths into the watch graph to receive compilation triggers when those files change. Add valid file paths into the compilation.fileDependencies
Set
to add them to the watched files.
Similar to the watch graph, you can monitor changed chunks (or modules, for that matter) within a compilation by tracking their hashes.
class MyPlugin {
constructor() {
this.chunkVersions = {};
}
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
var changedChunks = compilation.chunks.filter((chunk) => {
var oldVersion = this.chunkVersions[chunk.name];
this.chunkVersions[chunk.name] = chunk.hash;
return chunk.hash !== oldVersion;
});
callback();
});
}
}
module.exports = MyPlugin;
The release process for deploying webpack is actually quite painless. Read through the following steps, so you have a clear understanding of how it's done.
When merging pull requests into the main
branch, select the Create Merge Commit option.
npm version patch && git push --follow-tags && npm publish
npm version minor && git push --follow-tags && npm publish
npm version major && git push --follow-tags && npm publish
This will increment the package version, commits the changes, cuts a local tag, push to github & publish the npm package.
After that go to the github releases page and write a Changelog for the new tag.
When contributing to the core repo, writing a loader/plugin, or even working on a complex project, debugging tools can be central to your workflow. Whether the problem is slow performance on a large project or an unhelpful traceback, the following utilities can make figuring it out less painful.
stats
data made available through Node and the CLI.node-nightly
and the latest Node.js versions.Whether you want to sift through this data manually or use a tool to process it, the stats
data can be extremely useful when debugging build issues. We won't go in depth here as there's an entire page dedicated to its contents, but know that you can use it to find the following information:
On top of that, the official analyze tool and various others will accept this data and visualize it in various ways.
While console
statements may work well in straightforward scenarios, sometimes a more robust solution is needed. As most front-end developers already know, Chrome DevTools are a life saver when debugging web applications, but they don’t have to stop there. As of Node v6.3.0+, developers can use the built-in --inspect
flag to debug a node program in DevTools.
This gives you the power to easily create breakpoints, debug memory usage, expose and examine objects in the console, and much more. In this short demo, we'll utilize the node-nightly
package which provides access to the latest and greatest inspecting capabilities.
Let's start by installing it globally:
npm install --global node-nightly
Now, we'll need to run it once to finish the installation:
node-nightly
Now, we can use node-nightly
along with the --inspect
flag to start our build in any webpack-based project. Note that we cannot run NPM scripts
, e.g. npm run build
, so we'll have to specify the full node_modules
path:
node-nightly --inspect ./node_modules/webpack/bin/webpack.js
Which should output something like:
Debugger listening on ws://127.0.0.1:9229/c624201a-250f-416e-a018-300bbec7be2c
For help see https://nodejs.org/en/docs/inspector
Now jump to chrome://inspect
in the browser and you should see any active scripts you've inspected under the Remote Target header. Click the "inspect" link under each script to open a dedicated debugger or the Open dedicated DevTools for Node link for a session that will connect automatically. You can also check out the NiM extension, a handy Chrome plugin that will automatically open a DevTools tab every time you --inspect
a script.
We recommend using the --inspect-brk
flag which will break on the first statement of the script allowing you to go through the source to set breakpoints and start/stop the build as you please. Also, don't forget that you can still pass arguments to the script. For example, if you have multiple configuration files you could pass --config webpack.prod.js
to specify the configuration you'd like to debug.