webpack/webpack
Input boundaries are the config object (validated against JSON Schema), the input filesystem behind `InputFileSystem`, and optionally the network (`HttpUriPlugin`) or a data/virtual URL scheme. A request becomes a module through `NormalModuleFactory.create` (resolve → rules match → factory hooks), is read and transformed by the loader chain in `LoaderRunner`, then parsed into `Dependency` objects that populate the `ModuleGraph`. At seal time the `ModuleGraph` is frozen and `buildChunkGraph` fills a `ChunkGraph`; code generation renders each module through its `Generator` and `DependencyTemplate`s into `Source` objects, which are hashed, assembled into assets, passed through the `processAssets` stage pipeline, and finally written by `Compiler.emitAssets` through `OutputFileSystem`. Everything durable — module graph, code generation results, resolver results — round-trips through `lib/serialization/` into the filesystem cache pack.
- Input normalization is two distinct layers: `normalization.js` canonicalizes the user's config shape (shorthand → full form, 828 lines) and `defaults.js` fills mode/target-dependent values (2,889 lines); `applyWebpackOptionsBaseDefaults` runs before plugins and `applyWebpackOptionsDefaults` after.
- `NormalModuleFactory.create` builds a single mutable `ResolveData` object carrying `contextInfo`, `request`, `phase`, import `attributes`, `createData`, and three `LazySet`s of file/missing/context dependencies, then drives it through `beforeResolve` → `factorize` → `resolve` → `afterResolve` → `createModule` → `module`.
- Loader selection is a compiled rule set: `NormalModuleFactory`'s constructor compiles `options.defaultRules` and `options.rules` together via `ruleSetCompiler.compile`, so `module.rules` matching is a precompiled matcher rather than a per-request scan.
- Source text enters through `NormalModule._doBuild`, which calls the vendored `runLoaders` with a `processResource` callback; the loader chain is executed as a pitching pass then a normal pass (`iteratePitchingLoaders` → `processResource` → unwind), and each loader module is loaded by `loadLoader`.
- The loader chain can bypass parsing entirely: a loader may return `webpackAST` in its `additionalData`, which `LoaderRunner` types as `AdditionalDataKnown.webpackAST` — "the AST webpack should parse instead of the source".
- Cacheability is data that flows back out of the loader chain: `RunLoaderResult` carries `cacheable`, `notCacheableReasons`, and the `fileDependencies`/`contextDependencies`/`missingDependencies` lists that `FileSystemInfo` later snapshots to decide staleness.
- State ownership is explicit: the `Compilation` owns `modules`, `entries`, `chunks`, the `ModuleGraph` and `ChunkGraph`, and five work queues (`factorizeQueue`, `buildQueue`, `rebuildQueue`, `processDependenciesQueue`, `addModuleQueue`) which `seal`'s `finalCallback` clears on completion.
- `seal` freezes the module graph with `this.moduleGraph.freeze("seal")` before building chunks, then materializes one `Chunk` + `Entrypoint` per entry and seeds `chunkGraphInit` with each entry's resolved modules, validating `dependOn`/`runtime` conflicts and circular `dependOn` into `compilation.errors`.
- After chunking, the ordered tail of `seal` is `processRuntimeRequirements` → `_attachPendingRuntimeModules` → `createHash` (which returns the code-generation jobs) → `_runCodeGenerationJobs` → `createModuleAssets` → `createChunkAssets` → `processAssets` → freeze `this.assets` → `summarizeDependencies` → `afterSeal`.
- `this.assets` is frozen at the end of `processAssets` — literally `Object.freeze` when `backCompat` is off, and a `soonFrozenObjectDeprecation` proxy carrying `DEP_WEBPACK_COMPILATION_ASSETS` when it is on — so post-seal asset mutation is a hard boundary.
- The output boundary is `Compiler.emitAssets`, which writes at most 15 assets concurrently, recomputes `immutable` when a query/hash suffix is stripped, special-cases Windows drive-absolute target paths via `WINDOWS_ABS_PATH_REGEXP`, and refuses to write two assets whose paths differ only by casing or query string.
- Emitted-asset writes are deduplicated through two caches on the compiler — `_assetEmittingWrittenFiles` (path → generation) and `_assetEmittingSourceCache` (Source → `{ sizeOnlySource, writtenTo }`) — so an unchanged `Source` in a watch rebuild is compared rather than rewritten.
- Records are a separate persisted side-channel from the cache: `Compiler.readRecords` runs before `compile` and `emitRecords` after `emitAssets`, and `RecordIdsPlugin` uses them to keep module/chunk ids stable across builds via `recordsPath`.
- Build dependencies close the loop into the cache: after `done`, `run`'s final step is `this.cache.storeBuildDependencies(compilation.buildDependencies, compilation.optionalBuildDependencies, …)`, which `PackFileCacheStrategy` snapshots into the `PackContainer` alongside a version string.