The Webpack build process

Summary of the Webpack core build process

Webpack5 and Webpack4 source code is different, this article refers to Webpack5.38.1 source code

1. Summary of the core construction process

1.1 start the webpack

1.1.1 implement node_modules/bin/webpack. CMD 1.1.2 implement node_modules/webpack/bin/webpack. Js

1.2 start webpack – cli

1.2.1 Executes node_modules/webpack-cli/bin/cli.js to check whether Webpack is installed or not. 1.2.3 calls runCLI() to resolve command-line arguments and create compiled objects. Execution order: runCLI() => cli.run() => cli.buildCommand()

1.3 Create Compiler Object

1.3.1 Call Cli. BuildCommand () => Cli. CreateCompiler () => Webpack ()

1.4 Instance compiler object compiler and embed core hooks

1.4.1 mount the Node file read and write ability in Compiler 1.4.2 mount all plugins in Compiler 1.4.3 mount the default configuration in Compiler 1.4.4 enable the listening of core hooks: compilation, make

1.5 Execute the method Compiler.run () to start compilation

1.5.2 Trigger the Run hook - 1.5.2.1 The main function is to trigger the compile and make hook, after the completion of the make processing callback - 1.5.2.2 after the end of make, OnCompiled writes code to the file and outputs it to the dist directory

1.6 Execute method compiler.pile () to complete the compilation and output the file

BeforeCompile 1.6.2 trigger hook thisCompilation hook, Compile 1.6.4 trigger hook make (core hook) -1.6.4.1 Create the module according to the configuration of entry and so on - 1.6.4.2 Realize the compilation function: Convert code to AST syntax tree, and then convert it back to code code - 1.6.4.3 to process chunk 1.6.5 trigger hook finishMake 1.6.6 trigger hook afterCompile

2. Monitoring of core hooks

-beforerRun => unknown -run => unknown -beforeCompile => unknown -compile => unknown -thisCompilation => unknown -compilation => new Make => / WebpackOptionsApply() make => / WebpackOptionsApply() make => / WebpackOptionsApply() -finishMake => unknown -afterCompile => unknown

3. Core hook firing sequence

-beForerun => compiler.run() -run => compiler.run() -beforeCompile => compiler.compile() -compile => Compiler.compile () -thisCompilation => Compiler.compile () => Compiler.newCompilation () -thisCompilation => Compiler.compile () => Compiler.newCompilation () -make => compiler.compile() -finishMake => Compiler.compile () -afterCompile => compiler.compile()

4. Core code file path

// Based on version: "webpack": "^5.38.1", "webpack-cli": "^4.7.0" // File path:  node_modules/.bin/webpack.cmd node_modules/webpack/bin/webpack.js node_modules/webpack-cli/bin/cli.js node_modules/webpack/lib/webpack.js node_modules/webpack/lib/EntryPlugin.js node_modules/webpack/lib/Compiler.js node_modules/webpack/lib/Compilation.js

Detailed analysis of the Webpack build process

1. Start Webpack (execute Webpack from command line)

  • 1.1 Execute script: node_modules/.bin/webpack.cmd
  • 1.2 In webpack. CMD, find Node. exe based on the environment variable
  • 1.3 webpack. Using the node in the CMD. An.exe js script: node node_modules/webpack/bin/webpack. Js
# node_modules/.bin/webpack.cmd @echo off setLocal CALL :find_dp0 # 1.2. IF EXIST "%dp0% node.exe" (SET "_prog=%dp0% node.exe") ELSE (SET "_prog=node" PATHEXT=%PATHEXT:; .JS; =; %) # 1.3. Using the node. An.exe js script "% % _prog" "% dp0% \.. \webpack\bin\webpack.js" %* ENDLOCAL EXIT /b %errorlevel% :find_dp0 SET dp0=%~dp0 EXIT /b
  • 1.4Webpack.js to determine whether Webpack-CLI is installed
> 1.4.2 Webpack-cli => Runcli requires node_modules/webpack-cli/bin/cli.js
// node_modules/webpack/bin/webpack.js #! /usr/bin/env node // ... const runCli = cli => { const path = require("path"); const pkgPath = require.resolve(`${cli.package}/package.json`); const pkg = require(pkgPath); require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName])); }; // If webpack-cli is not installed, then if (! cli.installed) { // ... } else { runCli(cli); }

2. Start the webpack – cli

  • Node_modules /webpack-cli/bin/cli.js is imported from webpack.js
  • 2.2 Cli. Js to check whether Webpack is installed, if not prompted to install
  • 2.3 Execute runCLI() in cli.js, parse the command-line arguments, and create the compiled object. (For example: read configuration files, merge default configuration items, etc.)
Cli. Run () => parse command line argument =>... /lib/webpack-cli.js > 2.3.2 Cli. Run () => Create Compilated Object =>... /lib/webpack-cli.js
// node_modules/webpack-cli/bin/cli.js #! /usr/bin/env node // ... If (utils.packageexists ('webpack')) {process.argv => <br> // Module.prototype._compile => nodejs <br> // 2.3 Runcli runCLI(process.argv, originalModuleCompile); } else{ // ... }

3. Create compiler object

  • 3.1 Call cli. CreateCompiler () to generate a Compiler
Cli. BuildCommand () calls cli. CreateCompiler () to create a compiler; // Cli. BuildCommand () is defined in: node_modules/webpack-cli/lib/webpack-cli.js compiler = await this.createCompiler(options, callback); // Call webpack(options) in cli.createCompiler() returns a compiler // Webpack (options) is defined in: Node_modules/webpack/lib/webpack. Js compiler = this. Webpack (/ / merge options / /...).
  • 3.2 Judging whether to end the command line or whether to watch according to the parameters
if (isWatch(compiler) && this.needWatchStdin(compiler)) {
    process.stdin.on('end', () => {
        process.exit(0);
    });
    process.stdin.resume();
}

4. Instantate compiler object compiler and embed core hooks

  • 4.1 webpack createCompiler () calls (), defined in: node_modules/webpack/lib/webpack. Js
  • 4.2 Create a Comipler instance in createComppiler ()
> 4.2.1 createCompiler() instantiates a Compiler using new Compiler(), defined in:... /lib/ compiler.js > 4.2.2 Compiler inherits from Tapable, so it has hook capabilities (listen for events, trigger events, Webpack is an event stream)
  • 4.3 Ability to mount file reads and writes in the Compiler object:
new NodeEnvironmentPlugin({
    infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
  • 4.4 Mount all plugins in the Compiler object
  • 4.5 mount the default configuration on the Compiler object:
applyWebpackOptionsDefaults(options);
  • 4.6 Compilation and make hook monitoring were started, and the specific procedures were as follows:
> 4.6.1 CreateCompiler () method calls new WebPackOptionsApply (). Process (options, compiler); > 4.6.2 then starts listening on the hook entryOption; > 4.6.3 triggers the execution callback function of hook entryOption: the compilation hook and make hook listening are started
// 4.6.2 Then start listening on the entryOption hook, defined in the entryOptionPlugin. (Note that hooks are defined in the new Compiler) new EntryOptionPlugin().apply(Compiler); // 4.6.3 The entryOption hook entry callback is triggered by the compilation hook and the make hook listener. Callbacks are defined in the entryOptionPlugin. compiler.hooks.entryOption.call(options.context, options.entry); // 4.6.3 To trigger the hook entryOption execution callback, execute the following code; Start listening on the hook make. compiler.hooks.make.tapAsync('SingleEntryPlugin', (compilation, callback) => { const { context, entry, Name} = this console.log(" Make hook listener executes ~~~~~~") // Compilation. addEntry(context, entry, name, callback)})

5. Execution of compiler.run() in webpack() fires a series of hooks

Webpack () are defined in: node_modules/webpack/lib/Compiler. Js

  • 5.1 Triggering the Beforerun hook
  • 5.2 Trigger the Run hook
  • 5.3 Execute the compile method (this method continues to fire a bunch of hooks, see point 6)
  • 5.4 Once the compile method is successful, execute onCompiled to write the module code to the file: writeFile()
/ / the run () is a bunch of hooks according to their sequence in the trigger const run () = = > {/ / 5.1 this trigger beforeRun hook. Hooks. BeforeRun. CallAsync (this, err => { if (err) return finalCallback(err); This.hooks. Run. callAsync(this, err => {if (err) return finalCallback(err); this.readRecords(err => { if (err) return finalCallback(err); // Compile method this.compile(onCompiled); }); }); }); };

6. Run () compile()

The run () are defined in: node_modules/webpack/lib/Compiler. Js

  • 6.1 Trigger hook beforeCompile
  • 6.2 Trigger hook compile
  • 6.3 ThisCompilation trigger hook, Compilation (enable Compilation to create modules)
  • 6.4 trigger hook make (important: make the compilation. Addentry to create a module according to the entry and other configuration items, to perform compilation and other operations)
  • 6.5 Fires the hook finishMake
const params = this.newCompilationParams(); this.hooks.beforeCompile.callAsync(params, err => { // ... this.hooks.compile.call(params); // ThisCompilation = this.Compilation (params); // ThisCompilation = this.Compilation (params); / /... this.hooks.make.callAsync(compilation, err => { // ... this.hooks.finishMake.callAsync(compilation, err => { // ... }); }); });

7. Analysis of AddEntry (the focus of the compile entry)

  • 7.1 Compilation.addentry () is executed when the make hook is triggered, and several parameters are specified before the call: See code comments below
  • 7.2 When the make hook fires, it accepts a compilation pass to addEntry(), which is defined in new compilation()
// SRC /index.js // Options include name, filename, publicPath, etc. // context root path // dep Const {entry, options, context} = this; const dep = EntryPlugin.createDependency(entry, options); // Programmer operation is the value to be passed to the callback function when the make hook fires. AddEntry definition in compilation instantiation of the class compiler. The hooks. Make. TapAsync (" EntryPlugin ", (compilation, callback) => { compilation.addEntry(context, dep, options, err => { callback(err); }); });
/ / this is triggering this. Make hook hooks. Make. CallAsync (compilation, err = > {/ /... })
  • 7.3 After entering the entry function, call => Compilation.js in the following order
addEntry() => _addEntryItem() => addModuleTree() => handleModuleCreation()
  • 7.4 Execute addModuleTree(). In Compilation, we can create a common module object through NormalModuleFactory; Then get it through DependencyFactories
// In addModuleTree(), the ModuleFactory is the normal module object created by NormalModuleFactory, const ModuleFactory = this.dependencyFactories.get(Dep);
  • 7.5 HandleModuleCreation () method is called => Compilation.js in the following order
ModuleFactory factorizeModule() => factorizeQueue() => _factorizeModule() => factory.create()
  • 7.6 Factory.create () creates a module
> 7.6.1 Factory.create () is a member method defined in the NormalModuleFactory (NormalModuleFactory.js) > 7.6.2 Enclosing hooks. BeforeResolve. CallAsync processing the path of the loader compilation, within the callback is triggered hook: Factorize > 7.6.3 enclosing hooks. Factorize. CallAsync loader, then factoryResult passed to the callback ()
  • 7.7 Factory.create () completes and executes the FactoryResult in callback()
Callback () of > 7.7.1 factory.create gets Result (which is actually FactoryResult) const newModule = result.module; > 7.7.2 NewModule passes the above factorizeModule() callback(); // Callback gets the newModule and executes AddModule this.factorizeModule({//... },(err, newModule){// Callback gets newModule //... Enclosing addModule ()})
  • The 7.8 Factory.create () callback calls AddModule (newModule) to create the module
this.factorizeModule => ... => factory.create() => this.addModule () => this.addModule ()
  • 7.9 This.buildModule() compiles the module and passes the result of doBuild to the callback;
The > 7.9.2 doBuild() method implements the core build function in this.modules: In the normalModule.js file, this.buildModule() => build => doBuild

Some of them:

  1. What is the evaluating function? Reference: https://es6.ruanyifeng.com/?s…
const source = {
  get foo() { return 1 }
};
class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'
  1. AST syntax tree, reference: https://segmentfault.com/a/11…

Tapabel library with hooks

What is a tapabel?

Tapable is a library similar to EventEmitter in Node.js, but more focused on triggering and handling custom events. Webpack decouples the implementation from the process through Tapable, with all the concrete implementations in the form of plug-ins.

The classification of tapabel

Synchronous hooks (Synchook)

  1. Fuse hook (syncBialHook)
  2. WaterfallHook (Sync WaterfallHook)
  3. Loop hook (syncLoopHook)

Asynchronous hooks (asyncHook)

For the use of asynchronous hooks, there are three ways to add event listening: TAP TAPAsync TApPromise

  1. Asynchronous Serial Hook (AsyncSeriesHook)
  2. AsyncParallelHook
  3. Asynchronous ParallelBailHook

Hook listening and firing

Const {SyncHook} = require('tapable') // Hook = new Synchook (['name', 'age']) // Hook listener hook. Tap ('fn1'), Function (name, age) {console.log('fn1-- -->', name, age)}) // Hook. Tap ('fn2', function (name, age) {console.log('fn1-- -->', name, age)}) age) { console.log('fn2--->', name, age) }) hook.call('zoe', 18)

For code examples, see:This article Webpack related source code

Related knowledge points:

NodeJS EventEmitter 3. Tapable knowledge points in NodeJS EventEmitter

Special thanks: pull gou education front-end high salary training camp