创建一个 plugin
webpack 的插件组成有以下这几部分:
- 一个
javascript function
或者一个class
。 - 在
prototype
上定义apply
方法。 - 指定要插入的事件挂钩。
- 使用 webpack 提供的插件 API 操作构建。
- 在功能完成后调用 webpack 提供的回调函数。
// 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();}
);
}
}
基本的插件架构
plugin
是在其prototype
上使用 apply 方法实例化的对象。这个 apply 方法在安装插件时由 webpack 编译器调用一次。apply 方法被赋予对底层 webpack 编译器的引用,该引用授予对编译器回调的访问权。一个简单的插件结构如下:
class HelloWorldPlugin {apply(compiler) {
compiler.hooks.done.tap('Hello World Plugin', (stats /* stats is passed as argument when done hook is tapped. */) => {console.log('Hello World!');
});
}
}
module.exports = HelloWorldPlugin;
Compiler and Compilation
开发插件时最重要的两种资源是 Compiler 和 Compilation。
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;
Async event hooks 异步事件钩子
有些插件挂钩是异步的。为了利用它们,我们可以使用 tap 方法,它将以同步的方式运行,或者使用 tapAsync 方法或 tapprogre 方法之一,这是异步方法。
tapAsync
当我们使用 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
当我们使用 tapPromise 方法定义插件时,我们需要返回一个 promise,这个 promise 将在异步任务完成时 resolve。
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;
Example
一旦我们能够使用 webpack 编译器和每个单独的编译器,我们就可以对引擎本身进行无穷无尽的操作。我们可以重新格式化现有文件,创建派生文件,或者创建全新的资源。
让我们编写一个简单的示例插件,它生成一个名为 fillist .md 的新构建文件; 其中的内容将列出构建中的所有资源文件。这个插件可能看起来像这样:
class FileListPlugin {apply(compiler) {// emit is asynchronous hook, tapping into it using tapAsync, you can use tapPromise/tap(synchronous) as well
compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
// Create a header string for the generated file:
var filelist = 'In this build:\n\n';
// Loop through all compiled assets,
// adding a new line item for each filename.
for (var filename in compilation.assets) {filelist += '-' + filename + '\n';}
// Insert this list into the webpack build as a new file asset:
compilation.assets['filelist.md'] = {source: function() {return filelist;},
size: function() {return filelist.length;}
};
callback();});
}
}
module.exports = FileListPlugin;
Different Plugin Shapes
插件可以根据它所访问的事件挂钩被划分为不同的类型。每个事件钩子都预先定义为同步 (synchronous) 或异步(asynchronous)、瀑布式或并行钩子,并且钩子在内部使用 call/callAsync 方法调用。受支持的或可以使用的钩子列表通常指定在 this.hooks
上。