Understanding Webpack Module Creation: Stages and Dependency Handling
Webpack transforms entry files into modules through four stages—create, add, build, and processDep—where it generates a module instance via factory hooks, registers it in the compilation, runs loaders and parses the source into an AST, groups and resolves dependencies, and finally prepares the modules for bundling.
In the previous article we explained how webpack locates entry files. This article describes how those files are transformed into modules.
The overall process can be divided into four stages: create , add , build , and processDep .
1. create
During the create stage webpack generates a module instance. The core logic is in NormalModuleFactory.create which triggers beforeResolve and factory hooks.
create(data, callback) {
// ... omitted logic
this.hooks.beforeResolve.callAsync({ contextInfo, resolveOptions, context, request, dependencies }, (err, result) => {
const factory = this.hooks.factory.call(null);
if (!factory) return callback();
factory(result, (err, module) => {
callback(null, module);
});
});
}2. addModule
After a module instance is created it is stored in Compilation.modules and the internal _modules map. The entry module also receives a reason (e.g., SingleEntryDependency ) and is added to Compilation.entries .
(err, module) => {
// ...
const addModuleResult = this.addModule(module);
module = addModuleResult.module;
// for entry files
this.entries.push(module);
dependency.module = module;
module.addReason(null, dependency);
// start build stage
}3. build
The build stage parses the source, runs loaders and generates the final source string. The main entry point is NormalModule.build , which delegates to doBuild .
build(options, compilation, resolver, fs, callback) {
return this.doBuild(options, compilation, resolver, fs, err => {
// parse source to AST
const result = this.parser.parse(/* args */);
if (result !== undefined) {
handleParseResult(result);
}
});
}Inside doBuild the loader chain is executed and the result is stored in this._source .
runLoaders({ resource: this.resource, loaders: this.loaders, context: loaderContext, readResource: fs.readFile.bind(fs) }, (err, result) => {
this._source = this.createSource(this.binary ? asBuffer(source) : asString(source), resourceBuffer, sourceMap);
});The parser converts the source (or an AST provided by a loader) into an AST using acorn , then walks the tree with the sequence program → detectStrictMode → prewalkStatements → walkStatements , creating dependency objects such as HarmonyImportSpecifierDependency , ConstDependency , etc.
4. Dependency processing
After the build finishes, Compilation.processModuleDependencies groups dependencies by their factory and resource identifier, filters out placeholder dependencies, and invokes the corresponding factories to create dependent modules. The recursion continues until the whole graph is built.
processModuleDependencies(module, callback) {
const dependencies = new Map();
const addDependency = dep => {
const resourceIdent = dep.getResourceIdentifier();
if (resourceIdent) {
const factory = this.dependencyFactories.get(dep.constructor);
// group by factory and identifier
}
};
// walk through module.blocks, module.dependencies, etc.
}Finally the compiled modules and their dependency information are used in the seal phase to generate chunks and the final bundle.
The article also includes diagrams illustrating each stage and a complete example with a.js and demo.js .
Didi Tech
Official Didi technology account
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.