外掛向第三方開發人員公開 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
方法的實例化物件。此 apply
方法在安裝外掛時由 webpack 編譯器呼叫一次。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
方法使用外掛時,我們需要傳回一個承諾,當我們的非同步任務完成時會解析。
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',
}),
],
};
這將產生一個看起來像這樣的馬克ダウン檔案,名稱自選
# 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 掛鉤
SyncBailHook[params]
定義tap
方法存取。call(...params)
方法呼叫。在這些類型的掛鉤中,每個插件回呼都會一個接一個地使用特定 args
呼叫。如果任何插件傳回的值不是未定義,則掛鉤會傳回該值,且不會呼叫進一步的插件回呼。許多有用的事件,例如 optimizeChunks
、optimizeChunkModules
是 SyncBailHooks。
Waterfall 掛鉤
SyncWaterfallHook[params]
定義tap
方法存取。call(...params)
方法呼叫在這裡,每個插件都會一個接一個地使用前一個插件的傳回值中的參數呼叫。插件必須考慮其執行順序。它必須接受已執行的前一個插件的參數。第一個插件的值為 init
。因此,至少必須為瀑布掛鉤提供 1 個參數。此模式用於與 webpack 範本(例如 ModuleTemplate
、ChunkTemplate
等)相關的 Tapable 實例中。
非同步序列掛鉤
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
等事件。
非同步串列暫停
AsyncSeriesBailHook[params]
定義tap
/tapAsync
/tapPromise
方法存取。callAsync(...params)
方法呼叫非同步平行
AsyncParallelHook[params]
定義tap
/tapAsync
/tapPromise
方法存取。callAsync(...params)
方法呼叫Webpack 會在套用外掛預設值後套用組態預設值。這讓外掛可以提供自己的預設值,並提供建立組態預設值外掛的方法。