为 webpack 贡献的人是出于对开源、用户和生态系统的热爱,最重要的是,为了共同推动 Web 的发展。由于我们的 Open Collective 资金和透明度模型,我们能够通过贡献者、依赖项目以及贡献者和核心团队来提供支持和资金。要捐款,请点击下面的按钮...
但投资的回报是什么?
我们希望提供的最大核心功能是愉快的开发体验。像您这样的开发者可以通过贡献丰富而充满活力的文档、提交拉取请求来帮助我们覆盖小众用例,并帮助维护您所喜爱的 webpack。
任何人都可以通过以下任何一种方式提供帮助:
您可以要求您的雇主利用 webpack 来改善您的工作流程:它是一个用于字体、图像和图像优化以及 JSON 的一体化工具。向他们解释 webpack 将如何尽力捆绑您的代码和资源以实现最小的文件大小,从而带来更快的网站和应用程序。
为 webpack 贡献并非加入一个排他性俱乐部。作为开发者,您正在为下游项目的整体健康做出贡献。成百上千的项目依赖于 webpack,贡献将使整个生态系统对所有用户都更好。
本网站的其余部分致力于像您这样希望成为我们不断壮大的社区一员的开发者。
CTO、副总裁和所有者也能提供帮助!
Webpack 是一个用于捆绑代码的一体化工具。在社区驱动的插件和加载器的帮助下,它可以处理字体、图像、数据等等。所有资源由一个工具处理会非常有帮助,因为您或您的团队可以花费更少的时间确保一个包含许多运动部件的机器正常工作,而将更多时间用于构建您的产品。
除了经济援助,公司还可以通过以下方式支持 webpack:
您还可以鼓励您的开发者通过开源 webpack 加载器、插件和其他实用工具来为生态系统做出贡献。而且,如上所述,我们非常感谢您在增加 CI/CD 基础设施方面的任何帮助。
对于任何其他有兴趣帮助我们完成使命的人——例如风险投资家、政府实体、数字机构等——我们非常乐意与您合作,作为顶级的 npm 包之一,共同改进您的产品!请随时提出问题。
正在寻找帮助,有问题,或者想与其他 webpack 贡献者联系?
加入我们的 Discord 服务器,获取实时支持、讨论和协作。
webpack 功能和变更的文档现在正随着 webpack 的演进而更新。自动化问题创建集成已建立,并在近年证明有效。当一个功能合并后,我们仓库中会创建一个带有文档请求的问题,我们期望及时解决它。这意味着有一些功能、变更和破坏性变更正在等待文档、审查和发布。话虽如此,如果拉取请求的作者在30天以上放弃它,我们可能会将此拉取请求标记为过时。我们可能会接管完成它所需的工作。如果拉取请求作者授予 webpack 文档团队对其 fork 的写入权限,我们将直接提交到您的分支并完成工作。在其他情况下,我们可能需要自行重新开始或委托给愿意的社区成员。这可能会使您的拉取请求变得多余,并可能在清理过程中被关闭。
以下部分包含您编辑和格式化本网站内容所需了解的一切。在开始编辑或添加之前,请务必进行一些研究。有时最困难的部分是找到内容应该放在哪里,并确定它是否已经存在。
编辑
并扩展结构。每篇文章顶部都包含一小段用 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]
---
让我们来分解这些:
title
: 文章的名称。group
: 子部分的名称sort
: 文章在其所在部分(或)子部分(如果存在)中的顺序。contributors
: 为本文做出贡献的 GitHub 用户名列表。related
: 任何相关阅读或有用示例。请注意,related
将在页面底部生成一个**进一步阅读**部分,而 contributors
将在其下方生成一个**贡献者**部分。如果您编辑了一篇文章并希望获得认可,请不要犹豫,将您的 GitHub 用户名添加到 contributors
列表中。
css-loader
, ts-loader
, …BannerPlugin
, NpmInstallWebpackPlugin
, …语法:```javascript … ```
function foo() {
return 'bar';
}
foo();
在代码片段和项目文件(如.jsx
、.scss
等)中使用单引号
- import webpack from "webpack";
+ import webpack from 'webpack';
以及内联反引号中
正确
将值设置为 'index.md'
...
错误
将值设置为 "index.md"
...
列表应按字母顺序排列。
参数 | 解释 | 输入类型 | 默认值 |
---|---|---|---|
--debug | 将加载器切换到调试模式 | boolean | false |
--devtool | 为捆绑资源定义源映射类型 | string | - |
--progress | 以百分比显示编译进度 | boolean | false |
表格也应按字母顺序排列。
配置属性也应按字母顺序排列
devServer.compress
devServer.hot
devServer.static
语法:>
这是一个块引用。
语法:T>
语法:W>
语法:?>
编写文档时不要做假设。
- You might already know how to optimize bundle for production...
+ As we've learned in [production guide](/guides/production/)...
请不要假设事情很简单。避免使用“仅仅”、“简单地”之类的词语。
- Simply run command...
+ Run the `command-name` command...
始终为所有文档选项提供类型和默认值,以保持文档的可访问性和良好的编写质量。我们在指定文档选项后添加类型和默认值
configuration.example.option
string = 'none'
其中 = 'none'
表示给定选项的默认值为 'none'
。
string = 'none': 'none' | 'development' | 'production'
其中 : 'none' | 'development' | 'production'
列举了可能的类型值,在这种情况下,接受三种字符串:'none'
、'development'
和 'production'
。
在类型之间使用空格列出给定选项的所有可用类型
string = 'none': 'none' | 'development' | 'production'
boolean
要标记一个数组,请使用方括号
string
[string]
如果数组中允许有多种类型,请使用逗号
string
[string, RegExp, function(arg) => string]
要标记一个函数,如果可用,也要列出参数
function (compilation, module, path) => boolean
其中 (compilation, module, path)
列出了所提供的函数将接收的参数,而 => boolean
表示函数的返回值必须是 boolean
。
要将插件标记为可用选项值类型,请使用该 Plugin
的驼峰命名法标题
TerserPlugin
[TerserPlugin]
这意味着该选项期望一个或几个 TerserPlugin
实例。
要标记一个数字,请使用 number
number = 15: 5, 15, 30
要标记一个对象,请使用 object
object = { prop1 string = 'none': 'none' | 'development' | 'production', prop2 boolean = false, prop3 function (module) => string }
当对象的键可以有多种类型时,使用 |
来列出它们。以下是一个示例,其中 prop1
可以是字符串,也可以是字符串数组
object = { prop1 string = 'none': 'none' | 'development' | 'production' | [string]}
这使我们能够显示默认值、枚举和其他信息。
如果对象的键是动态的、用户定义的,请使用 <key>
来描述它
object = { <key> string }
有时,我们希望在列表中描述对象和函数的某些属性。在适用情况下,直接将类型添加到列出属性的列表中
madeUp
(boolean = true
):简短描述shortText
(string = 'i am text'
):另一个简短描述一个示例可以在 EvalSourceMapDevToolPlugin
页面的 options
部分找到。
请使用相对 URL(例如 /concepts/mode/
)链接我们自己的内容,而不是绝对 URL(例如 https://webpack.js.cn/concepts/mode/
)。
加载器是一个导出函数的 Node 模块。当资源应由该加载器转换时,会调用此函数。给定函数将能够使用提供给它的 this
上下文访问 加载器 API。
在我们深入了解不同类型的加载器、它们的用法和示例之前,让我们先看看在本地开发和测试加载器的三种方式。
要测试单个加载器,您可以使用 path
在规则对象中 resolve
一个本地文件
webpack.config.js
const path = require('path');
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {
/* ... */
},
},
],
},
],
},
};
要测试多个,您可以利用 resolveLoader.modules
配置来更新 webpack 搜索加载器的位置。例如,如果您的项目中有本地的 /loaders
目录
webpack.config.js
const path = require('path');
module.exports = {
//...
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')],
},
};
顺便说一句,如果您已经为您的加载器创建了一个单独的仓库和包,您可以将它 npm link
到您想要测试的项目中。
当单个加载器应用于资源时,加载器只带有一个参数被调用——一个包含资源文件内容的字符串。
同步加载器可以 返回
一个表示转换后模块的单一值。在更复杂的情况下,加载器可以使用 this.callback(err, values...)
函数返回任意数量的值。错误要么传递给 this.callback
函数,要么在同步加载器中抛出。
加载器预期返回一个或两个值。第一个值是作为字符串或缓冲区的生成的 JavaScript 代码。第二个可选值是作为 JavaScript 对象的 SourceMap。
当多个加载器串联时,重要的是要记住它们是逆序执行的——根据数组格式,要么从右到左,要么从下到上。
在以下示例中,foo-loader
将接收原始资源,而 bar-loader
将接收 foo-loader
的输出,并在必要时返回最终转换后的模块和源映射。
webpack.config.js
module.exports = {
//...
module: {
rules: [
{
test: /\.js/,
use: ['bar-loader', 'foo-loader'],
},
],
},
};
编写加载器时应遵循以下指南。它们按重要性排序,有些仅适用于特定场景,请阅读后续详细部分以获取更多信息。
加载器应该只执行一个任务。这不仅使维护每个加载器的工作更容易,而且还允许它们在更多场景中进行链式调用。
利用加载器可以串联在一起的事实。不要编写一个处理五个任务的单个加载器,而是编写五个更简单的加载器来分担这项工作。将它们隔离不仅可以使每个单独的加载器保持简单,而且可能允许它们用于您最初没有想到的用途。
以通过加载器选项或查询参数指定数据来渲染模板文件为例。它可以编写为一个单独的加载器,从源编译模板,执行它并返回一个导出包含 HTML 代码字符串的模块。然而,根据指南,存在一个 apply-loader
可以与其他开源加载器串联使用
pug-loader
: 将模板转换为导出函数的模块。apply-loader
: 使用加载器选项执行函数并返回原始 HTML。html-loader
: 接受 HTML 并输出有效的 JavaScript 模块。保持输出模块化。加载器生成的模块应遵循与普通模块相同的设计原则。
确保加载器在模块转换之间不保留状态。每次运行都应始终独立于其他编译模块以及同一模块的先前编译。
利用 loader-utils
包,它提供了各种有用的工具。除了 loader-utils
,还应该使用 schema-utils
包来进行基于 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));
// Apply some transformations to the source...
return `export default ${JSON.stringify(source)}`;
}
在 webpack 中,加载器可以链式调用并与链中的后续加载器共享数据。为此,您可以使用原始加载器中的 this.callback
方法随内容(源代码)传递数据。在原始加载器的默认导出函数中,您可以使用 this.callback
的第四个参数传递数据。
export default function (source) {
const options = getOptions(this);
// Pass data using the fourth argument of this.callback
this.callback(null, `export default ${JSON.stringify(source)}`, null, {
some: data,
});
}
在上面的示例中,this.callback
第四个参数中的 some
属性用于将数据传递给下一个链式加载器。
如果加载器使用外部资源(即通过从文件系统读取),它们**必须**指明。此信息用于在观察模式下使可缓存加载器失效并重新编译。这是一个如何使用 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);
});
}
根据模块的类型,可能使用不同的Schema来指定依赖。例如在 CSS 中,使用 @import
和 url(...)
语句。这些依赖应该由模块系统解析。
这可以通过两种方式完成:
require
语句。this.resolve
函数解析路径。css-loader
是第一种方法的一个很好的例子。它通过将 @import
语句替换为对其他样式表的 require
,并将 url(...)
替换为对引用文件的 require
,从而将依赖转换为 require
。
在 less-loader
的情况下,它不能将每个 @import
转换为 require
,因为所有 .less
文件必须一次性编译以进行变量和混合跟踪。因此,less-loader
通过自定义路径解析逻辑扩展了 less 编译器。然后它利用第二种方法,this.resolve
,通过 webpack 解析依赖。
避免在加载器处理的每个模块中生成通用代码。相反,在加载器中创建一个运行时文件,并生成对该共享模块的 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) {
// Custom loader logic
return `${runtime({
source,
y: Math.random(),
})}`;
}
不要将绝对路径插入到模块代码中,因为当项目根目录移动时,它们会破坏哈希。您可以使用以下代码将绝对路径转换为相对路径。
// `loaderContext` is same as `this` inside loader function
JSON.stringify(
loaderContext.utils.contextify(
loaderContext.context || loaderContext.rootContext,
request
)
);
如果您正在开发的加载器只是另一个包的简单包装,那么您应该将该包作为 peerDependency
包含进来。这种方法允许应用程序的开发者根据需要指定 package.json
中的确切版本。
例如,sass-loader
将 node-sass
指定为对等依赖,如下所示
{
"peerDependencies": {
"node-sass": "^4.0.0"
}
}
所以您已经编写了一个加载器,遵循了上面的指南,并将其设置为在本地运行。接下来是什么?我们来看一个单元测试示例,以确保我们的加载器按预期工作。我们将使用 Jest 框架来完成此操作。我们还将安装 babel-jest
和一些预设,这将允许我们使用 import
/ export
和 async
/ await
。让我们首先安装并将它们保存为 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',
},
},
],
],
};
我们的加载器将处理 .txt
文件,并将 [name]
的任何实例替换为提供给加载器的 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)}`;
}
我们将使用此加载器处理以下文件
test/example.txt
Hey [name]!
请密切注意下一步,因为我们将使用 Node.js API 和 memfs
来执行 webpack。这使我们避免将 output
发送到磁盘,并且将使我们能够访问 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 脚本来运行它
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"
}
}
一切就绪后,我们可以运行它,看看我们的新加载器是否通过了测试
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.
它成功了!至此,您应该已经准备好开始开发、测试和部署您自己的加载器。我们希望您能与社区的其他人分享您的创作!
插件将 webpack 引擎的全部潜力暴露给第三方开发者。通过使用分阶段构建回调,开发者可以将自己的行为引入 webpack 构建过程。构建插件比构建加载器更高级一些,因为您需要了解一些 webpack 的底层内部机制才能进行钩入。准备好阅读一些源代码吧!
一个 webpack 插件由以下部分组成:
apply
方法。// A JavaScript class.
class MyExampleWebpackPlugin {
// Define `apply` as its prototype method which is supplied with compiler as its argument
apply(compiler) {
// Specify the event hook to attach to
compiler.hooks.emit.tapAsync(
'MyExampleWebpackPlugin',
(compilation, callback) => {
console.log('This is an example plugin!');
console.log(
'Here’s the `compilation` object which represents a single build of assets:',
compilation
);
// Manipulate the build using the plugin API provided by webpack
compilation.addModule(/* ... */);
callback();
}
);
}
}
插件是具有 apply
方法的实例化对象,该方法在其原型上。在安装插件时,webpack 编译器会调用此 apply
方法一次。apply
方法被赋予底层 webpack 编译器的引用,从而可以访问编译器回调。插件的结构如下:
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap(
'Hello World Plugin',
(
stats /* stats is passed as an argument when done hook is tapped. */
) => {
console.log('Hello World!');
}
);
}
}
module.exports = HelloWorldPlugin;
然后要使用插件,请在您的 webpack 配置 plugins
数组中包含一个实例
// webpack.config.js
var HelloWorldPlugin = require('hello-world');
module.exports = {
// ... configuration settings here ...
plugins: [new HelloWorldPlugin({ options: true })],
};
使用 schema-utils
来验证通过插件选项传递的选项。这是一个例子
import { validate } from 'schema-utils';
// schema for options object
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) {
// Tap into compilation hook which gives compilation as argument to the callback function
compiler.hooks.compilation.tap('HelloCompilationPlugin', (compilation) => {
// Now we can tap into various hooks available through compilation
compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
console.log('Assets are being optimized.');
});
});
}
}
module.exports = HelloCompilationPlugin;
有关 compiler
、compilation
和其他重要对象上可用的钩子列表,请参阅插件 API 文档。
一些插件钩子是异步的。要钩入它们,我们可以使用 tap
方法,它将以同步方式运行,或者使用 tapAsync
方法或 tapPromise
方法,它们是异步方法。
当我们使用 tapAsync
方法钩入插件时,我们需要调用作为函数最后一个参数提供的回调函数。
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync(
'HelloAsyncPlugin',
(compilation, callback) => {
// Do something async...
setTimeout(function () {
console.log('Done with async work...');
callback();
}, 1000);
}
);
}
}
module.exports = HelloAsyncPlugin;
当我们使用 tapPromise
方法钩入插件时,我们需要返回一个 Promise,该 Promise 在异步任务完成后解决。
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapPromise('HelloAsyncPlugin', (compilation) => {
// return a Promise that resolves when we are done...
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log('Done with async work...');
resolve();
}, 1000);
});
});
}
}
module.exports = HelloAsyncPlugin;
一旦我们能够连接到 webpack 编译器和每个单独的编译,我们就可以用引擎本身做无限的事情。我们可以重新格式化现有文件,创建派生文件,或者完全创建新的资源。
让我们编写一个示例插件,它生成一个名为 assets.md
的新构建文件,其内容将列出我们构建中的所有资源文件。这个插件可能看起来像这样
class FileListPlugin {
static defaultOptions = {
outputFile: 'assets.md',
};
// Any options should be passed in the constructor of your plugin,
// (this is a public API of your plugin).
constructor(options = {}) {
// Applying user-specified options over the default options
// and making merged options further available to the plugin methods.
// You should probably validate all the options here as well.
this.options = { ...FileListPlugin.defaultOptions, ...options };
}
apply(compiler) {
const pluginName = FileListPlugin.name;
// webpack module instance can be accessed from the compiler object,
// this ensures that correct version of the module is used
// (do not require/import the webpack or any symbols from it directly).
const { webpack } = compiler;
// Compilation object gives us reference to some useful constants.
const { Compilation } = webpack;
// RawSource is one of the "sources" classes that should be used
// to represent asset sources in compilation.
const { RawSource } = webpack.sources;
// Tapping to the "thisCompilation" hook in order to further tap
// to the compilation process on an earlier stage.
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
// Tapping to the assets processing pipeline on a specific stage.
compilation.hooks.processAssets.tap(
{
name: pluginName,
// Using one of the later asset processing stages to ensure
// that all assets were already added to the compilation by other plugins.
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
(assets) => {
// "assets" is an object that contains all assets
// in the compilation, the keys of the object are pathnames of the assets
// and the values are file sources.
// Iterating over all the assets and
// generating content for our Markdown file.
const content =
'# In this build:\n\n' +
Object.keys(assets)
.map((filename) => `- ${filename}`)
.join('\n');
// Adding new asset to the compilation, so it would be automatically
// generated by the webpack in the output directory.
compilation.emitAsset(
this.options.outputFile,
new RawSource(content)
);
}
);
});
}
}
module.exports = { FileListPlugin };
webpack.config.js
const { FileListPlugin } = require('./file-list-plugin.js');
// Use the plugin in your webpack configuration:
module.exports = {
// …
plugins: [
// Adding the plugin with the default options
new FileListPlugin(),
// OR:
// You can choose to pass any supported options to it:
new FileListPlugin({
outputFile: 'my-assets.md',
}),
],
};
这将生成一个选定名称的 markdown 文件,内容如下所示
# In this build:
- main.css
- main.js
- index.html
插件可以根据其钩入的事件钩子进行分类。每个事件钩子都被预定义为同步或异步,或瀑布式或并行钩子,并使用 call/callAsync 方法在内部调用钩子。支持或可以钩入的钩子列表通常在 this.hooks
属性中指定。
例如
this.hooks = {
shouldEmit: new SyncBailHook(['compilation']),
};
它表示唯一支持的钩子是 shouldEmit
,它是一个 SyncBailHook
类型钩子,并且将传递给钩入 shouldEmit
钩子的任何插件的唯一参数是 compilation
。
支持的各种钩子类型有:
SyncHook
new SyncHook([params])
tap
方法钩入。call(...params)
方法调用。Bail Hooks
SyncBailHook[params]
定义tap
方法钩入。call(...params)
方法调用。在这种类型的钩子中,每个插件回调将按特定 args
依次调用。如果任何插件返回除 undefined 之外的任何值,则该值将由钩子返回,并且不再调用后续插件回调。许多有用的事件,如 optimizeChunks
、optimizeChunkModules
都是 SyncBailHooks。
Waterfall Hooks
SyncWaterfallHook[params]
定义tap
方法钩入。call(...params)
方法调用这里每个插件都会依次被调用,并带有前一个插件返回值的参数。插件必须考虑其执行顺序。它必须接受来自前一个执行插件的参数。第一个插件的值是 init
。因此,瀑布钩子至少必须提供 1 个参数。此模式用于与 webpack 模板(如 ModuleTemplate
、ChunkTemplate
等)相关的 Tapable 实例。
Async Series Hook
AsyncSeriesHook[params]
定义tap
/tapAsync
/tapPromise
方法钩入。callAsync(...params)
方法调用插件处理函数被调用,带有所有参数和签名 (err?: Error) -> void
的回调函数。处理函数按注册顺序调用。callback
在所有处理函数都被调用后调用。这也是 emit
、run
等事件常用的模式。
异步瀑布 插件将以异步瀑布方式应用。
AsyncWaterfallHook[params]
定义tap
/tapAsync
/tapPromise
方法钩入。callAsync(...params)
方法调用插件处理函数被调用,带有当前值和签名 (err: Error, nextValue: any) -> void.
的回调函数。当被调用时,nextValue
是下一个处理函数的当前值。第一个处理函数的当前值是 init
。所有处理函数应用后,回调函数将使用最后一个值被调用。如果任何处理函数为 err
传递了值,则回调函数将使用此错误被调用,并且不再调用其他处理函数。此插件模式预期用于 before-resolve
和 after-resolve
等事件。
Async Series Bail
AsyncSeriesBailHook[params]
定义tap
/tapAsync
/tapPromise
方法钩入。callAsync(...params)
方法调用Async Parallel
AsyncParallelHook[params]
定义tap
/tapAsync
/tapPromise
方法钩入。callAsync(...params)
方法调用webpack 在插件默认值应用后应用配置默认值。这允许插件拥有自己的默认值,并提供了一种创建配置预设插件的方式。
插件提供了在 webpack 构建系统中执行自定义的无限机会。这使您可以创建自定义资源类型、执行独特的构建修改,甚至在使用中间件时增强 webpack 运行时。以下是编写插件时变得有用的一些 webpack 功能。
编译密封后,可以遍历编译中的所有结构。
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
: 编译中模块(构建输入)的集合。每个模块管理源库中原始文件的构建。module.fileDependencies
: 包含在模块中的源文件路径数组。这包括源 JavaScript 文件本身(例如:index.js
),以及它所要求的全部依赖资源文件(样式表、图像等)。查看依赖关系有助于查看哪些源文件属于一个模块。compilation.chunks
: 编译中代码块(构建输出)的集合。每个代码块管理最终渲染资源的组成。chunk.getModules()
: 包含在代码块中的模块数组。通过扩展,您可以查看每个模块的依赖项,以了解哪些原始源文件流入了代码块。chunk.files
: 由代码块生成的文件名集合(Set)。您可以从 compilation.assets
表中访问这些资源源。在运行 webpack 中间件时,每个编译都包含一个 fileDependencies
Set
(正在监听的文件)和一个 fileTimestamps
Map
,它将监听的文件路径映射到时间戳。这些对于检测编译中哪些文件已更改非常有用
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;
您还可以将新的文件路径输入到监听图中,以便在这些文件更改时接收编译触发。将有效文件路径添加到 compilation.fileDependencies
Set
中,以将它们添加到监听文件。
与监听图类似,您可以通过跟踪代码块(或模块)的哈希值来监控编译中已更改的代码块(或模块)。
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;
部署 webpack 的发布过程实际上相当简单。请通读以下步骤,以便您清楚了解它是如何完成的。
将拉取请求合并到 main
分支时,请选择**创建合并提交**选项。
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
这将增加包版本,提交更改,创建一个**本地标签**,推送到 GitHub 并发布 npm 包。
之后,前往 GitHub 发布页面,为新标签编写更新日志。
在为核心仓库贡献、编写加载器/插件,甚至处理复杂项目时,调试工具可能成为您工作流程的核心。无论问题是大型项目上的性能缓慢还是无用的堆栈跟踪,以下实用工具都可以使解决问题变得不那么痛苦。
无论您是想手动筛选这些数据,还是使用工具进行处理,stats
数据在调试构建问题时都非常有用。我们在此不深入探讨,因为有一个专门的页面详细介绍了其内容,但请注意,您可以使用它来查找以下信息
最重要的是,官方的 分析工具 和 其他各种工具 将接受这些数据并以各种方式将其可视化。
虽然 console
语句在简单场景下可能效果很好,但有时需要更强大的解决方案。正如大多数前端开发者已经知道的那样,Chrome DevTools 在调试 Web 应用程序时是救星,但它们不应该止步于此。从 Node v6.3.0+ 开始,开发者可以使用内置的 --inspect
标志在 DevTools 中调试 Node 程序。
让我们首先使用 node --inspect
调用 webpack。
请注意,我们不能运行 npm scripts
,例如 npm run build
,所以我们必须指定完整的 node_modules
路径
node --inspect ./node_modules/webpack/bin/webpack.js
这应该会输出类似以下内容:
Debugger listening on ws://127.0.0.1:9229/c624201a-250f-416e-a018-300bbec7be2c
For help see https://node.org.cn/en/docs/inspector
现在跳转到浏览器中的 chrome://inspect
,您应该在**远程目标**标题下看到您已检查的任何活动脚本。单击每个脚本下的“检查”链接以打开专用调试器,或单击**为 Node 打开专用 DevTools**链接以连接自动连接的会话。您还可以查看 NiM 扩展,这是一个方便的 Chrome 插件,每次您使用 --inspect
脚本时都会自动打开一个 DevTools 标签页。
我们建议使用 --inspect-brk
标志,它会在脚本的第一条语句处中断,允许您浏览源代码以设置断点并根据需要启动/停止构建。此外,不要忘记您仍然可以向脚本传递参数。例如,如果您有多个配置文件,您可以传递 --config webpack.prod.js
来指定您想要调试的配置。