Detailed process of webpack construction and webpack Construction

Source: Internet
Author: User

Detailed process of webpack construction and webpack Construction

As a module loading and packaging artifact, you only need to configure a few files and load various loaders to enjoy painless process-based development. However, for a complicated plug-in set such as webpack, its overall process and ideas are still very transparent to us.

This article aims to clarify the webpack command from the command line, or configure npm script and then execute package. what does webpack do for us when the packaged bundle file appears in the project directory through the commands in json.

Test with webpack version for webpack@3.4.1

Webpack. config. js defines related configurations, including entry, output, module, plugins, etc. Execute the webpack command on the command line, and webpack will package and process files according to the configuration in the configuration file, and generate the final packaged file.

Step 1: What happened when running the webpack command? (Bin/webpack. js)

If the webpack command is not found in the global command line when running webpack on the command line, run the local node-modules/bin/webpack. js file.

In bin/webpack. js, The yargs library is used to parse the command line parameters, process the webpack configuration object options, and call processOptions()Function.

// Process compilation. The core function processOptions (options) {// processing in the promise style. if (typeof options) has not been configured yet. then = "function "){...} // process the input options as an array. var firstOptions = []. concat (options) [0]; var statsPresetToOptions = require (".. /lib/Stats. js "). presetToOptions; // set the output options var outputOptions = options. stats; if (typeof outputOptions = "boolean" | typeof outputOptions = "string") {outputOptions = StatsPresetToOptions (outputOptions);} else if (! OutputOptions) {outputOptions ={};}// process various realistic parameters ifArg ("display", function (preset) {outputOptions = statsPresetToOptions (preset );});... // introduce the webpack under lib. js, entry file var webpack = require (".. /lib/webpack. js "); // sets the maximum Error trace stack Error. stackTraceLimit = 30; var lastHash = null; var compiler; try {// compile. The key is to enter lib/webpack. view the Javascript file compiler = webpack (options);} catch (e) {// handle the error var WebpackOptionsV AlidationError = require (".. /lib/WebpackOptionsValidationError "); if (e instanceof WebpackOptionsValidationError) {if (argv. color) console. error ("\ u001b [1 m \ u001b [31 m" + e. message + "\ u001b [39m \ u001b [22 m"); else console. error (e. message); process. exit (1); // eslint-disable-line no-process-exit} throw e;} // display related parameter processing if (argv. progress) {var ProgressPlugin = require (".. /lib/ProgressPlugin "); compiler. Apply (new ProgressPlugin ({profile: argv. profile});} // The compiled callback function compilerCallback (err, stats) {}// process if (firstOptions in watch mode. watch | options. watch) {var watchOptions = firstOptions. watchOptions | firstOptions. watch | options. watch | {}; if (watchOptions. stdin) {process. stdin. on ("end", function () {process. exit (0); // eslint-disable-line}); process. stdin. resume ();} compiler. Watch (watchOptions, compilerCallback); console. log ("\ nWebpack is watching the files... \ N ");} else // call the run () function to officially enter compiler. run (compilerCallback );}
Step 2: Call webpack to return the compiler object process (lib/webpack. js)

As shown in, the key function in lib/webpack. js is webpack, which defines compilation-related operations.

"Use strict"; const Compiler = require (". /Compiler "); const MultiCompiler = require (". /MultiCompiler "); const NodeEnvironmentPlugin = require (". /node/NodeEnvironmentPlugin "); const WebpackOptionsApply = require (". /WebpackOptionsApply "); const WebpackOptionsDefaulter = require (". /WebpackOptionsDefaulter "); const validateSchema = require (". /validateSchema "); const WebpackOptionsValidationError = require (". /WebpackOptionsValidationError "); const webpackOptionsSchema = require (".. /schemas/webpackOptionsSchema. json "); // The core method. This method is called and the Compiler Instance Object compilerfunction webpack (options, callback) {...} is returned ){...} exports = module. exports = webpack; // set the common attributes of a webpack object. webpackOptionsDefaulter = WebpackOptionsDefaulter; webpack. webpackOptionsApply = WebpackOptionsApply; webpack. compiler = Compiler; webpack. multiCompiler = MultiCompiler; webpack. nodeEnvironmentPlugin = NodeEnvironmentPlugin; webpack. validate = validateSchema. bind (this, webpackOptionsSchema); webpack. validateSchema = validateSchema; webpack. webpackOptionsValidationError = WebpackOptionsValidationError; // exposes some plug-ins: function exportPlugins (obj, mappings ){...} exportPlugins (exports ,{...}); exportPlugins (exports. optimize = {},{...});

Next, let's take a look at what operations are mainly defined in the webpack function.

// Core method. Call this method to return the Compiler Instance Object compilerfunction webpack (options, callback) {// verify that the const webpackOptionsValidationErrors = validateSchema (webpackOptionsSchema, options) meets the format ); if (webpackOptionsValidationErrors. length) {throw new WebpackOptionsValidationError (webpackOptionsValidationErrors);} let compiler; // when the input options are arrays, MultiCompiler is called for processing, no such configuration as if (Array. isArray (options) {compiler = New MultiCompiler (options. map (options => webpack (options);} else if (typeof options = "object") {// configure the default parameter new WebpackOptionsDefaulter () for options (). process (options); // initialize a Compiler instance compiler = new Compiler (); // set the default value of context to the current directory of the process, and the absolute path is compiler. context = options. context; // define the options attribute of compiler. options = options; // Node environment plug-in, where inputFileSystem, outputFileSystem, and watchFile of compiler are set. System, and defines the before-run hook function new NodeEnvironmentPlugin (). apply (compiler); // apply each plug-in if (options. plugins & Array. isArray (options. plugins) {compiler. apply. apply (compiler, options. plugins);} // call the environment Plugin compiler. applyPlugins ("environment"); // call the after-environment Plugin compiler. applyPlugins ("after-environment"); // process the compiler object and call some necessary plug-ins compiler. options = new WebpackOptionsApply (). process (options, Compiler);} else {throw new Error ("Invalid argument: options");} if (callback) {if (typeof callback! = "Function") throw new Error ("Invalid argument: callback"); if (options. watch = true | (Array. isArray (options) & options. some (o => o. watch) {const watchOptions = Array. isArray (options )? Options. map (o => o. watchOptions | {}): (options. watchOptions | {}); return compiler. watch (watchOptions, callback);} compiler. run (callback);} return compiler ;}

The webpack function mainly performs the following two operations,

  • Instantiate the Compiler class. This class inherits from the Tapable class, which is a plug-in Architecture Based on publishing and subscription. Webpack is the whole process of implementing the publish/subscribe mode based on Tapable. In Tapable, register the plug-in name and corresponding callback functions through plugins, and call the callback registered under a plug-in different ways through functions such as apply, applyPlugins, applyPluginsWater, and applyPluginsAsync.
  • Use WebpackOptionsApply to process the webpack compiler object.compiler.applySome necessary plug-ins are called. Some plug-ins are registered in these plug-ins. In the subsequent compilation process, some plug-ins are called to process some processes.
Step 3: Call compiler's run process (Compiler. js)

Run () call

The run function mainly triggers the before-run event and the run event in the callback function of the before-run event. The run event calls the readRecord function to read files and callcompile()Compile the function.

Compile () call

The compile function defines the compilation process, which mainly includes the following processes:

  • Create compilation Parameters
  • Trigger the before-compile event,
  • Trigger the compile event and start compilation.
  • Create a compilation object that is responsible for the specific details of the compilation process.
  • Trigger the make event, start creating a module, and analyze its dependencies.
  • Depending on the type of the entry configuration, it is determined which plug-in calls the make Event Callback. For example, a single entry calls a callback function registered by the make event in SingleEntryPlugin. js.
  • Call the addEntry function of the compilation object to create modules and dependencies.
  • In the make Event Callback Function, the result is encapsulated by seal.
  • The onCompiled callback function defined in the run method is called to complete the emit process and write the result to the target file.

Compile Function Definition

Compile (callback) {// create compilation parameters, including the module factory and the array of compilation dependency parameters const params = this. newCompilationParams (); // triggers the before-compile event to start the entire compilation process. applyPluginsAsync ("before-compile", params, err => {if (err) return callback (err); // triggers the compile event this. applyPlugins ("compile", params); // construct a compilation object. The compilation object is responsible for the compilation details. const compilation = this. newCompilation (params); // triggers the make event. The callback function for listening to the make event is registered in different entryplugins, such as singleEntryPlugin this. applyPluginsParallel ("make", compilation, err => {if (err) return callback (err); compilation. finish (); compilation. seal (err => {if (err) return callback (err); this. applyPluginsAsync ("after-compile", compilation, err => {if (err) return callback (err); return callback (null, compilation );});});});});}

[Problem] after a make event is triggered, which plug-ins register the make event and get the chance to run it?

Taking the configuration of a single entry as an example, the EntryOptionPlugin defines the plug-in to which the entries of different configurations should be called for resolution. The corresponding make Event Callback Function is registered in the portal plug-in of different configurations and called after the make event is triggered.

As follows:

The apply method of a plug-in is the core method of a plug-in. When a plug-in is called, The apply method is called.

The EntryOptionPlugin plug-in is called in webpackOptionsApply. It internally defines the plug-in used to parse the entry file.

Const SingleEntryPlugin = require (". /SingleEntryPlugin "); const MultiEntryPlugin = require (". /MultiEntryPlugin "); const DynamicEntryPlugin = require (". /DynamicEntryPlugin "); module. exports = class EntryOptionPlugin {apply (compiler) {compiler. plugin ("entry-option", (context, entry) => {function itemToPlugin (item, name) {if (Array. isArray (item) {return new MultiEntryPlugin (context, item, name);} else {return new SingleEntryPlugin (context, item, name );}} // determine the type of the entry field to call different entry plug-ins to process if (typeof entry = "string" | Array. isArray (entry) {compiler. apply (itemToPlugin (entry, "main");} else if (typeof entry = "object") {Object. keys (entry ). forEach (name => compiler. apply (itemToPlugin (entry [name], name);} else if (typeof entry = "function") {compiler. apply (new DynamicEntryPlugin (context, entry) ;}return true ;});}};

When the entry-option event is triggered, the EntryOptionPlugin plug-in does the following:

Determine the type of the entry, which is determined by the entry field. It corresponds to three cases where the entry field is string object function.

Different types call different plug-ins to process portal configurations. The general processing logic is as follows:

  • The entry of the array type calls the multiEntryPlugin plug-in for processing, corresponding to the multi-entry scenario.
  • The function entry calls the DynamicEntryPlugin plug-in for processing, corresponding to the asynchronous chunk scenario.
  • String-type entry or object-type entry, which calls SingleEntryPlugin for processing, corresponds to the single-entry scenario.

[Problem] When is the entry-option event triggered?

The following code shows that in WebpackOptionsApply. js, The EntryOptionPlugin of the processing entry is called first, and then the entry-option event is triggered to call different types of entry Processing plug-ins.

Note:The process of calling the plug-in is the process of registering events and callback functions.

WebpackOptionApply. js

// Call compiler. apply (new EntryOptionPlugin (); compiler. applypluginsbilresult ("entry-option", options. context, options. entry );

As mentioned above, when a make event is triggered, the corresponding callback logic is registered in the plug-ins of different configuration entries. The following uses SingleEntryPlugin as an example to describe the entire process from the make event being triggered to the end of compilation.

SingleEntryPlugin. js

Class SingleEntryPlugin {constructor (context, entry, name) {this. context = context; this. entry = entry; this. name = name;} apply (compiler) {// compilation event is triggered when the Compilation object is initialized. plugin ("compilation", (compilation, params) => {const normalModuleFactory = params. normalModuleFactory; compilation. dependencyFactories. set (SingleEntryDependency, normalModuleFactory) ;}); // make event is executed in comp The compiler. plugin ("make", (compilation, callback) =>{ const dep = SingleEntryPlugin. createDependency (this. entry, this. name); // The Key to Compilation. Call addEntry in Compilation to add the entry to the Compilation process. Compilation. addEntry (this. context, dep, this. name, callback) ;}) ;}static createDependency (entry, name) {const dep = new SingleEntryDependency (entry); dep. loc = name; return dep;} module. exports = SingleEntryPlugin;

Compilation is responsible for compiling details, including how to create modules and module dependencies, and generating js Based on templates. Such as addEntry, buildModule, and processModuleDependencies.

Compilation. js

AddEntry (context, entry, name, callback) {const slot = {name: name, module: null}; this. preparedChunks. push (slot); // Add the module on the chunk to depend on this. _ addModuleChain (context, entry, (module) => {entry. module = module; this. entries. push (module); module. issuer = null ;}, (err, module) =>{ if (err) {return callback (err) ;}if (module) {slot. module = module;} else {const idx = this. preparedChunks. indexOf (slot); this. preparedChunks. splice (idx, 1) ;}return callback (null, module );});}
_ AddModuleChain (context, dependency, onModule, callback) {const start = this. profile & Date. now ();... // obtain the corresponding module factory based on the module type and create the module const moduleFactory = this. dependencyFactories. get (dependency. constructor );... // create a module and pass the module as a parameter to the callback function moduleFactory. create ({contextInfo: {issuer: "", compiler: this. compiler. name}, context: context, dependencies: [dependency]}, (err, module) =>{ if (err ){ Return errorAndCallback (new EntryModuleNotFoundError (err);} let afterFactory; if (this. profile) {if (! Module. profile) {module. profile ={};} afterFactory = Date. now (); module. profile. factory = afterFactory-start;} const result = this. addModule (module); if (! Result) {module = this. getModule; onModule; if (this. profile) {const afterBuilding = Date. now (); module. profile. building = afterBuilding-afterFactory;} return callback (null, module);} if (result instanceof Module) {if (this. profile) {result. profile = module. profile;} module = result; onModule (module); moduleReady. call (this); return;} onModule (module); // construct a module, including calling loader to process files, generating AST using acorn, and traversing AST to collect dependency on this. buildModule (module, false, null, null, (err) =>{ if (err) {return errorAndCallback (err);} if (this. profile) {const afterBuilding = Date. now (); module. profile. building = afterBuilding-afterFactory;} // starts to process the collected dependency moduleReady. call (this) ;}); function moduleReady () {this. processModuleDependencies (module, err =>{ if (err) {return callback (err) ;}return callback (null, module );});}});}

_ AddModuleChain mainly does the following:

  • Call the corresponding module factory class to create a module
  • BuildModule: starts to build modules and collect dependencies. The most time-consuming step in the construction process is to call the loader processing module and dependencies between modules. The process of generating an AST using acorn traverses the AST loop collection and building a dependency module. Here, you can learn more about the principles of using the loader processing module in webpack.
Step 4: After the module build is complete, use seal to process the module and chunk, including merging and splitting.

The Compilation seal function is called in the make Event Callback Function.

Seal (callback) {const self = this; // triggers the seal event and provides the seal execution time self in other plug-ins. applyPlugins0 ("seal"); self. nextFreeModuleIndex = 0; self. nextFreeModuleIndex2 = 0; self. preparedChunks. forEach (preparedChunk => {const module = preparedChunk. module; // Save the module in the origins of the chunk, and the origins Save the module information const chunk = self. addChunk (preparedChunk. name, module); // create an entrypoint const entrypoint = self. entrypoints [chun K. name] = new Entrypoint (chunk. name); // Save the chunk created by the chunk to entrypoint, and save the instance of this entrypoint to entrypoint in the entrypoints of the chunk. unshiftChunk (chunk); // Save the module to the chunk's _ modules array. addModule (module); // records the chunk information module on the module instance. addChunk (chunk); // defines the chunk's entryModule attribute chunk. entryModule = module; self. assignIndex (module); self. assignDepth (module); self. processDependenciesBlockForChunk (module, chunk) ;}); Self. sortModules (self. modules); self. applyPlugins0 ("optimize"); while (self. applypluginsbilresult1 ("optimize-modules-basic", self. modules) | self. applypluginsbilresult1 ("optimize-modules", self. modules) | self. applypluginsbilresult1 ("optimize-modules-advanced", self. modules) {/* empty */} self. applyPlugins1 ("after-optimize-modules", self. modules); while (self. applypluginsbilresult1 (" Optimize-chunks-basic ", self. chunks) | self. applypluginsbilresult1 ("optimize-chunks", self. chunks) | self. applypluginsbilresult1 ("optimize-chunks-advanced", self. chunks) {/* empty */} self. applyPlugins1 ("after-optimize-chunks", self. chunks); self. applyPluginsAsyncSeries ("optimize-tree", self. chunks, self. modules, function sealPart2 (err) {if (err) {return callback (err);} self. applyPlugins2 ("After-optimize-tree", self. chunks, self. modules); while (self. applypluginsbilresult ("optimize-chunk-modules-basic", self. chunks, self. modules) | self. applypluginsbilresult ("optimize-chunk-modules", self. chunks, self. modules) | self. applypluginsbilresult ("optimize-chunk-modules-advanced", self. chunks, self. modules) {/* empty */} self. applyPlugins2 ("after-optimize-chunk-modules", self. chunk S, self. modules); const shouldRecord = self. applypluginsbilresult ("shocould-record ")! = False; self. applyPlugins2 ("revive-modules", self. modules, self. records); self. applyPlugins1 ("optimize-module-order", self. modules); self. applyPlugins1 ("advanced-optimize-module-order", self. modules); self. applyPlugins1 ("before-module-ids", self. modules); self. applyPlugins1 ("module-ids", self. modules); self. applyModuleIds (); self. applyPlugins1 ("optimize-module-ids", self. modules); self. applyPlugi Ns1 ("after-optimize-module-ids", self. modules); self. sortItemsWithModuleIds (); self. applyPlugins2 ("revive-chunks", self. chunks, self. records); self. applyPlugins1 ("optimize-chunk-order", self. chunks); self. applyPlugins1 ("before-chunk-ids", self. chunks); self. applyChunkIds (); self. applyPlugins1 ("optimize-chunk-ids", self. chunks); self. applyPlugins1 ("after-optimize-chunk-ids", self. chunks); self. sort ItemsWithChunkIds (); if (shouldRecord) self. applyPlugins2 ("record-modules", self. modules, self. records); if (shouldRecord) self. applyPlugins2 ("record-chunks", self. chunks, self. records); self. applyPlugins0 ("before-hash"); // create hash self. createHash (); self. applyPlugins0 ("after-hash"); if (shouldRecord) self. applyPlugins1 ("record-hash", self. records); self. applyPlugins0 ("before-module-assets"); self. cr EateModuleAssets (); if (self. applypluginsbilresult ("shocould-generate-chunk-assets ")! = False) {self. applyPlugins0 ("before-chunk-assets"); // use template to create the final js Code self. createChunkAssets ();} self. applyPlugins1 ("additional-chunk-assets", self. chunks); self. summarizeDependencies (); if (shouldRecord) self. applyPlugins2 ("record", self, self. records); self. applyPluginsAsync ("additional-assets", err =>{ if (err) {return callback (err);} self. applyPluginsAsync ("optimize-chunk-assets", self. chunks, err =>{ if (err) {return callback (err);} self. applyPlugins1 ("after-optimize-chunk-assets", self. chunks); self. applyPluginsAsync ("optimize-assets", self. assets, err =>{ if (err) {return callback (err);} self. applyPlugins1 ("after-optimize-assets", self. assets); if (self. applypluginsbilresult ("need-additional-seal") {self. unseal (); return self. seal (callback);} return self. applyPluginsAsync ("after-seal", callback );});});});});}

In seal, we can find that many different plug-ins are called, mainly to operate some plug-ins of chunk and module to generate the final source code. CreateHash is used to generate the hash, createChunkAssets is used to generate the chunk source code, and createModuleAssets is used to generate the Module source code. In createChunkAssets, the system determines whether the chunk is the entry chunk. The entry chunk is generated using the mainTemplate. Otherwise, the chunkTemplate is used to generate the chunkset.

Step 5: input the generated code to the specified output location through emitAssets

The 'run' method in compiler defines the compile callback function onCompiled, which is called after compilation. EmitAsset is called in the callback function, which triggers the emit event and writes the file to a specified location in the file system.

Summary

The webpack source code uses Tapable to control its event stream, and exposes some event hooks to plugin during the webpack construction process through the plugin mechanism, so that developers can customize packaging by writing corresponding plug-ins.

Well, the above is all the content of this article. I hope the content of this article has some reference and learning value for everyone's learning or work. If you have any questions, please leave a message to us, thank you for your support.

References:

Webpack Process

Webpack source code parsing

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.