| title | Plugin Patterns | ||||
|---|---|---|---|---|---|
| sort | 5 | ||||
| contributors |
|
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):
for (const chunk of compilation.chunks) {
// Explore each module within the chunk (built inputs):
for (const module of chunk.getModules()) {
// Explore each source file path that was included into the module:
if (module.buildInfo && module.buildInfo.fileDependencies) {
for (const fileDependency of module.buildInfo.fileDependencies) {
// we've learned a lot about the source structure now...
console.log(fileDependency);
}
}
}
// Explore each asset filename generated by the chunk:
for (const filename of chunk.files) {
// Get the asset source for each file generated by the chunk:
const source = compilation.assets[filename].source();
}
}
callback();
});
}
}
export default 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.
W> Deprecation warning: Array functions will still work.
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.
W> Deprecation warning: Array functions will still work.
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 thecompilation.assetstable.
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 = [...compilation.fileTimestamps.keys()].filter(
(watchfile) =>
(this.prevTimestamps.get(watchfile) || this.startTime) <
(compilation.fileTimestamps.get(watchfile) || Infinity),
);
this.prevTimestamps = compilation.fileTimestamps;
callback();
});
}
}
export default 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.
T> The fileDependencies Set is rebuilt in each compilation, so your plugin must add its own watched dependencies into each compilation to keep them under watch.
W> Since webpack 5, compilation.fileDependencies, compilation.contextDependencies and compilation.missingDependencies are now a Set instead of a Sortable Set and thus no longer sorted.
Similar to the watch graph, you can monitor changed chunks within a compilation by tracking their content hashes.
W> compilation.chunks is a Set, not an array. Calling .filter() or .map() directly on it throws a TypeError.
Use for...of or spread it into an array first.
W> chunk.hash is not defined on chunk objects. Use chunk.contentHash.javascript instead.
Accessing chunk.hash returns undefined, causing every chunk to appear changed on every rebuild.
class MyPlugin {
constructor() {
this.chunkVersions = {};
}
apply(compiler) {
compiler.hooks.emit.tapAsync("MyPlugin", (compilation, callback) => {
const changedChunks = [];
for (const chunk of compilation.chunks) {
const key = chunk.id ?? chunk.name;
const currentHash = chunk.contentHash.javascript;
if (currentHash === undefined) continue;
const oldHash = this.chunkVersions[key];
this.chunkVersions[key] = currentHash;
if (currentHash !== oldHash) {
changedChunks.push(chunk);
}
}
if (changedChunks.length > 0) {
console.log(
"Changed chunks:",
changedChunks.map((chunk) => chunk.id),
);
}
callback();
});
}
}
export default MyPlugin;T> Use chunk.id ?? chunk.name as the tracking key. Some chunks have an id but no name, so relying on chunk.name alone can miss changes.
CSS or other non-JS chunks may not have a javascript key in contentHash — the continue guard skips those safely.