Webpack principle

View all document pages:
The whole development of the stackFor more information.


Original link:
Webpack principle, the original advertising modal box block, reading experience is not good, so organize the cost of text, easy to find.

Summary of working principle

The basic concept

Before you understand the Webpack principle, you need to master the following core concepts to facilitate your understanding:

  • EntryWebpack performs the first step in the build starting with Entry, which can be abstracted into input.
  • Module: module, in Webpack everything is a module, a module corresponds to a file. Webpack recursively finds all dependent modules starting from the configured Entry.
  • Chunk: Code block: a Chunk is composed of multiple modules for code merging and splitting.
  • Loader: module converter, used to convert the original content of the module into new content as required.
  • Plugin: Extension plug-in, which broadcasts corresponding events at specific times in the Webpack build process. The plug-in can listen for these events and do corresponding things at specific times.

Generalization process

The running flow of Webpack is a sequential process, from start to finish:

  1. Initialization parameters: read and merge parameters from configuration files and Shell statements to get the final parameters;
  2. Start compiling: initialize the Compiler object using the parameters obtained in the previous step, load all the configured plug-ins, and execute the object’s run method to start compiling.
  3. Determine entry: find all entry files according to the entry in the configuration;
  4. Module compilation: Starting from the entry file, call all configured Loader to translate the module, find out the module that the module depends on, and then recurse this step until all the entry dependent files have gone through this step;
  5. Complete module compilation: After using Loader to translate all modules in step 4, obtain the final content of each module after translation and the dependencies between them;
  6. Output resources: assemble chunks containing multiple modules one by one according to the dependency between the entry and modules, and then convert each Chunk into a separate file and add it to the output list. This step is the last chance to modify the output content.
  7. Output complete: After determining the output content, determine the output path and file name based on the configuration, and write the file content to the file system.

In the above process, Webpack will broadcast a specific event at a specific point in time, the plug-in will execute a specific logic after listening for the event of interest, and the plug-in can call the API provided by Webpack to change the running result of Webpack.

Process details

The Webpack build process can be divided into the following three phases:

  1. Initialization: start build, read and merge configuration parameters, load Plugin, instantiate Compiler.
  2. Compilation: issued from Entry, the corresponding Loader is successively called for each Module to translate the file content, and then the Module depends on the Module is found, and the compilation process is carried out recursively.
  3. Output: Combine compiled modules into chunks, convert chunks into files, and output them to the file system.

If only one build is performed, the above phases will be executed one at a time in sequence. However, when listening mode is enabled, the flow changes to the following:

Many events occur during each major phase, and Webpack broadcasts these events for Plugin use, as described below.

Initialization phase

The event name explain
Initialization parameter Read and merge parameters from configuration files and Shell statements to arrive at the final parameters. This process also executes plug-in instantiation statements in the configuration filenew Plugin().
instantiationCompiler Class with the parameters obtained in the previous stepCompilerInstance,CompilerResponsible for file listening and start compiling.CompilerExample contains the completeWebpackConfiguration, global only oneCompilerInstance.
Load the plug-in Call the plug-ins in turnapplyMethod that allows the plug-in to listen for all subsequent event nodes. Pass it to the plug-in as wellcompilerInstance reference to facilitate plug-in adoptioncompilerCall the API provided by Webpack.
environment Start applying node.js-style file systems to Compiler objects to facilitate subsequent file finding and reading.
entry-option Read configuredEntrysFor eachEntryInstantiate a correspondingEntryPlugin, for the followingEntryPrepare for the recursive parsing work.
after-plugins Call all the built-in and configured plug-insapplyMethods.
after-resolvers Initialization is complete according to the configurationresolver.resolverIs responsible for finding files in the file system at the specified path.
The blank space The blank space
The blank space The blank space
The blank space The blank space

Compilation phase

The event name explain
run Start a new compilation.
watch-run andrunSimilarly, the difference is that it starts compilation in listening mode, and in this event you can get information about which files have changed causing a new compilation to restart.
compile This event is used to tell the plug-in that a new build is about to start, and the plug-in is taggedcompilerObject.
compilation whenWebpackRun in development mode, every time a file change is detected, a new oneCompilationWill be created. aCompilationObject contains the current module resources, build resources, changing files, and so on.CompilationObject also provides many event callbacks for plug-ins to extend.
make A newCompilationIs created and is about to be created fromEntryStart reading files based on file type and configurationLoaderCompile the file, and then find out the file that the file depends on after compilation, recursive compilation and parsing.
after-compile At a timeCompilationThe execution is complete.
invalid This event is triggered when a file does not exist, a file compilation error, etc., and does not cause Webpack to exit.
The blank space The blank space
The blank space The blank space

During compilation, the most important events are the Compilation events, because the compilation stage calls the Loader to complete the conversion operation of each module. The compilation stage also includes many small events, which are as follows:

The event name explain
build-module Use the corresponding Loader to convert a module.
normal-module-loader After converting a module with Loader, useacornParses the transformed content and outputs the corresponding abstract syntax tree (AST) to facilitate post-Webpack code analysis.
program Start from the configured entry module, analyze its AST, and add it to the list of dependent modules when importing other module statements such as require. At the same time, perform recursive analysis on the newly identified dependent modules, and finally figure out the dependencies of all modules.
seal After all modules and their dependent modules are converted by Loader, Chunk generation starts based on the dependency relationship.

The output stage

The event name explain
should-emit All the files that need to be exported have been generated, asking the plug-in which files need to be exported and which files do not.
emit Once you have determined which files to output, execute the file output, which can be retrieved and modified here.
after-emit The file output is complete.
done Successfully complete the compilation and output process once completed.
failed If you encounter an exception in the compile and output process that causes Webpack to exit, you will go directly to this step, and the plug-in can obtain the specific cause of the error from this event.

In the output stage, the converted results of each module and their dependence relations have been obtained, and related modules have been combined together to form a Chunk. In the output stage, the corresponding template will be used to generate the final file content to be output according to the type of Chunk.

Output file analysis

Although you learned how to use Webpack in previous chapters and have a rough idea of how it works, have you ever wondered what the bundle.js output from Webpack looks like? Why are the original module files merged into a single file? Why does bundle.js run directly in a browser? This section will clarify the above questions.

Take a look at the bundle.js file, built from the simplest project to install and use, as follows:

See the Pen bundle.js by whjin (@whjin) on CodePen.

The above seemingly complex code is actually an immediate function, which can be abbreviated as:

Function (modules) {function(webpack_require__() {} function(modules) {function(webpack_require__) {} })([/* Array of all modules */])Copy the code

The reason bundle.js can run directly in the browser is that the output file defines a loading function that can be executed in the browser through the __webpack_require__ function to simulate the Require statement in Node.js.

The reason why separate module files are merged into a single bundle.js file is that browsers cannot load module files locally as quickly as Node.js, and must load files that are not yet available through network requests. If there are many modules, the load time will be very long, so put all modules in an array and perform a network load.

If you look closely at the implementation of the __webpack_require__ function, you will also find that Webpack is cache optimized: modules that have been loaded will not be executed a second time, the results of the execution will be cached in memory, and when a module is accessed a second time, the cached return value will be read directly from memory.

Output when splitting code

For example, change the source main.js to the following:

Js import('./show').then((show) => {// execute show function show('Webpack'); });Copy the code

The rebuild outputs two files, the execution entry file bundle.js and the asynchronous loading file 0.bundle.js.

The contents of 0. Bundle. js are as follows:

/ / load in this file (0. Bundle. Js) contained in the module webpackJsonp (/ / in the other file storage module ID [0], / / this file contains module [/ / show. The corresponding module will be js (function (the module, exports) { function show(content) { window.document.getElementById('app').innerText = 'Hello,' + content;  } module.exports = show; }) ] );Copy the code

Bundle.js contains the following contents:

See the Pen bundle.js by whjin (@whjin) on CodePen.

Bundle.js is very similar to bundle.js, except that:

  • One more__webpack_require__.eLoad files corresponding to chunks that are separated and need to be loaded asynchronously;
  • One morewebpackJsonpThe install function is used to install modules from asynchronously loaded files.

The output file for CommonsChunkPlugin extraction is the same as the output file for asynchronous loading, with __webpack_require__. E and webpackJsonp. The reason is that both extracting common code and asynchronous loading are essentially code splitting.

Write the Loader

Loader is like a translator, it can convert the source file into a new output, and a file can be chained through multiple translators.

Take handling SCSS files as an example:

  • The SCSS source code will be handed over firstsass-loaderConvert SCSS to CSS;
  • thesass-loaderOutput CSS tocss-loaderProcessing, find out the CSS dependent resources, compression CSS, etc.
  • thecss-loaderOutput CSS tostyle-loaderProcessing into JavaScript code loaded through a script;

It can be seen that the preceding processes need to be executed in sequence, namely, sas-loader, CSS-loader, and style-loader. The Webpack configuration is as follows:

See the pen Loader by whjin (@whjin) on CodePen.

The duties of a Loader

As can be seen from the above example, a Loader has a single responsibility and only needs to complete one conversion. If a source file needs to go through a multi-step conversion to work properly, use multiple Loaders to convert it. When multiple Loaders are called to convert a file, each Loader will execute it in chained order. The first Loader will get the original content to be processed, and the results of the previous Loader will be passed to the next Loader for subsequent processing. The final Loader returns the processed final result to the Webpack.

So, when you develop a Loader, keep its responsibilities simple and you only care about the inputs and outputs.

Loader basis

Since Webpack runs on Node.js, a Loader is essentially a Node.js module that needs to export a function. The job of this exported function is to get the original content before processing, perform processing on the original content, and return the processed content.

A simple Loader source is as follows:

Module.exports = function(source) {// this function returns the original content of the file passed to the Loader. The Loader does not convert any return source. };Copy the code

Since Loader runs in Node.js, you can call any node.js API, or install a third-party module to call:

const sass = require('node-sass');
module.exports = function(source) {
  return sass(source);
};
Copy the code

Loader advanced

The above is just the simplest Loader. Webpack also provides some apis for Loader to call.

Get the Loaderoptions

In the top Webpack configuration that processes SCSS files, the options parameter is passed to csS-Loader to control csS-Loader. How to obtain the options passed by the user in the Loader written by myself? Need to do this:

const loaderUtils = require('loader-utils'); Module.exports = function(source) {// getOptions from user to current Loader const options = loaderutils.getoptions (this); return source; };Copy the code

Return other results

All of the above loaders only return the converted content, but some scenarios require returning something other than the content.

For example, using Babel-Loader to convert ES6 code, it also needs to output the corresponding Source Map of the converted ES5 code to facilitate debugging of the Source code. To return the Source Map to Webpack along with the ES5 code, write:

Module.exports = function(source) {this.callback(null, source, sourceMaps); // module.exports = function(source) {this.callback(null, source, sourceMaps); // When you use this.callback to return the content, the Loader must return undefined, // to let Webpack know that the Loader returns the result in this.callback, Not return within return; };Copy the code

The this.callback is an API injected by Webpack into Loader to facilitate communication between Loader and Webpack. This. Callback can be used as follows:

This callback (/ / when unable to convert the original content, give Webpack returns an Error err: Error | null, / / the original content converted content the content: String | Buffer, / / for the converted concluded the content of the original content of the Source Map, convenient debug sourceMap? AbstractSyntaxTree: SourceMap, // If the AST is generated from the original content, you can return the AST, so that the Loader that needs the AST can reuse the AST, so as to avoid the repeated generation of AST and improve the performance of abstractSyntaxTree? : AST );Copy the code

Source Map generation is time consuming and is usually done in development environments and not in other environments to speed up builds. For this Webpack provides Loader
this.sourceMapThis API tells Loader whether the user needs the Source Map in the current build environment. If you’re writing a Loader that generates a Source Map, take this into account.

Synchronous and asynchronous

Loaders can be synchronous or asynchronous. The loaders described above are synchronous because their conversion processes are synchronous and the conversion results are returned after completion. But there are some scenarios where the transformation step can only be done asynchronously, such as when you need to make a network request to get the result, which would block the entire build and cause it to be very slow if you do it synchronously.

When the transition step is asynchronous, you can do this:

Module.exports = function(source) {var callback = this.async(); // module.exports = function(source) {var callback = this.async(); SomeAsyncOperation (source, function(err, result, sourceMaps, ast) {// Callback (err, result, sourceMaps, ast); }); };Copy the code

Working with binary data

By default, Webpack passes the original content to Loader in UTF-8 format. However, in some scenarios, the Loader processes binary files instead of text files. For example, file-loader, Webpack needs to import binary data to the Loader. To do this, you need to write Loader like this:

Module. exports = function(source) {// exports.raw === true, Source instanceof Buffer === true; // Loader can also return the type of Buffer // in exports.raw! If == true, the Loader can return a result of type Buffer. }; // The exports.raw property tells Webpack whether the Loader needs binary data module.exports.raw = true;Copy the code

The most critical line above is the last line of module.exports.raw = true; Loader can only get the string without the line.

Cache acceleration

In some cases, some transformations are computationally expensive and time-consuming, and if the transformations are repeated with each build, the build will be slow. For this reason, Webpack caches the results of all Loader processes by default, which means that the corresponding Loader is not re-invoked to perform the conversion operation if the file to be processed or its dependent file has not changed.

If you want Webpack not to cache the Loader’s results, you can do this:

Module.exports = function(source) {this.cacheable(false); return source; };Copy the code

Other Loader API

In addition to the Webpack API mentioned above that can be called in the Loader, there are the following common apis:

  • this.context: Directory of the file being processed, if the file being processed by Loader is/src/main.js,this.contextIs equal to/src.
  • this.resource: Full request path for the file currently being processed, includingquerystring, e.g./src/main.js? name=1.
  • this.resourcePath: Path to the file being processed, for example/src/main.js.
  • this.resourceQuery: Current file processingquerystring.
  • this.target: equals Target in the Webpack configuration.
  • this.loadModule: If the Loader obtains the result of the current file based on the processing result of other files when processing a file, the Loader can passthis.loadModule(request: string, callback: function(err, source, sourceMap, module))To getrequestProcessing result of the corresponding file.
  • this.resolve: likerequireStatement to get the full path to the specified file asresolve(context: string, request: string, callback: function(err, result: string)).
  • this.addDependency: Adds a dependent file to the current file so that when the dependent file changes, Loader will be invoked to process the file again. Use method:addDependency(file: string).
  • this.addContextDependencyAnd:addDependencySimilar, butaddContextDependencyAdd the entire directory to the dependency of the file currently being processed. Use method:addContextDependency(directory: string).
  • this.clearDependencies: Clears all dependencies on the file being processedclearDependencies().
  • this.emitFile: Outputs a file as followsemitFile(name: string, content: Buffer|string, sourceMap: {... }).

Loading a local Loader

In the process of Loader development, in order to test whether the Loader written can work properly, the Loader may be invoked only after it is configured in Webpack. In the preceding sections, loaders are installed through Npm, and the Loader name is directly used to use Loader. The code is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css/,
        use: ['style-loader'],
      },
    ]
  },
};
Copy the code

Using a locally developed Loader in the same way is a hassle because you need to make sure that the source code for the Loader is in node_modules. To do this, you need to publish the Loader you wrote to the Npm repository and then install it to the local project.

There are two convenient ways to solve the above problems, which are as follows:

Npm link

Npm link is used to develop and debug local Npm modules. It can link the source code of a local module under development to the node_modules directory of the project without releasing the module, so that the project can directly use the local Npm module. Because it is implemented by soft link, the Npm module code is edited locally, and the edited code can be used in the project.

The steps to complete Npm Link are as follows:

  • Ensure that the local Npm module under development (that is, the Loader under developmentpackage.jsonThe configuration is correct.
  • Execute in the root directory of the local Npm modulenpm linkTo register local modules globally.
  • Execute in the project root directorynpm link loader-nameLink the local Npm module registered globally in step 2 to the project’snode_moduelsBelow, among themloader-nameIs in step 1package.jsonThe module name configured in the file.

After linking the Loader to the project, you can use the local Loader as if it were a real Npm module.

ResolveLoader

ResolveLoader is used to configure how Webpack finds the Loader. By default, only node_modules is searched, and resolveloader.modules needs to be modified in order for Webpack to load the Loader placed in the local project.

If the local Loader is in./loaders/loader-name in the project directory, the following configuration is required:

Module. exports = {resolveLoader:{module: ['node_modules','./loaders/'],}}Copy the code

After the above configuration is added, Webpack will first look for Loader in node_modules, and if it can’t find Loader, it will look for Loader in./loaders/.

In actual combat

The above mentioned many theories, the next from the actual, to write a Loader to solve the actual problem.

Common-require-loader (); common-require-loader ();

// @require '.. /style/index.css'Copy the code

Converted to:

require('.. /style/index.css');Copy the code

The Loader is used to correctly load JavaScript written for Fis3 that contains annotated loading dependent CSS files.

The usage of Loader is as follows:

module.exports = { module: { rules: [ { test: /\.js$/, use: [' common-require-loader '], // convert include to common-require-loader for JavaScript files with fis3 CSS import syntax: [path.resolve(__dirname, 'node_modules/imui')] } ] } };Copy the code

The Loader implementation is very simple, the complete code is as follows:

Function replace(source) {// use the re to replace // @require '.. /style/index.css' convert to require('.. /style/index.css'); return source.replace(/(\/\/ *@require) +(('|").+('|")).*/, 'require($2); '); } module.exports = function (content) { return replace(content); };Copy the code

Writing a Plugin

Webpack makes it more flexible through the Plugin mechanism to adapt to various application scenarios. A number of events are broadcast during the life cycle of a Webpack run, and the Plugin can listen for these events and change the output when appropriate through the API provided by Webpack.

A basic Plugin code looks like this:

Class BasicPlugin{// Get the configuration passed in by the user to the plugin from the constructor(options){} // Webpack calls the Apply method of the BasicPlugin instance to the plugin instance Compiler object apply(Compiler){compiler.plugin('compilation',function(compilation) {})}} // export plugin module.exports = BasicPlugin;Copy the code

When using this Plugin, the configuration code is as follows:

const BasicPlugin = require('./BasicPlugin.js');
module.export = {
  plugins:[
    new BasicPlugin(options),
  ]
}
Copy the code

After Webpack starts, new BasicPlugin(options) is executed to initialize a BasicPlugin to get an example during the configuration reading process. After initializing the Compiler object, basicPlugin.apply(Compiler) is called to pass the compiler object to the plug-in instance. Once the compiler object is acquired, the plug-in instance can listen for events broadcast by Webpack through compiler.plugin(event name, callback function). The Compiler object can also be used to manipulate Webpack.

With the simplest Plugin above, you probably get a sense of how the Plugin works, but there are a lot of details to pay attention to in actual development, which are described below.

CompilerCompilation

The two most commonly used objects for Plugin development are Compiler and Compilation, which bridge the gap between Plugin and Webpack. Compiler and Compilation have the following meanings:

  • The Compiler object contains all configuration information for the Webpack environment, includingoptions.loaders.pluginsThis information, this object is instantiated when Webpack starts, it’s globally unique, and you can simply think of it as a Webpack instance;
  • The Compilation object contains the current module resources, compile-generated resources, changing files, and so on. When Webpack is running in development mode, a new Compilation is created each time a file change is detected. The Compilation object also provides many event callbacks for plug-ins to extend. Compiler objects can also be read from the Compilation.

The difference between Compiler and Compilation is that Compiler represents the entire Webpack lifecycle from startup to shutdown, while Compilation simply represents a new Compilation.

Flow of events

Webpack is like a production line that goes through a series of processes to convert source files into output. Each process on this production line has a single responsibility. There are dependencies between multiple processes. Only after the current process is completed can the next process be handled. A plug-in is like a function that plugs into a production line and works with resources on the production line at a specific time.

Webpack organizes this complex production line through Tapable. Webpack broadcasts events as it runs, and the plug-in only needs to listen for the events it cares about to join the production line and change how the production line works. Webpack’s event flow mechanism ensures the orderliness of plug-ins and makes the whole system very extensible.

Webpack’s event flow mechanism applies the observer pattern, much like EventEmitter in Node.js. Compiler and Compilation inherit from Tapable and can broadcast and listen for events directly on Compiler and Compilation objects as follows:

/** * event-name is the name of the event. Params is the attached parameter */ compiler.apply('event-name',params); /** * listen for events with the name event-name. When event-name occurs, the function will be executed. * The params argument in the function is the argument attached to the broadcast event. */ compiler.plugin('event-name',function(params) { });Copy the code

Similarly, compilation.apply and compilation.plugin are used in the same way as above.

When you’re developing a plug-in, you might not know where to start because you don’t know which events to listen for to get the job done.

There are also two points to keep in mind when developing plug-ins:

  • As long as the Compiler or Compilation object is available, new events can be broadcast, so a newly developed plug-in can broadcast events for other plug-ins to listen on.
  • The Compiler and Compilation object passed to each plug-in is the same reference. This means that changing the properties of the Compiler or Compilation object in one plug-in can affect subsequent plug-ins.
  • Some events are asynchronous, and these asynchronous events take two parameters. The second parameter is a callback function that needs to be called to notify Webpack when the plug-in has finished processing the task before moving on to the next process. Such as:
Compiler. plugin('emit',function(compilation, callback) { The running process will always be stuck executing callback() below; });Copy the code

Commonly used API

Plug-ins can be used to modify output files, add output files, even improve Webpack performance, and so on, but plug-ins can do a lot of things by calling the APIS provided by Webpack. Because Webpack provides a large number of apis, there are many that are rarely used, and space is limited, so here are some commonly used apis.

Read output resources, code blocks, modules and their dependencies

Some plug-ins may need to read the results of Webpack processing, such as output resources, code blocks, modules and their dependencies, for further processing.

At the time of the EMIT event, the conversion and assembly of the source file has been completed and the final output resources, code blocks, modules and their dependencies can be read, and the contents of the output resources can be modified. The plug-in code is as follows:

See the Pen emit by whjin ( @whjin) on CodePen.

Listening for file changes

Webpack starts with the configured entry module and finds all dependencies in turn. When the entry module or its dependencies change, a new Compilation is triggered.

When developing plug-ins, you often need to know which file changes caused the new Compilation, using code like this:

See the Pen Compilation by whjin (@whjin) on CodePen.

By default, Webpack only monitors whether the entry and its dependent modules have changed, and in some cases the project may need to introduce new files, such as an HTML file. Because JavaScript files do not import HTML files, Webpack does not listen for changes to the HTML files and does not trigger a new Compilation when the HTML files are edited. To listen for changes to the HTML file, we need to add the HTML file to the dependency list, using the following code:

Compiler.plugin ('after-compile', (compilation, callback) => {// Add HTML files to the dependency list so that Webpack can listen for HTML module files, In the HTML template file changes to restart a compilation. FileDependencies. Push (filePath); callback(); });Copy the code

Modifying output Resources

In some cases, the plugin needs to modify, add, or delete the output resources. To do this, you need to listen for the EMIT event. When the emit event occurs, all module transformations and code block corresponding files have been generated, and the resources that need to be exported are about to be exported. The EMIT event is therefore the last chance to modify the Webpack output resource.

Compilation. assets is a key-value pair. The key is the name of the file to be exported, and the value is the corresponding content of the file.

Set up the compilation.assets code as follows:

compiler.plugin('emit', (compilation, Callback) => {// Set the output resource compilation named fileName. Assets [fileName] = {// Return the contents of the file source: () => {// fileContent can be a string representing a text file or a binary file. }, // Return file size size: () => {return buffer.bytelength (fileContent, 'utf8'); }}; callback(); });Copy the code

Read the compilation.assets code as follows:

Compiler.plugin ('emit', (compilation, callback) => {// Read the output resource named fileName const asset = compiler.assets [fileName];  // Get the contents of the output resource asset.source(); Asset-.size (); callback(); });Copy the code

Determine which plug-ins Webpack uses

When developing a plug-in, you may need to make the next decision based on whether the current configuration uses another plug-in, so you need to read Webpack’s current plug-in configuration. To determine whether the ExtractTextPlugin is currently used as an example, you can use the following code:

// The ExtractTextPlugin is used in the current configuration. Function hasExtractTextPlugin(Compiler) {// List of all plug-ins used in the current configuration const plugins = compiler.options.plugins; Return plugins.find(plugin=>plugin.__proto__. Constructor === ExtractTextPlugin) ! = null; }Copy the code

In actual combat

Let’s take you through a step-by-step implementation of a plug-in with a practical example.

The name of this plugin is EndWebpackPlugin, and it is used to add some additional actions when Webpack is about to exit, such as publishing the output file to the server after Webpack has successfully compiled and exported the file. The plug-in can also distinguish between successful Webpack builds. Use the plug-in as follows:

Module.exports = {plugins:[// EndWebpackPlugin was initialized with two arguments, a callback on success and a callback on failure; New EndWebpackPlugin(() => {// Webpack failed to build, (err) => {// Webpack failed, Console. error(err);})]}Copy the code

To implement the plug-in, you need two events:

  • done: occurs when Webpack is about to exit after a successful build and output of files;
  • failed: occurs when the build fails due to an exception and Webpack is about to exit;

Implementing the plug-in is very simple and the complete code is as follows:

Class EndWebpackPlugin {constructor(doneCallback, failCallback) {// Save the callback function passed in the constructor. DoneCallback = doneCallback; this.failCallback = failCallback; } apply(compiler) {compiler.plugin('done', (stats) => {doneCallback this. DoneCallback (stats); }); Compiler. Plugin ('failed', (err) => {// Callback failCallback this. FailCallback (err); }); Module.exports = EndWebpackPlugin;Copy the code

As you can see from the development of this plug-in, finding the right event point to complete the functionality is very important when developing plug-ins. Webpack broadcasts common events as it runs, as detailed in the Overview of how it works, so you can find the ones you need.

Debugging Webpack

When writing Webpack plugins and loaders, the results may be different from what you expected, just like when you run into strange bugs in your code. For bugs that cannot be seen at a glance, it is often necessary to debug the program’s source code to find the problem.

While debugging can be done using console.log, this method is inconvenient and inelegant. This section will show you how to break the plug-in code in the Working Principles overview. Since Webpack runs on top of Node.js, debugging Webpack is relative to debugging node.js programs.

Debug in Webstorm

Webstorm integrates node.js debugging tools, so debugging Webpack using Webstorm is very easy.

1. Set a breakpoint

Set a breakpoint where you think there might be a problem. Click on the edit area and the red dot to the left of the code indicates that the breakpoint has been set.

2. Configure an execution portal

Tell Webstorm how to start Webpack. Since Webpack is actually a Node.js application, you need to create a new node.js execution portal.

There are three points to note in the above configuration:

  • NameSet up adebug webpack, just like setting an alias, easy to remember and distinguish;
  • Working directorySet to the root of the project where the plug-in you want to debug resides.
  • JavaScript fileThe node.js execution entry file, set to the Webpack execution entry filenode_modules/webpack/bin/webpack.js.

3. Start debugging

After the above two steps, you are ready to start debugging. When starting, select the Debug Webpack that you set earlier.

4. Execute the breakpoint

Once started, the program stops at the breakpoint location, where you can easily view the current state of variables to find problems.

The principle of summary

Webpack is a huge Node.js application, and if you read its source code, you’ll see that implementing a complete Webpack requires a lot of code. But you don’t need to know all the details, just the overall architecture and some of the details.

For users of Webpack, it is a simple and powerful tool; Webpack is a highly scalable system for developers.

Webpack works because it hides complex implementations and exposes users to a simple tool that allows them to achieve their goals quickly. At the same time, the overall architecture design is reasonable, high scalability, development and expansion difficulty is not high, through the community to complement a large number of missing functions, so that Webpack can almost be competent for any scene.

Hopefully, this chapter will teach you not only how to write Webpack extensions, but also how to design good system architectures.