CodeVetter · Repo Unpack · Behavior traces
webpack/webpack
Four flows are worth walking: a one-shot `webpack(config, cb)` run, a single module's resolve→build→parse path, a watch rebuild, and the persistent-cache store/restore path. Each is a callback chain rather than promises, with `tapable` hooks at every joint and `logger.time`/`timeEnd` pairs marking the phases you see in `stats` profiling output. The names below are the actual functions in call order.
- Startup (one-shot): `webpack(options, cb)` → `create()` → schema check → `createCompiler` → `compiler.run(cb)`; `run` calls `cache.endIdle` if idle, then `hooks.beforeRun` → `hooks.run` → `readRecords` → `compile(onCompiled)`.
- `Compiler.compile` runs `newCompilationParams()` → `hooks.beforeCompile` → `hooks.compile` → `newCompilation(params)` → `hooks.make` → `hooks.finishMake` → `process.nextTick` → `compilation.finish` → `compilation.seal` → `hooks.afterCompile`, timing each phase with the `webpack.Compiler` logger.
- Completion: `onCompiled` checks `hooks.shouldEmit`; if emitting it defers a `process.nextTick`, calls `emitAssets`, re-enters `compile` when `needAdditionalPass` is true, then `emitRecords` → build `new Stats(compilation)` → `hooks.done` → `cache.storeBuildDependencies` → `finalCallback`, which sets `cache.beginIdle()` and fires `hooks.afterDone`.
- The callback form of `webpack()` always closes the compiler for you — `compiler.run((err, stats) => compiler.close((err2) => callback(err || err2, stats)))` — which is why plugin authors using the no-callback form must call `close` themselves.
- Module creation: `Compilation.handleModuleCreation` → `factorizeQueue` → `NormalModuleFactory.create` → `hooks.beforeResolve` (a `false` result short-circuits to an ignored module) → `hooks.factorize`/`resolveResource` → `hooks.afterResolve` → `hooks.createModule`/`createModuleClass` → `hooks.module` waterfall.
- Module build: `NormalModule.build` → `_doBuild` → `runLoaders` (pitch pass, `processResource`, normal pass) → on success the parser runs and dependencies are collected; every failure path funnels through `markModuleAsErrored(err)` followed by `_initBuildHash(compilation)` so an errored module still has a hash.
- Parser and generator instances are resolved per module type through `NormalModuleFactory.getParser`/`createParser` and `getGenerator`/`createGenerator`, cached in `parserCache`/`generatorCache` as `Map<type, WeakMap<options, instance>>`, and a type's one-time async setup runs through the `prepareModuleType` HookMap with in-flight callers queued.
- Seal, in order: clear queues in `finalCallback`, optionally re-attach `ChunkGraph` per module for `backCompat`, `hooks.seal`, loop `hooks.optimizeDependencies` until it returns falsy, `hooks.beforeChunks`, `moduleGraph.freeze("seal")`, entry→chunk creation with `assignDepths`, then the optimize/hash/codegen/assets tail.
- Watch rebuild: `Watching._invalidate` → `_go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles)` → `compiler.compile` → `_done(err, compilation)`, which re-enters `_go` when `this.invalid` was set during the run; `_done` is pre-bound in the constructor and the first pass is kicked off by `_invalidate` when `_initial`.
- Persistence: `IdleFileCachePlugin` taps `cache.hooks.store` to queue `strategy.store(identifier, etag, data)` and `cache.hooks.get` to `strategy.restore(...)`, then flushes on an idle timer whose delay is the smaller of `idleTimeout` and `idleTimeoutForInitialStore`, with `idleTimeoutAfterLargeChanges` as a third setting.
- A cache pack is read lazily by location: `Pack.get(identifier, etag)` looks the identifier up in `itemInfo`, finds its `location`, and only then materializes that `PackContent`'s map — so restoring one module does not deserialize the whole pack.
- Shutdown: `Compiler.close` first closes any active `watching`, fires `hooks.shutdown`, then defers `_releaseUnusedCompilationData(lastCompilation)` behind `Promise.resolve().then(...)` so a `close()` made inside the run callback cannot release `codeGenerationResults` before `afterDone` fires on the same stack, and finally calls `cache.shutdown(callback)`.
- Minification is a late `processAssets` stage, not part of codegen: `optimization.minimizer` defaults to a plugin wrapper that lazily requires `minimizer-webpack-plugin` at apply time and only constructs it once it knows which of JS/CSS/HTML are actually enabled (returning early if none are).
- Test execution mirrors the real runtime rather than mocking it: `test/templates/ConfigTestCases.js` runs a real `webpack()` build, resolves the bundle via a per-case `findBundle`, and executes it in a `vm` context — `test/harness/runner/asModule.js` even builds `vm.SyntheticModule`/`SourceTextModule` wrappers so ESM output is linked and evaluated for real.