To prepare
tapable
Webpack is essentially an event flow mechanism, and its workflow is to connect various plug-ins in series, and the core of this is tapable, the most core of Webpack, The Compiler responsible for compiling and the Compilation responsible for creating bundles are instances of Tapable. Tapable exposes a number of hook classes that can be used to create hooks for plug-ins.
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code
enhanced-resolve
Github.com/webpack/enh…
Enhanced – Resolve is an asynchronous, highly configurable library for resolving file paths. With enhanced- Resolve, Webpack resolves three file paths: absolute, relative, and module paths. Webpack can generate the configuration of enhanced- Resolve based on the parameters in the configuration and the default configuration, and then leverage the enhanced- Resolve library to resolve the various paths.
acorn & ast estree
Github.com/acornjs/aco…
Github.com/estree/estr…
Acorn is a small and fast javascript parser implemented entirely in javascript. Pass the code to be parsed to Acorn. parse to output an AST that follows the Estree specification. Estree is a JSON-style AST, and popular implementations of Bable and ESLint are also based on Estree. For example, let code = 1 + 1; After parsing into ast:
{
"type": "Program",
"start": 0,
"end": 11,
"body": [
{
"type": "ExpressionStatement",
"start": 5,
"end": 10,
"expression": {
"type": "BinaryExpression",
"start": 5,
"end": 10,
"left": {
"type": "Literal",
"start": 5,
"end": 6,
"value": 1,
"raw": "1"
},
"operator": "+",
"right": {
"type": "Literal",
"start": 9,
"end": 10,
"value": 1,
"raw": "1"
}
}
}
],
"sourceType": "script"
}
Copy the code
loader-runner
Github.com/webpack/loa…
Juejin. Cn/post / 684490…
Webpack is used to control the library that executes the loader.
neo-async & async
Github.com/suguru03/ne…
Github.com/caolan/asyn…
Async document
The Async library is a library of asynchronous invocation tools that provide a number of methods for controlling asynchronous processes. Neo-async is an enhanced library of Async that has been optimized for performance. There are many scenarios in Webpack that utilize the neo-Async library to execute multiple methods in parallel.
demo
The WebPack version number used in this example is 4.44.2.
// a.js (webpack config) import add from './b.js' add(1, 2) import('./c').then(del => del(1, 2) 2)) ----- // b.js import mod from './d.js' export default function add(n1, n2) { return n1 + n2 } mod(100, 11) ----- // c.js import mod from './d.js' mod(100, 11) import('./b.js').then(add => add(1, 2)) export default function del(n1, n2) { return n1 - n2 } ----- // d.js export default function mod(n1, n2) { return n1 % n2 }Copy the code
Webpack related configurations:
{ mode: "development", entry: { app: "./debug/src/a.js" }, devtool: "none", output: { path: Path. resolve(__dirname, "dist"), filename: "[name].[chunkhash].js"}, module: {rules: [// 前 {enforce: "pre", test: / \. Js $/, use: "Babel - loader"}, {/ / regular matching test: / \. M? Js $/, exclude: / (node_modules | bower_components) /, use: {loader: "Babel - loader", the options: {presets: [" @ Babel/preset - env "]}}}, / / rear {enforce: "post", the test: / \. Js $/, use: "babel-loader" } ] } };Copy the code
Where, A. js is the entry file configured in webPack Config. A. js depends on B. js/ C. js, while B. js depends on D. js and C. js depends on D. js/ B. js.
Finally compiled by Webpack, two chunk files will be generated, among which:
app.hash.js
Contains thewebpack runtime
Code anda.js/b.js/d.js
Module code;0.bundle.hash.js
Contains asynchronously loadedc.js
The code.
The build process
Preparation before compilation
Executed first
const compiler = webpack(config);
Copy the code
Create a build process and pass in the configuration above. In the Webpack function, it is executed first
const webpackOptionsValidationErrors = validateSchema(
webpackOptionsSchema,
options
);
Copy the code
Do a type check on the incoming configuration and execute
if (Array.isArray(options)) { compiler = new MultiCompiler( Array.from(options).map(options => webpack(options)) ); } else if (typeof options === "object") { options = new WebpackOptionsDefaulter().process(options); compiler = new Compiler(options.context); / /... }Copy the code
If the configuration passed in is an array, multiple compilation processes are created. We’ll start with the case where the incoming configuration is an object. After performing
options = new WebpackOptionsDefaulter().process(options);
Copy the code
Merge the default system configuration and configuration files. Then, using the merged configuration, execute
compiler = new Compiler(options.context);
Copy the code
Create the Compiler object. The Compiler class defines the entire build flow and is an extension of Tapable, attaching a bunch of hooks done/ Runemit /seal, etc. After the Compiler object is created, execute
if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { if (typeof plugin === "function") { plugin.call(compiler, compiler); } else { plugin.apply(compiler); }}}Copy the code
Register the plug-in in the configuration, and you can see that the Compiler instance is passed in when the plug-in is registered. The Compiler contains all the hooks in the build process, so the plug-in can register events in the hooks in the Compiler instance for processing. The compiler’s partial hooks pass in the compilation object parameters, which in turn contain many of the hooks that the resource builds.
Execute after registering the plug-in
compiler.options = new WebpackOptionsApply().process(options, compiler);
Copy the code
Register and activate some default plug-ins depending on the options configuration. The Compiler object is now created.
perform
compiler.run()
Copy the code
Start the compilation process. In the compiler.compile method, execute first
const params = this.newCompilationParams();
Copy the code
Initialize the parameters of the Compilation object
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory(),
compilationDependencies: new Set()
};
return params;
}
Copy the code
The NormalModuleFactory class is used to create an instance of normalModule, and an instance of normalModule is a Module. It is executed in the new NormalModuleFactory
this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
Copy the code
This method formats the loader configuration in preparation for later module applications of loader.
And then execute it
const compilation = this.newCompilation(params);
Copy the code
This method instantiates a compilation object, which also extends to the Tapable class, and also mounts a bunch of hooks. A compilation object represents a build of a resource.
At this point, the preparation before compilation is complete. The compiler.hook. Make hook is called to start building the module.
Building blocks
Parsing the entry module from the compiler.addentry method.
Resolve the path
Starting with calling moduleFactory.create, the first step is to parse the path. Trigger normalModuleFactory. Hooks. Resolver hooks, execution
const loaderResolver = this.getResolver("loader");
const normalResolver = this.getResolver("normal", data.resolveOptions);
Copy the code
The result is two Resolver objects, where the resolver is an instance of the enhanced resolve mentioned earlier, which resolves absolute paths asynchronously. The Inline loader is then parsed
const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!" ); const noAutoLoaders = noPreAutoLoaders || requestWithoutMatchResource.startsWith("!" ); const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!" ); let elements = requestWithoutMatchResource .replace(/^-? ! +/, "") .replace(/!! +/g, "!" ) .split("!" );Copy the code
The absolute path of the inline loader and the current module is then resolved in parallel via asynclib. parallel.
asyncLib.parallel( [ callback => this.resolveRequestArray( contextInfo, context, elements, // inline loader loaderResolver, callback ), callback => { // ...... normalResolver.resolve( contextInfo, context, resource, {}, (err, resource, resourceResolveData) => { if (err) return callback(err); callback(null, { resourceResolveData, resource }); } ); } ], (err, results) => { // ...... })Copy the code
In the callback, get the completed inline loader and the absolute path to the current file, then continue parsing the laoder in the configuration, execute
const result = this.ruleSet.exec({ resource: resourcePath, realResource: matchResource ! == undefined ? resource.replace(/\? .*/, "") : resourcePath, resourceQuery, issuer: contextInfo.issuer, compiler: contextInfo.compiler });Copy the code
RuleSet here is the ruleSet object created in Constructor when new NormalModuleFactory is created. In the new RuleSet object, the loader configuration in the configuration has been formatted
{
resource: function(),
resourceQuery: function(),
compiler: function(),
issuer: function(),
use: [
{
loader: string,
options: string,
<any>: <any>
}
],
rules: [<rule>],
oneOf: [<rule>],
<any>: <any>,
}
Copy the code
Where resource is a conditional function that can be executed to filter out the loader corresponding to the current module, use is loader and configuration. For example, in Demo:
The module: {rules: [/ / front {enforce: "pre", test: / \. Js $/, use: "Babel - loader"}, {/ / regular matching test: / \. M? Js $/, exclude: /(node_modules|bower_components)/, use: { loader: "babel-loader", options: { presets: "@ Babel/preset - env"}}}, / / rear {enforce: "post", the test: / \. Js $/, use: "Babel - loader"}]}Copy the code
After formatting isIn the implementationthis.ruleSet.exec
Then the corresponding of the current module can be obtainedloader
, such asdemo
In the entry filea.js
, the matching result after parsing is:
After the matching loader results are obtained, the path of the three sets of Loaders will be parsed in parallel
asyncLib.parallel( [ this.resolveRequestArray.bind( this, contextInfo, this.context, useLoadersPost, / / rear loader loaderResolver), enclosing resolveRequestArray. Bind (this, contextInfo, enclosing context, useLoaders, / / ordinary loader loaderResolver), enclosing resolveRequestArray. Bind (this, contextInfo, enclosing context, useLoadersPre, / / front loader loaderResolver)], (err, results) = > {/ /... })Copy the code
After obtaining the path, merge the inline, front-loader, normal, and post-loader sort.
- normal
loader
Order of execution: pre -> normal -> Inline -> POST - Before the resource path
xxx! =!
Decorations: pre -> Inline -> Normal -> POST - Before the resource path
-!
Decoration: Inline -> POST - Before the resource path
!
Decoration: pre -> Inline -> POST - Before the resource path
!!!!!
Adornment: inline
This gives you the loaders in the correct order to match the modules.
The resolve process ends at this point.
Compile the module
After the path is resolved, run
createdModule = new NormalModule(result);
Copy the code
Create a Module, each of which is a NormalModule object. Build the module from the normalModule.dobuild method. Executed first
const loaderContext = this.createLoaderContext(
resolver,
options,
compilation,
fs
);
Copy the code
Generate a context object for all loaders to use, and execute
runLoaders( { resource: this.resource, loaders: this.loaders, context: loaderContext, readResource: fs.readFile.bind(fs) }, (err, result) => { // ...... })Copy the code
Pass the Module to loader for processing. The runloader here is a method from the Loader-Runner library above that executes the loaders in sequence. Get the module processed by loader in the callback and execute it
const result = this.parser.parse( this._ast || this._source.source(), { current: this, module: this, compilation: compilation, options: options }, (err, result) => { if (err) { handleParseError(err); } else { handleParseResult(result); }});Copy the code
Start parsing module. In the parse method, it is executed first
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments
});
Copy the code
willmodule
Conform toEstree
Specification of theast
, here parsingast
Is the use of theacorn
Library. In order toa.js
For example, convert toast
As shown in figure:
The next step is to traverse the tree, execute
if (this.hooks.program.call(ast, comments) === undefined) {
this.detectMode(ast.body);
this.prewalkStatements(ast.body);
this.blockPrewalkStatements(ast.body);
this.walkStatements(ast.body);
}
Copy the code
These lines of code process the AST line by line, resolving dependencies in the module.
In the enclosing hooks. The program. The call (ast, comments) this hook, will trigger HarmonyDetectionParserPlugin and UseStrictPlugin this two plug-ins callback.
HarmonyDetectionParserPlugin
If there is one in the codeimport
orexport
Or of typejavascript/esm
So it’s going to increaseHarmonyCompatibilityDependency
和HarmonyInitDependency
Rely on.UseStrictPlugin
Used to check whether files existuse strict
If yes, add oneConstDependency
Rely on.
In detectStrictMode, it checks if the current execution block has Use Strict, and if so, it is executed
this.scope.isStrict = true
Copy the code
The prewalkStatements function handles variables. Each node is iterated through, calling different handlers depending on statement.type. For example, the first node of A. js is importDeclaration, which processes this node, First trigger HarmonyImportDependencyParserPlugin plug-in, increase ConstDependency and HarmonyImportSideEffectDependency dependence. Then trigger HarmonyImportDependencyParserPlugin plug-ins, execution
parser.scope.renames.set(name, "imported var");
Copy the code
Set the value of Add to imported var.
The walkStatements step dives deeper into the function, making calls to the contents of the function
this.detectMode(expression.body.body);
this.prewalkStatement(expression.body);
this.walkStatement(expression.body);
Copy the code
Loop through dependencies in functions. For example, the third sentence import(‘./c’).then(del => del(1, 2)), which is an asynchronous loading dependency statement, is also a function call statement. The ImportParserPlugin plugin generates an ImportDependenciesBlock dependency and adds it to module.blocks.
// ImportParserPlugin const depBlock = new ImportDependenciesBlock( param.string, expr.range, Object.assign(groupOptions, { name: chunkName }), parser.state.module, expr.loc, parser.state.module ); / / parser. State. The current for the current processing module of the parser. State. The current. The addBlock (depBlock);Copy the code
When allast
After being parsed, all dependencies of the current module are obtained. Common dependencies are saved inmodule.dependencies
Asynchronous dependencies are stored inmodule.blocks
In the. fora.js
For example, the obtained dependencies are:
Then perform
this._initBuildHash(compilation);
Copy the code
Generate the hash value and store it in this._buildhash, where the module is compiled.
Go back to the callback, execute the afterBuild callback, and then call
compilation.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
Copy the code
Based on the resolved dependencies, the dependent modules are cyclically compiled.
All modules are now compiled. Eventually, all files are converted to modules, and module and dependencies are added to generate chunk and package files.
Generate the chunk
Go back to the compiler.hook. Make callback and execute the compiler.seal function. A large number of hooks are triggered in the SEAL method, providing a large number of hooks for hacking into the WebPack build process.
And then we start generating chunk. In this process, the entry module configured in config is first traversed and an empty chunk is created for each entry module. An entryPoint is then instantiated, which is a chunkGroup containing runtimeChunk. Each chunkGroup can contain as many chunks as possible.
And then call
GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
GraphHelpers.connectChunkAndModule(chunk, module);
Copy the code
Establish relationships among Module, Chunk, and Entrypoint.
And then call
buildChunkGraph(
this,
/** @type {Entrypoint[]} */ (this.chunkGroups.slice())
);
Copy the code
Establish relationships between chunk and other dependencies. In the buildChunkGraph method, three methods are called
// PART ONE
visitModules(
compilation,
inputChunkGroups,
chunkGroupInfoMap,
blockConnections,
blocksWithNestedBlocks,
allCreatedChunkGroups
);
// PART TWO
connectChunkGroups(
blocksWithNestedBlocks,
blockConnections,
chunkGroupInfoMap
);
// Cleaup work
cleanupUnconnectedGroups(compilation, allCreatedChunkGroups);
Copy the code
In visitModules, this is done first
const blockInfoMap = extraceBlockInfoMap(compilation); ----------------------------------- const extraceBlockInfoMap = compilation => { // ...... For (const module of compile.modules) {// Loop each module blockQueue = [module]; currentModule = module; while (blockQueue.length > 0) { block = blockQueue.pop(); blockInfoModules = new Set(); blockInfoBlocks = []; / /... // For synchronous modules, {for (const dep of block.dependencies) iteratorDependency(dep); } if (block.blocks) {for (const b of block.blocks) iteratorBlockPrepare(b); } const blockInfo = { modules: blockInfoModules, blocks: blockInfoBlocks }; blockInfoMap.set(block, blockInfo); BlockInfoMap}} return blockInfoMap; };Copy the code
In this method, for allmodule
I’m going to iterate. I’m going to iteratemodule
The process will be on thismodule
的 dependencies
Dependency to get thismodule
The dependency module will handle this at the same timemodule
的 blocks
(asynchronously loaded modules). The traversal process will set up the basicmodule graph
, including ordinarymodule
And asynchronousmodule(block)
, and finally stored in amap
表 (blockInfoMap)
, represents the dependencies between modules. fordemo
, the relationship diagram can be obtained:
At this point, we only get the dependency relationship between empty chunk and individual modules. Chunk and module are not associated yet. The next step is to add the associated dependencies to chunk. For example, a. Js, the key steps are:
- To deal with
a.js
And geta.js
Dependencies inconst blockInfo = blockInfoMap.get(block);
, - Common modules for dependencies
b.js
, if the currentchunk
If no, joinchunk
And cache it. For asynchronous modulesc.js
, it isc.js
Create a newchunk
And add to the loop; - In the current
chunk
In, loop processingb.js
Dependency in; - In the new
chunk
In, loop processingc.js
Dependency in.
When the loop has done all the processingmodule
When,chunk
The graph is also generated fordemo
In terms of generationchunk
Figure as follows
Then perform
connectChunkGroups(
blocksWithNestedBlocks,
blockConnections,
chunkGroupInfoMap
);
Copy the code
Establish a father-son relationship between chunkGroup. There is no module in chunk2, which naturally has nothing to do with other chunks.
Then perform
cleanupUnconnectedGroups(compilation, allCreatedChunkGroups);
Copy the code
Clear empty chunkgroups that have no parent-child relationship.
Final generatedchunk
The graph is:
Generate the file
Generate a hash
After chunk is generated, the seal hook is called
this.createHash();
Copy the code
Generates the hash value. This method does two main things, generating a hash for module and a hash for chunk.
The generated code for the Module hash is as follows:
createHash() { //...... const modules = this.modules; for (let i = 0; i < modules.length; i++) { const module = modules[i]; const moduleHash = createHash(hashFunction); module.updateHash(moduleHash); module.hash = moduleHash.digest(hashDigest); module.renderedHash = module.hash.substr(0, hashDigestLength); } / /... }Copy the code
The key updateHash method is encapsulated in the implementation of each Module class. For normalModule, this method is:
updateHash(hash) {
hash.update(this._buildHash);
super.updateHash(hash);
}
Copy the code
Where _buildHash is the Module hash generated when the module is compiled. Super. The update code is
updateHash(hash) {
hash.update(`${this.id}`);
hash.update(JSON.stringify(this.usedExports));
super.updateHash(hash);
}
Copy the code
You can see that module ID and exports information used are also updated into the hash. Super. The update code is
updateHash(hash) {
for (const dep of this.dependencies) dep.updateHash(hash);
for (const block of this.blocks) block.updateHash(hash);
for (const variable of this.variables) variable.updateHash(hash);
}
Copy the code
What information is written to the hash for each dependency is determined by the updateHash method in xxxDependency. As you can see, a module hash contains:
- each
module
In its own unique need to writehash
Information in; module id
And what’s being usedexports
Information;- Dependent information.
The chunk hash generation code is as follows:
createHash() { //...... for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; const chunkHash = createHash(hashFunction); try { // ...... chunk.updateHash(chunkHash); const template = chunk.hasRuntime() ? this.mainTemplate : this.chunkTemplate; template.updateHashForChunk( chunkHash, chunk, this.moduleTemplates.javascript, this.dependencyTemplates ); this.hooks.chunkHash.call(chunk, chunkHash); chunk.hash = /** @type {string} */ (chunkHash.digest(hashDigest)); hash.update(chunk.hash); chunk.renderedHash = chunk.hash.substr(0, hashDigestLength); this.hooks.contentHash.call(chunk); } catch (err) { this.errors.push(new ChunkRenderError(chunk, "", err)); }} / /... }Copy the code
The first step is execution
chunk.updateHash(chunkHash); ------------------------------------------ updateHash(hash) { hash.update(`${this.id} `); hash.update(this.ids ? this.ids.join(",") : ""); hash.update(`${this.name || ""} `); for (const m of this._modules) { hash.update(m.hash); }}Copy the code
Writes hash information for id, IDS, name, and all modules it contains.
Then write the template information that generated chunk:
const template = chunk.hasRuntime()
? this.mainTemplate
: this.chunkTemplate;
template.updateHashForChunk(
chunkHash,
chunk,
this.moduleTemplates.javascript,
this.dependencyTemplates
);
Copy the code
Webpack divides templates into two types: mainTemplate, which eventually generates code containing the Runtime, and chunkTemplate. We’ll focus on mainTemplate’s updateHashForChunk method
updateHashForChunk(hash, chunk, moduleTemplate, dependencyTemplates) { this.updateHash(hash); this.hooks.hashForChunk.call(hash, chunk); / /... } ---------------------------------------------------- updateHash(hash) { hash.update("maintemplate"); hash.update("3"); this.hooks.hash.call(hash); }Copy the code
The template type mainTemplate is written, and the triggered Hash hook and hashForChunk hook write the output of some files.
After storing all the relevant information into the hash buffer, call the digest method to generate the final hash, and then extract the required length from the hash. The hash of chunk is obtained.
this.fullHash = /** @type {string} */ (hash.digest(hashDigest));
this.hash = this.fullHash.substr(0, hashDigestLength);
Copy the code
Generate the file
Is called after the hash value is generated
this.createChunkAssets();
Copy the code
To determine what text content is ultimately exported to each chunk. The render template is mainTemplate or chunkTemplate, depending on whether chunk contains the WebPack Runtime code. In addition to generating common Module code, mainTemplate also includes generating Runtime code, and chunkTemplate is mainly used for generating common chunk code.
const template = chunk.hasRuntime()
? this.mainTemplate
: this.chunkTemplate;
Copy the code
Then use getRenderManifest to get what render needs.
const manifest = template.getRenderManifest({
chunk,
hash: this.hash,
fullHash: this.fullHash,
outputOptions,
moduleTemplates: this.moduleTemplates,
dependencyTemplates: this.dependencyTemplates
});
Copy the code
Then perform
source = fileManifest.render();
Copy the code
Used to generate code. Take the chunk of the demo portal as an example. Since the chunk of the demo portal contains runtime code, mainTemplate is used, and the code generation process of mainTemplate includes generating common Module code. So let’s use mainTemplate as an example to illustrate the code generation process:
// mainTemplate manifest render: () => compilation.mainTemplate.render( hash, chunk, moduleTemplates.javascript, dependencyTemplates ), -------------- render(hash, chunk, moduleTemplate, DependencyTemplates) {const buf = this.renderBootstrap(Hash, chunk, moduleTemplate) const buf = this.renderBootstrap(hash, chunk, moduleTemplate) dependencyTemplates ); // The method registered in MainTemplate wraps the Runtime code, Let source = this.hooks. Render. Call (new OriginalSource(template.prefix (buf, " \t") + "\n", "webpack/bootstrap" ), chunk, hash, moduleTemplate, dependencyTemplates ); / /... return new ConcatSource(source, ";" ); }Copy the code
After calling hooks. Render, we get the chunk containing the Runtime bootstrap code, and finally return an instance of the ConcatSource type. The final code is saved in the Children of the ConcatSource class.
Let’s focus on how the module code is generated. In this.links.render. Call, execute this.links.modules. Call to generate module code
this.hooks.render.tap( "MainTemplate", (bootstrapSource, chunk, hash, moduleTemplate, dependencyTemplates) => { const source = new ConcatSource(); / /... source.add( this.hooks.modules.call( new RawSource(""), chunk, hash, moduleTemplate, dependencyTemplates ) ); source.add(")"); return source; });Copy the code
This hook is registered in JavascriptModulesPlugin, execution of the Template. RenderChunkModules method:
compilation.mainTemplate.hooks.modules.tap( "JavascriptModulesPlugin", (source, chunk, hash, moduleTemplate, dependencyTemplates) => { return Template.renderChunkModules( chunk, m => typeof m.source === "function", moduleTemplate, dependencyTemplates, "/******/ " ); }); -------------------------------------------------------- static renderChunkModules( chunk, filterFn, moduleTemplate, dependencyTemplates, prefix = "" ) { // ...... const allModules = modules.map(module => { return { id: module.id, source: moduleTemplate.render(module, dependencyTemplates, { chunk }) }; }); / /... return source; } --------------------------------- render(module, dependencyTemplates, options) { try { const moduleSource = module.source( dependencyTemplates, this.runtimeTemplate, this.type ); const moduleSourcePostContent = this.hooks.content.call( moduleSource, module, options, dependencyTemplates ); const moduleSourcePostModule = this.hooks.module.call( moduleSourcePostContent, module, options, dependencyTemplates ); const moduleSourcePostRender = this.hooks.render.call( moduleSourcePostModule, module, options, dependencyTemplates ); return this.hooks.package.call( moduleSourcePostRender, module, options, dependencyTemplates ); } catch (e) { e.message = `${module.identifier()}\n${e.message}`; throw e; }}Copy the code
In module.source, execute the this.generator.generate method:
source(dependencyTemplates, runtimeTemplate, type = "javascript") { // ...... const source = this.generator.generate( this, dependencyTemplates, runtimeTemplate, type ); / /... return cachedSource; }Copy the code
The Generator here is created during the NormalModuleFactory creation of the NormalModule. Execute this.sourceBlock in generate:
generate(module, dependencyTemplates, runtimeTemplate) {
// ......
this.sourceBlock(
module,
module,
[],
dependencyTemplates,
source,
runtimeTemplate
);
return source;
}
Copy the code
In sourceBlock, the dependency is looped first
sourceBlock( module, block, availableVars, dependencyTemplates, source, runtimeTemplate ) { for (const dependency of block.dependencies) { this.sourceDependency( dependency, dependencyTemplates, source, runtimeTemplate ); } / /... } ------------------------------ sourceDependency(dependency, dependencyTemplates, source, runtimeTemplate) { const template = dependencyTemplates.get(dependency.constructor); / /... template.apply(dependency, source, runtimeTemplate, dependencyTemplates); }Copy the code
That is, implement the Apply method in dependency one by one. For a. s., the first is dependent on HarmonyCompatibilityDependency, it is the apply method
apply(dep, source, runtime) { const usedExports = dep.originModule.usedExports; if (usedExports ! == false && ! Array.isArray(usedExports)) { const content = runtime.defineEsModuleFlagStatement({ exportsArgument: dep.originModule.exportsArgument }); source.insert(-10, content); }}Copy the code
Insert __webpack_require__.r(__webpack_exports__); . The __webpack_require__.r method adds an __esModule attribute to the __webpack_exports__ object, identifying it as an ES module.
Then there is the harmonyInit Dependency, whose Apply method traverses all dependencies and executes the Dependency’s template.Harmonyinit method. In this process, A.js code
import add from './b.js'
Copy the code
In the corresponding HarmonyImportSideEffectDependency and HarmonyImportSpecifierDependency template. HarmonyInit method will be performed at this moment, and then get the following
/* harmony import */ var _b_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b.js */ "./debug/src/b.js");
Copy the code
ConstDependency and HarmonyImportDependency do nothing here.
Then the HarmonyImportSpecifierDependency, get it
Object(_b_js__WEBPACK_IMPORTED_MODULE_0__["default"])
Copy the code
The corresponding
add()
Copy the code
Add in this sentence. This completes the dependency.
We then loop through the asynchronous module in sourceBlock:
sourceBlock( module, block, availableVars, dependencyTemplates, source, runtimeTemplate ) { // ...... for (const childBlock of block.blocks) { this.sourceBlock( module, childBlock, availableVars.concat(vars), dependencyTemplates, source, runtimeTemplate ); } / /... }Copy the code
As you can see, processing asynchronous modules takes the dependencies out of blocks and calls the sourceBlock loop.
When the module is parsed, the resulting ReplaceSource object contains an array replacements containing the source code conversion operations. Each element in the array has the following structure:
[Start position of replacement source, end position of replacement source, end content of replacement, priority]Copy the code
At this point, the operations on the source code are available. Then perform
const moduleSourcePostRender = this.hooks.render.call(
moduleSourcePostModule,
module,
options,
dependencyTemplates
);
Copy the code
The main job of this hook function is to wrap the module code that has been completed above. The package content is mainly a module loading system of Webpack itself, including module import and export, etc. The final generated form of each module code is as follows:
/***/ (function(module, __webpack_exports__, __webpack_require__) {// Module generates the code wrapped inside the function /***/})Copy the code
Then perform
return this.hooks.package.call(
moduleSourcePostRender,
module,
options,
dependencyTemplates
);
Copy the code
This hook is used to add comments.
At this point, the wrapped code for a Module is complete.
RenderChunkModules = renderChunkModules = renderChunkModules = renderChunkModules = renderChunkModules = renderChunkModules
{ /***/ "./debug/src/a.js": /*! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** (webpack)/debug/src/a.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / *! No exports provided */ /***/ (function(module, __webpack_exports__, __webpack_require__) {// Module contents...... /***/ }), /***/ "./debug/src/b.js": /*! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** (webpack)/debug/src/b.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / *! Exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) {// Module contents...... /***/ }), /***/ "./debug/src/d.js": /*! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** (webpack)/debug/src/d.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / *! Exports provided: default */ /***/ (function(module, __webpack_exports__, __webpack_require__) {// Module contents...... / /}) / * * * * * * * * * /}Copy the code
At this point, all the code is complete.
The output file
After going through all of the above phases, execute emitAssets to save the generated source in Assets. Then go back to compiler and call
this.outputFileSystem.mkdirp(outputPath, emitFiles);
Copy the code
Concatenate the files in the emitFiles method, get the file path, file name, and write the file to the output directory.
reference
Webpack core module Tapable parsing
The use and principle analysis of Webpack core library Tapable
Webpack with Rollup behind Acorn
Webpack 4 source code main process analysis
An overview of the Webpack series
Webpack source code analysis