or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

asset-management.mdhooks.mdindex.mdplugin-configuration.mdutilities.md

hooks.mddocs/

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.