0
# Hook System
1
2
This document covers the Tapable-based hook system for customizing manifest generation at various stages of the webpack compilation process.
3
4
## Hook Interface
5
6
```typescript { .api }
7
interface WebpackAssetsManifest {
8
hooks: {
9
apply: SyncHook<[manifest: WebpackAssetsManifest]>;
10
customize: SyncWaterfallHook<[
11
entry: KeyValuePair | false | undefined | void,
12
original: KeyValuePair,
13
manifest: WebpackAssetsManifest,
14
asset: Asset | undefined
15
]>;
16
transform: SyncWaterfallHook<[asset: AssetsStorage, manifest: WebpackAssetsManifest]>;
17
done: AsyncSeriesHook<[manifest: WebpackAssetsManifest, stats: Stats]>;
18
options: SyncWaterfallHook<[options: Options]>;
19
afterOptions: SyncHook<[options: Options, manifest: WebpackAssetsManifest]>;
20
};
21
}
22
```
23
24
## Hook Types and Usage
25
26
### apply Hook
27
28
Runs after the plugin setup is complete, before compilation starts:
29
30
```typescript { .api }
31
apply: SyncHook<[manifest: WebpackAssetsManifest]>;
32
```
33
34
```typescript
35
manifest.hooks.apply.tap("MyPlugin", (manifest) => {
36
console.log("Plugin setup complete");
37
manifest.set("build-time", Date.now().toString());
38
});
39
40
// Or via constructor options
41
const manifest = new WebpackAssetsManifest({
42
apply(manifest) {
43
manifest.set("version", "1.0.0");
44
},
45
});
46
```
47
48
### customize Hook
49
50
Customizes individual asset entries as they are added to the manifest:
51
52
```typescript { .api }
53
customize: SyncWaterfallHook<[
54
entry: KeyValuePair | false | undefined | void,
55
original: KeyValuePair,
56
manifest: WebpackAssetsManifest,
57
asset: Asset | undefined
58
]>;
59
```
60
61
```typescript
62
manifest.hooks.customize.tap("MyCustomizer", (entry, original, manifest, asset) => {
63
// Skip certain files
64
if (original.key.includes(".map")) {
65
return false; // Skip this entry
66
}
67
68
// Modify entry
69
if (entry && entry.key.startsWith("img/")) {
70
return {
71
key: entry.key.replace("img/", "images/"),
72
value: entry.value,
73
};
74
}
75
76
// Add integrity information
77
if (entry && manifest.options.integrity) {
78
const integrity = asset?.info[manifest.options.integrityPropertyName];
79
if (integrity && typeof entry.value === "string") {
80
return {
81
key: entry.key,
82
value: {
83
src: entry.value,
84
integrity,
85
},
86
};
87
}
88
}
89
90
return entry;
91
});
92
```
93
94
### transform Hook
95
96
Transforms the entire manifest before serialization:
97
98
```typescript { .api }
99
transform: SyncWaterfallHook<[asset: AssetsStorage, manifest: WebpackAssetsManifest]>;
100
```
101
102
```typescript
103
manifest.hooks.transform.tap("MyTransformer", (assets, manifest) => {
104
// Add metadata
105
const transformed = {
106
...assets,
107
_meta: {
108
generatedAt: new Date().toISOString(),
109
version: manifest.options.extra.version || "unknown",
110
nodeEnv: process.env.NODE_ENV,
111
},
112
};
113
114
return transformed;
115
});
116
```
117
118
### done Hook
119
120
Runs after compilation is complete and the manifest has been written:
121
122
```typescript { .api }
123
done: AsyncSeriesHook<[manifest: WebpackAssetsManifest, stats: Stats]>;
124
```
125
126
```typescript
127
manifest.hooks.done.tapPromise("MyDoneHandler", async (manifest, stats) => {
128
console.log(`Manifest written to ${manifest.getOutputPath()}`);
129
130
// Upload to CDN
131
await uploadToCDN(manifest.toString());
132
133
// Generate additional files
134
await generateServiceWorkerManifest(manifest.assets);
135
});
136
137
// Synchronous version
138
manifest.hooks.done.tap("MySyncHandler", (manifest, stats) => {
139
console.log(`Assets: ${Object.keys(manifest.assets).length}`);
140
});
141
```
142
143
### options Hook
144
145
Modifies plugin options before they are processed:
146
147
```typescript { .api }
148
options: SyncWaterfallHook<[options: Options]>;
149
```
150
151
```typescript
152
manifest.hooks.options.tap("OptionsModifier", (options) => {
153
// Environment-specific options
154
if (process.env.NODE_ENV === "development") {
155
options.writeToDisk = true;
156
options.space = 2;
157
} else {
158
options.space = 0; // Minify in production
159
}
160
161
return options;
162
});
163
```
164
165
### afterOptions Hook
166
167
Runs after options have been processed and validated:
168
169
```typescript { .api }
170
afterOptions: SyncHook<[options: Options, manifest: WebpackAssetsManifest]>;
171
```
172
173
```typescript
174
manifest.hooks.afterOptions.tap("PostOptionsSetup", (options, manifest) => {
175
console.log(`Plugin configured with output: ${options.output}`);
176
177
// Set up additional hooks based on final options
178
if (options.integrity) {
179
console.log("Subresource integrity enabled");
180
}
181
});
182
```
183
184
## Hook Combination Examples
185
186
### Advanced Customization
187
188
```typescript
189
const manifest = new WebpackAssetsManifest({
190
output: "assets-manifest.json",
191
integrity: true,
192
});
193
194
// Setup hook - initialize shared data
195
manifest.hooks.apply.tap("Setup", (manifest) => {
196
manifest.options.extra.startTime = Date.now();
197
});
198
199
// Customize individual entries
200
manifest.hooks.customize.tap("AssetCustomizer", (entry, original, manifest, asset) => {
201
if (!entry) return entry;
202
203
// Group assets by type
204
const ext = manifest.getExtension(original.key);
205
const group = ext === ".js" ? "scripts" : ext === ".css" ? "styles" : "assets";
206
207
return {
208
key: `${group}/${entry.key}`,
209
value: entry.value,
210
};
211
});
212
213
// Transform entire manifest
214
manifest.hooks.transform.tap("ManifestTransformer", (assets, manifest) => {
215
const buildTime = Date.now() - (manifest.options.extra.startTime as number);
216
217
return {
218
...assets,
219
_build: {
220
time: buildTime,
221
timestamp: new Date().toISOString(),
222
env: process.env.NODE_ENV,
223
},
224
};
225
});
226
227
// Post-processing
228
manifest.hooks.done.tapPromise("PostProcessor", async (manifest, stats) => {
229
// Write additional manifests
230
const serviceWorkerManifest = Object.entries(manifest.assets)
231
.filter(([key]) => !key.startsWith("_"))
232
.map(([key, value]) => ({
233
url: typeof value === "string" ? value : value.src,
234
revision: stats.hash,
235
}));
236
237
await manifest.writeTo("./dist/sw-manifest.json");
238
});
239
```
240
241
### Plugin Integration
242
243
```typescript
244
class MyWebpackPlugin {
245
apply(compiler) {
246
compiler.hooks.compilation.tap("MyPlugin", (compilation) => {
247
// Find WebpackAssetsManifest instances
248
const manifests = compiler.options.plugins?.filter(
249
plugin => plugin instanceof WebpackAssetsManifest
250
) as WebpackAssetsManifest[];
251
252
manifests.forEach(manifest => {
253
// Hook into the manifest
254
manifest.hooks.customize.tap("MyPlugin", (entry, original, manifest, asset) => {
255
// Custom logic for this plugin
256
if (entry && asset?.info.myPluginMetadata) {
257
return {
258
...entry,
259
value: {
260
...entry.value,
261
metadata: asset.info.myPluginMetadata,
262
},
263
};
264
}
265
return entry;
266
});
267
});
268
});
269
}
270
}
271
```
272
273
### Hook Utilities Access
274
275
The manifest provides utility functions through the `utils` property:
276
277
```typescript { .api }
278
interface WebpackAssetsManifest {
279
utils: {
280
isKeyValuePair: typeof isKeyValuePair;
281
isObject: typeof isObject;
282
getSRIHash: typeof getSRIHash;
283
};
284
}
285
```
286
287
```typescript
288
manifest.hooks.customize.tap("UtilityExample", (entry, original, manifest, asset) => {
289
// Use utility functions
290
if (manifest.utils.isKeyValuePair(entry) && manifest.utils.isObject(entry.value)) {
291
// Generate custom SRI hash
292
const content = asset?.source.source();
293
if (content) {
294
entry.value.customHash = manifest.utils.getSRIHash("md5", content);
295
}
296
}
297
298
return entry;
299
});
300
```
301
302
## Hook Execution Order
303
304
1. **options** - Modify plugin options
305
2. **afterOptions** - Post-process validated options
306
3. **apply** - Plugin setup complete
307
4. **customize** - Per-asset customization (during compilation)
308
5. **transform** - Transform entire manifest (before serialization)
309
6. **done** - Post-compilation cleanup and additional processing
310
311
Understanding this order is crucial for proper hook coordination and data flow.