Webpack design patterns

Webpack source code is a plug-in architecture, and many of its functions are implemented through a number of built-in plug-ins. Webpack wrote its own plug-in system for this purpose, called Tapable, which provides the ability to register and call plug-ins.

Tapable

Tabpable is an event publish subscription plug-in that supports both synchronous and asynchronous; Tabpable inheritance on the class you want to use, and add the event name to the class’s constructor using this.hooks.

 this.hooks = {
            accelerate: new SyncHook(["newSpeed"]),
            break: new SyncHook(),
            calculateRoutes: new AsyncParallelHook(["source"."target"."routesList"])};Copy the code
To subscribe to

To subscribe, you need to get an instance of the class mentioned above and subscribe via the instance object.hook.break.tap.

myCar.hooks.break.tap("WarningLampPlugin", () => warningLamp.on());
Copy the code
release

To trigger this time to call. Hooks. Accelerate. The call can trigger to subscribe to accelerate all monitoring function, newSpeed is incoming parameters.

setSpeed(newSpeed) {
        this.hooks.accelerate.call(newSpeed);
    }
Copy the code
Webpack’s plug-in architecture

Webpack defines a lifecycle from configuration initialization to build completion. Each phase of the lifecycle defines what it means to complete a different function. Webpack’s process is to define a specification that allows both internal and custom plug-ins to be built as long as they follow the specification. As mentioned above, WebPack is a plug-in architecture. Webpack mainly uses Compiler and Compilation classes to control the entire life cycle of WebPack and define the execution process. They all inherit Tabpable and register events that need to be fired for each process in their life cycle through Tabpable. Webpack internally implements a bunch of plugins that implement functionality in the webPack build process, subscribing to events of interest, and calling different subscription functions in the execution process that form the entire WebPack life cycle.

Overview of the WebPack process

Webpack first merges configuration parameters with command line parameters and default parameters, and initializes the required plug-in and configuration plug-in execution environment parameters. After initialization is complete, Compiler’s run is called to actually start the WebPack build process. The WebPack build process includes compile, make, build, SEAL, and Emit phases, and the execution of these phases completes the build process.

  • According to our Webpack configuration, we register the corresponding plug-in and call compile.run to enter the compile phase
  • In the first phase of compilationcompilationFor example, if you want to register a factory for each type of module, you need to register a factory for each type of module
  • Enter themakePhase, will be fromentryStart with two steps:
  • The first step is to call loaders to compile the module’s original code into standard JS code
  • The second step is to call Acorn to parse the JS code and collect the dependencies. Each module records its dependencies, forming a tree of relationships
  • The last callcompilation.sealEnter therenderPhase, which determines how many files to generate based on the previously collected dependencies, and what is the content of each file

Initialize the

Start the

/bin/cli.js’ file of the webpack-CLI plug-in is called from bin/webpack.js, using yargs in cli.js to parse the command-line arguments and merge the options in the configuration file. Then call lib/webpack.js to instantiate the Compiler.

Instantiate the compiler

Instantiating the Compiler is done in lib/webpack.js, first checking whether the configuration parameters are valid; If it is an array, create multiple compiler. Otherwise, create a compiler. To create a compiler, we first call WebpackOptionsDefaulter to merge the passed parameters and default parameters into new options, create the compiler, create read and write file objects, and perform registration configuration plugins. Finally, WebpackOptionsApply initializes a bunch of internal default plug-ins needed for the build.

perform

If the watch is started, call compiler.watch to monitor the build file; otherwise, start compiler.run to build the file.

Compile build

The next step is to formally enter the WebPack build process. The entry point of the WebPack build process is the Run or watch method of the Compiler. After beforeRun and run hook functions are executed in the run method and then compile, you can write the plug-in to process some initialization data before building.

Explain two classes before we get into the build

  • Compiler: This class is the nerve center of WebPack. On the one hand, all configuration data is stored on this instance, and on the other hand, it controls the overall process during the build.
  • Compilation: This class is the CTO of WebPack. All build data generated during the build process is stored in this object. It governs every detail of the build process.

compile

Run instantiates parameters such as normalModuleFactory, then invokes the this.hooks. BeforeCompile event to execute some plugins that need to be processed before compilation. The this.hooks.com compile event is executed last (for example, DllReferencePlugin is executed in the compile hook, where the proxy plug-in is registered); This.hooks.com from running after the execution object instantiation Compilation, and call the this.hooks.com pilation inform interested plug-ins, such as in the Compilation. Adding dependent dependencyFactories factory class operation, etc. The compile phase is mainly to prepare for the make phase, which is to recursively look up building blocks from the entry.

make

Make is a compilation event that is triggered when the compilation is complete. This event usually notifies the EntryOptionPlugin registered with WebpackOptionsApply. Use the entries parameter in the plug-in to create a SingleEntryDependency or MultiEntryDependency dependency, register the same listener on make events for multiple entries, and execute multiple entries in parallel; Then call compilation. AddEntry (context, dep, name, callback) to officially enter the make phase.

Without doing anything in addEntry, we call this._addModulechain, find the factory function in _addModuleChain based on the dependency, and call the create of the factory function to generate an empty MultModule object. We save the MultModule object into the Compilation modules and then execute multModule. build. Since it is an entry module, we call afterBuild without doing anything in the build. In afterBuild determine whether a dependent, and if the end of leaf nodes directly, or call processModuleDependencies method to find the dependence; Because the entry passes in a SingleEntryDependency, the following is a formal look at building from the SingleEntryDependency.

Mentioned above will create a entrance SingleEntryDependency incoming, so the above afterBuild sure there are at least a dependence, processModuleDependencies method is invoked; ProcessModuleDependencies according to the current module. The object dependencies for the module relies on all need to be loaded in the resources and the corresponding factory class, Pass the Module and the dependencies that need to be loaded as parameters to the addModuleDependencies method. In addModuleDependencies, execute all resource dependencies asynchronously. In this case, call create of the dependent factory class to find the absolute path of the resource and the absolute paths of all Loaders on which the resource depends, and create the corresponding Module and return. It then uses the Moduel resource path as a key to determine if the resource has been loaded, and if so returns the resource reference directly to the loaded Module. Otherwise, call this.buildModule to execute module.build to load the resource; When the build is complete, you have the final module processed by the Loader. AfterBuild is called recursively until all modules have been loaded.

DllReferencePlugin

During the make phase webPack will deinstantiate the module according to the create of the normalModuleFactory; After the instantiation moduel to trigger this. Hooks. The module, if the build configuration register DllReferencePlugin plug-in, DelegatedModuleFactoryPlugin will monitor this. Hooks. The module, The plugin determines if the moduel path is in this.options.content and creates a proxy module (DelegatedModule) to override the default module if it exists. The delegateData of the DelegatedModule object is held by the delegateData of the manifest object. Therefore, the DelegatedModule object does not execute bulLED. The DelegatedModule object only needs to import the corresponding ID where it is used.

build

Build is mentioned in the make phase above, but it is not covered in depth because build is executed in the Module object. This section separately describes how build is loaded and executed by the loader and returns after looking for dependencies on that module.

DoBuild is called in the build to load the resource. DoBuild is passed in the resource path and plug-in resources to call the runLoaders method of the Loader-Runner plug-in to load and execute the loader. When finished, the result is returned, and the source code and sourceMap are stored in the _source attribute of the module based on the returned data. The Parser class is called in the callback function of doBuild to generate the AST syntax tree, and the buildModule method is called to return the Compilation class after the dependencies are generated from the AST syntax tree.

Loader-runner Process

The runLoaders method calls iteratePitchingLoaders to recursively look for loaders that execute the pich property; If there are multiple Loaders with pitch attribute, all loaders with pitch attribute are executed in sequence. After execution, all normal Loaders with pitch attribute are executed in reverse order and return result. Loaders without pitch attribute are not executed again. If there is no loader with pitch attribute in loaders, the loader is executed in reverse. The iterateNormalLoaders method completes the execution of a normal loader and returns result after processing all loaders. For example, the following is the execution rule of loader.

Loader execution sequence:

|- a-loader `pitch` |- b-loader `pitch` |- c-loader `pitch` |- requested module is picked up as a dependency |- c-loader  normal execution |- b-loader normal execution |- a-loader normal executionCopy the code
Parser

Acorn is not the scope of this article, so you can read it. After the AST syntax tree is produced in Parser, the walkStatements method is called to analyze the syntax tree. According to the node type of the AST, the type of each node is recursively searched, different logic is executed, and dependencies are created.

MiniCssExtractPlugin

If use in webpack MiniCssExtractPlugin plug-in individually packaged into the CSS file, can deal with the rules in the style of configuration MiniCssExtractPlugin. Loader, when parsing to the CSS file, The pitch method implemented in the MiniCssExtractPlugin loader is first executed, Pitch method for each CSS module to invoke this. _compilation. Create a childCompiler createChildCompiler and childCompilation; ChildCompiler controls the return after the module is loaded and built. Modules built in childCompilation are CssModule and are distinguished using type=’ CSS /mini-extract’.

The MiniCssExtractPlugin in the seal module determines if the module type is’ CSS/mini-tractExtract ‘. Other JS templates that do not recognize modules of type=’ CSS /mini-extract’ are filtered out, thus implementing style separation.

summary

After all resource bulids are completed, the Make phase of WebPack is over. The make phase is the most time-consuming, because the I/O flow operations such as file path parsing and file reading will be performed. When make finishes, all compiled modules are stored in the Compilation Modules array. All modules in modules form a graph.

seal

After the build of all modules and their dependent modules is completed, WebPack will listen to the SEAL event and call each plug-in to encapsulate the built results. Each module and chunk should be collated one by one to generate the compiled source code, merge, split, and generate hash. At the same time, this is the key to code optimization and feature addition during development.

In seal, events of the type optimizeDependencies are first triggered to optimizeDependencies (for example, tree shaking is executed here). Note that asynchrony is not allowed in optimizeDependencies; After the optimization is complete, create chunks based on the entry module. If the entry is single, there is only one chunk, and if the entry is multiple, there are multiple chunks. At the end of this phase, the asynchronous derivative module in the module is found according to the chunk recursive analysis, and a chunk is created for the node based on this module. The difference between the template and the chunk created in the entry is that the template is different. Optimization events such as optimizeModules and optimizeChunks are triggered after all chunks are executed to notify interested plug-ins for optimization. After all the optimization is done, hash chunk and then call createChunkAssets to generate source objects based on the template; All the parsed files are cached using summarizeDependencies, and the plug-in is called to generate soureMap and the final data, as shown in the diagram below for the SEAL phase.

Generating assets

During encapsulation, WebPack calls the Compilation method createChunkAssets to generate the packaged code. The createChunkAssets process is as follows

As you can see from the figure above, there are different chunk processing templates. Choose mainTemplate (entry file packaging template) or chunkTemplate (asynchronous load JS packaging template) based on the chunk entry. Select template according to the template. The template after getRenderManifest generated manifest object, the object of the render method is the chunk to the entrance of the packaging; The only difference between mainTemplate and chunkTemplate is that mainTemplate has the bootsRap code that wepBack executes. Call when invoking render template. RenderChunkModules method, this method creates a ConcatSource container used to store the chunk of source code, This method then iterates over the modules of the current chunk and executes moduleTemplate-render to get the source code for each module. Obtaining the source code in ModuleTemplate-Render triggers the plug-in to encapsulate the code format required by WePack; When all modules are generated, put them into ConcatSource and return. It is stored in the Compilation assets with the output file name of the chunk as the key.

Seal products

All kinds of optimization and final code generated in the SEAL phase will be stored in the Assets attribute of the Compilation. Assets is an object that is stored with the final output name as the key. Each output file corresponds to an output object, as shown in the figure below.

emit

Finally, WebPack calls emitAssets() in the Compiler and asynchronously outputs the file to the corresponding path according to the configuration item in the output, thus ending the entire WebPack packaging process. Note that if you want to process the results, you need to extend the custom plug-in after the emit triggers.

watch

When you configure Watch, webpack-dev-Middleware replaces the Original Webpack outputFileSystem with a MemoryFileSystem (memory-fs plug-in) instance.

monitoring

When executing watch, a Watching object is instantiated. Monitoring and building packaging are both controlled by Watching instances; In the Watching constructor, set the variable delay notification time (200 by default), and then call the _go method; The first webpack build and subsequent rebuilds with file changes are done by _ executing the _go method and calling this.compiler.compile in the __go method to start the compilation. When webPack builds, it fires the _done method and calls the this.watch method inside the _done method, Incoming compilation. FileDependencies and compilation. ContextDependencies need to monitor folder and directory; Watch invokes the this.com piler. WatchFileSystem. The official start of the create listening watch method.

Watchpack

On the this.com piler. WatchFileSystem. Every time the watch will recreate a Watchpack instance, After creation, monitor aggregated events and trigger this.watcher.watch(files.concat(missing), dirs.concat(missing), startTime) methods and close the old Watchpack instance; The watch will call WatcherManager to create a DirectoryWatcher object for each folder in the directory where the file is located. The Watch constructor of the DirectoryWatcher object will call the Chokidar plug-in to listen for the folder. And bind a bunch of trigger events and return watcher; The Watchpack registers a change event for each Watcher, which is triggered whenever a file changes.

Set a timer to delay triggering the change event after the Watchpack plug-in listens to a file change, which solves the problem of frequent triggering when making multiple quick changes.

The trigger

When files change NodeWatchFileStstem of aggregated to monitor events according to the watcher for each monitored file was last modified time, and keep the object in this.com piler. FileTimestamps then trigger _go method to build on.

This. FileTimestamps are assigned to the Compilation object in the compile phase, and all modules are recursively built from the entry in the make phase. Unlike the first build, the Compilation. AddModule method will first retrieve the Module from the cache according to the resource path. If buildTimestamp is longer than buildTimestamp, bulid the module again. Otherwise, recursively look for dependencies on that module.

During the webPack build process, file parsing and module building are time-consuming, so WebPack has cached the absolute file path and module during the build process, and only the changed module will be operated during rebuild, which can greatly improve the rebuild process of WebPack.

conclusion

Just start to read webPack source code in the heart of ten thousand horse pentium, MMMP countless event names, see not over the internal plug-in, various events between the past transfer over; ~ ~ ~ that’s it, ~ ~