Summary of working principle

When Webpack starts, it recursively resolves all modules that Entry depends on, starting from the Module configured in the Entry. When a Module is found, the system finds the corresponding conversion rule based on the configured Loader, converts the Module, and resolves the Module dependent on the current Module. These modules are grouped by Entry, and an Entry and all its dependent modules are assigned to a group called a Chunk. Finally, Webpack converts all chunks into files for output. Webpack executes the logic defined in the Plugin at appropriate times throughout the process.

Webpack common noun explanation

parameter instructions
entry Project entry
module Every file in development can be regarded as a module. Modules are not limited to JS, but also include CSS, images, etc
chunk Code blocks, a chunk can be composed of multiple modules
loader Module converter, the processor of a module, transforms a module
plugin Extension plug-in, plug-in can process chunk, can also process the final packaging results, can complete tasks that loader cannot complete
bundle The final packaged files are usually one-to-one corresponding to chunk. Bundle is the output of chunk compression and packaging

The basic flow

The basic flow of Webpack can be divided into three phases:

  1. Preparation stage: The main task is to create Compiler and Compilation objects;
  2. Compilation phase: This phase is the task of completing modules parsing and generating chunks;
  • Module parsing: Contains three main steps: creating instances, loaders applications, and dependency collection;
  • Chunks are generated, and the main step is to find modules that each chunk needs to contain.
  1. Output phase: The main task of this phase is to generate the final file according to chunks. There are three steps: template Hash update, template render chunk, and file generation.

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 large phase, and Webpack broadcasts these events for Plugin use.

A more vivid analogy:

Webpack can be seen as a factory workshop, plugin and loader are two types of machines in the workshop, the factory has a workshop director and a production workshop. The director of the workshop, named Compiler, is responsible for directing the machine Compilation of the production workshop to carry out production labor. The Compilation will first process the incoming raw materials (entry) with a machine called Loader, and the product produced is Chunk. After the Chunk is produced, it will be assembled into bundles and then processed by a plugin machine to get the final Bundle, which will then be transported to the corresponding output. The operation of the production line in this factory is Tapable, factory production line for the whole process control, in the workshop in a number of production lines, each line has a lot of steps (hook), step is completed, will enter the next operation, until the production line process is complete, all will be passed on to the next line output processing again. The entire workshop production line also constitutes one of the largest production lines.

Write the loader

According to the workflow description above, we know that in Webpack, the real compiler is our loader. Loader is actually a parser that deals with a single module (loader is not as well understood as parser). We usually compile Babel ES6. SCSS, LESS and other compilations are completed in loader.

Module. exports = {module: {rules: [{// added support for SCSS files test: /\. SCSS $/, // SCSS file processing sequence is ass-loader > css-loader > style-loader use: ['style-loader', {loader:' csS-loader ', // Pass csS-Loader configuration options:{minimize:true,}}, 'sass-loader'],},]},};Copy the code

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. The basic principles of Loader development can be concluded as follows:

  1. Single principle: Each Loader only does one thing, easy to use, easy to maintain;
  2. Chain calls: Webpack calls each Loader in a chain order;
  3. Unified principle: Follow Webpack design rules and structure, input and output are strings, each Loader is completely independent, plug and play;
  4. Stateless principle: No state should be preserved in loader when converting different modules;

Loader is essentially a function that accepts the content of the process and returns the result after processing. The loader function is normally written as:

Module.exports = function(content, sourcemap) {module.exports = function(content, sourcemap) {module.exports = function(content, sourcemap) { return content; };Copy the code

In the example above, we see that the loader is actually a funtion, so we use a return to return the data processed by the Loader. This is not the most recommended method, and in most cases we prefer to use this. Callback to return data. If written this way, the sample code can be rewritten as:

Module. exports = function(content) {// handle content... this.callback(err, content); // err is an error object, and content is a string returned after translation};Copy the code

tips: This is a special context passed in by webPack when the loader is called by webPack. If you want to use this. Callback or loader-utils getOptions, this is a special context passed in by WebPack when the loader is called. So you shouldn’t use arrow functions at this point!

In this example, the loader uses either return or this.callback to perform asynchronous operations, such as pulling asynchronous requests. Two asynchronous writing methods are provided in the Loader.

The first is to write an asynchronous function with async/await:

module.exports = async function(content) { function timeout(delay) { return new Promise((resolve, Reject) => {setTimeout(() => {resolve(content); }, delay); }); } const data = await timeout(1000); return data; };Copy the code

Alternatively, the this.async method is used to retrieve an asynchronous callback and then return it. The above example code is modified using this.async as follows:

module.exports = function(content) { function timeout(delay) { return new Promise((resolve, Reject) => {setTimeout(() => {resolve(content); }, delay); }); } const callback = this.async(); timeout(1000).then(data => { callback(null, data); }); };Copy the code

This. Async returns the same parameter as this. Callback.

//loader/style-loader.js
function loader(source, map) {
  let style = `
    let style = document.createElement('style');
    style.innerHTML = ${JSON.stringify(source)};
    document.head.appendChild(style)
  `;
  return style;
}
module.exports = loader;

Copy the code

Importing a Local Loader

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

Copy the code

Example 2: Now let’s write a markdown-loader manually. Markdown- Loader converts Markdown syntax files to HTML. In this case, haggis is used to convert Markdown to HTML. In order to facilitate loader programming, Webpack officially packages the common tool functions used in loader programming into loader-utils and schema-utils modules, which include commonly used methods such as obtaining Loader options and parameter verification.

markdown-loader.js

// Import dependence on const haggis = require(' haggis '); const loaderUtils = require('loader-utils'); Module.exports = function(content) {// getOptions is used to getOptions from the loader. const options = loaderUtils.getOptions(this); // Set cache this.cacheable(); // Initialize a motion converter const Converter = new mores. converter (options); console.log('options', options); // Handle content = Converter.makehtml (content); This.callback (null, content); };Copy the code

markdown.md

# test markdown test automatic options effect, automatic detection url add link: www.google.com ## table | h1 | h2 | h3 | | ---- | ----- | ------ | | 100 | [a][1] | ! [b][2] | | *foo* | **bar** | ~~baz~~ |Copy the code

index.js

import html from './markdown.md';
console.log(html);
Copy the code

webpack.config.js

      {
        test: /\.md$/,
        use: [
          'html-loader',
            {
                loader: "./loader/markdown-loader.js",
                options: {
                    simplifiedAutoLink: true,
                    tables: true
                }
            }
        ]
    }
Copy the code

Results:

Loader file-loader: The url-loader function is similar to file-loader, but when the file size (byte) is lower than the specified limit, it can return a DataURL (to improve web page performance) csS-loader: Just like images, WebPack can’t handle CSS files by default, so you need to use the Loader to convert CSS files to the type webPack can handle. Parse @import dependencies in CSS files and copy the dependent code in place of @import when packaging. Style-loader: After the CSS file is processed by CSS-loader, the processed content is inserted into the HTML HEAD code. Scss-loader: automatically converts SCSS to CSS less-loader: automatically converts less to CSS PostCSS-loader: PostCSS is different from SASS/LESS. PostCSS is not a CSS preprocessor. PostCSS is a tool to use plug-ins to convert CSS, PostCSS has many very useful plug-ins. Examples include autoprefixer(auto-complete browser prefixes), postCSS-pxtorem (automatically convert PX to REM). Instructions must be placed at the end of the CSS rule and executed first. Eslint-loader: Used to check common JavaScript code errors. It can also be used for “code specification” checks. In enterprise development, the project leader will customize a set of ESLint rules and then apply them to the project to implement the secondary code specification and effectively control the quality of the project code. When compiling a package, an error is reported if there is a syntax error or a syntax that does not conform to the specification, and an error message is displayed

Writing a plugin

The packaging process mentioned above can be divided into:

  1. Initialization parameters: includes reading and merging parameters from configuration files and shells, and then deriving the final parameters; Parameters in the shell are better than those in the configuration file;
  2. The parameters obtained in one step instantiate a Compiler class, register all plug-ins, and build life cycle binding hooks for the corresponding Webpack.
  3. Start compiling: Execute the Compiler class’s run method to start compiling;

Compiler. run calls compiler.compile and instantiates a Compilation class inside compiler.compile. Compilation is used to build packages.

1) Search entry: find out all entry files according to entry configuration; 2) Compiling module: according to the file type and Loader configuration, use the corresponding Loader to convert files; 3) Parse AST syntax tree of file; 4) Find file dependencies; 5) Recursively compile dependent modules.

After the recursion, the final result of each file is obtained, and the code block chunk is generated according to entry configuration. Output all chunks to the corresponding output path. Tapable is always running through the Webpack workflow, and various Tapable hooks form the life cycle of Webpack. The relationship between Tapable Hook and life cycle is as follows: Hook: corresponding to Tapable Hook; Life cycle: the execution process of Webpack, Hook is actually the life cycle, generally similar to entryOption Hook, in the life cycle entry-option.

Plugin of Webpack is the core concept of Webpack, it can be said that the whole Webpack is composed of plug-ins. This is in the latter part of the workflow, Webpack handles the dependencies of the entire module, and when the bundle is finally generated, it is left to the built-in plug-in and the user configured plug-in to handle in turn. Unlike a Loader, which operates on a single module, plugin focuses on the packaged bundle as a whole, that is, the bundle made up of all modules. So output related things need to be implemented by plug-ins, such as compressing and splitting common code.

The webPack plug-in needs to include:

  1. A Webapck plug-in must be a class;
  2. The class must contain a function for Apply that accepts the Compiler object arguments;
  3. The class can use the Compiler of Webpack and the hooks of the Compilation object;

The compiler and compilation

Compiler module is the core module of Webpack. Each time a Webpack build is performed, a Compiler object is instantiated inside the Webpack and its run method is called to start a full compilation process. We use Webpack API Webpack (options) directly to get a Compiler instantiated object, at this time, the Webpack will not immediately start building, we need to manually execute comipler.run().

const webpack = require('webpack'); const webpackConfig = require('./webpack.config.js'); // Just pass in config const compiler = webpack(webpackConfig); // Start compiler.run(callback);Copy the code

In the WebPack Plugin, each plug-in has an Apply method. This method receives the Compiler object as an argument, and we can write the plug-in by binding the handler function at the corresponding hook time

During the Compilation phase, modules are loaded, sealed, optimized, chunked, hashed, and recreated. The Compilation object contains the current module resources, compile-generated resources, changing files, and so on. When Webpack runs in Watch 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. Compilation hook portal

The simplest plugin, myplugin.js:

class MyPlugin {
 constructor(options) {
    console.log("Plugin Created by Scarlett");
    console.log(options);
    this.options = options;
  }
  apply (compiler) {}
}
module.exports = MyPlugin;

Copy the code

webpack.config.js

const MyPlugin = require('./plugins/MyPlugin')
module.exports = {
  plugins: [
    new MyPlugin({title:'MyPlugin'})
  ],
}
Copy the code

Results:

Tips: The webPack plug-in is actually the last class that contained the Apply methods.

If you want to execute a script in the compiler hook, you can add callback methods to the corresponding event hooks to perform the desired operations. Since webpack hooks come from the Tapable class, some special types of hooks require special TAP methods. For example, compiler’s EMIT hook supports tap, tapPromise, and tapAsync. However, no matter which way tap is used, the corresponding value needs to be returned according to the Tapable specification. The following example uses emit. TapPromise, and a Promise object needs to be returned. An asynchronous example:

class HelloWorldPlugin { apply(compiler) { compiler.hooks.emit.tapPromise('HelloAsyncPlugin', Compilation => {// return a Promise to resolve when our asynchronous task is completed... Return new Promise((resolve, reject) => {setTimeout(function() {console.log(' Async work done... '); resolve(); }, 1000); }); }); } } module.exports = HelloWorldPlugin;Copy the code

FileListPlugin is an official plugin

Class FileListPlugin {apply(compiler) {// emit is an asynchronous hook, use tapAsync to touch it, You can also use tapPromise/tap (synchronous) compiler. Hooks. Emit. TapAsync (' FileListPlugin '(compilation, the callback) = > {/ / in the generated file, Create a header string: var filelist = 'In this build:\n\n'; // Walk through all compiled resource files, // add a line of content for each file name. for (var filename in compilation.assets) { filelist += '- ' + filename + '\n'; } // Insert this list as a new file resource into the webpack build: compilation. Assets ['filelist.md'] = {source: function() {return filelist; }, size: function() { return filelist.length; }}; callback(); }); } } module.exports = FileListPlugin;Copy the code

File upload qiniu CDN Plugin

const qiniu = require('qiniu'); const path = require('path'); Seven cattle class MyWebpackPlugin {/ / SDK MAC object constructor (options) {/ / read incoming options for this. The options = options | | {}; // Check options for the parameter this.checkQiniuconfig (); / / MAC object initialization seven cows this. MAC = new qiniu. The auth. Digest. MAC (this. Options. Qiniu. The accessKey, enclosing the options. Qiniu. SecretKey); } checkQiniuConfig() {if (! this.options.qiniu) { this.options.qiniu = { accessKey: process.env.QINIU_ACCESS_KEY, secretKey: process.env.QINIU_SECRET_KEY, bucket: process.env.QINIU_BUCKET, keyPrefix: process.env.QINIU_KEY_PREFIX || '' }; } const qiniu = this.options.qiniu; if (! qiniu.accessKey || ! qiniu.secretKey || ! qiniu.bucket) { throw new Error('invalid qiniu config'); } } apply(compiler) { compiler.hooks.afterEmit.tapPromise('MyWebpackPlugin', (compilation) => { // afterEmit: Return new Promise((resolve, Reject) => {const uploadCount = object.keys (compilation.assets).length; // Let currentUploadedCount = 0; / / seven cows parameter SDK const putPolicy = new qiniu. Rs. PutPolicy ({. Scope: this options. Qiniu. Bucket}); const uploadToken = putPolicy.uploadToken(this.mac); const config = new qiniu.conf.Config(); config.zone = qiniu.zone.Zone_z1; const formUploader = new qiniu.form_up.FormUploader() const putExtra = new qiniu.form_up.PutExtra(); // Let globalError = null; For (const filename of object.keys (compilation.assets)) {// Start uploading formuploader.putFile (uploadToken, this.options.qiniu.keyPrefix + filename, path.resolve(compilation.outputOptions.path, filename), putExtra, (err) => { console.log(`uploade ${filename} result: ${err ? `Error:${err.message}` : 'Success'}`) currentUploadedCount++; if (err) { globalError = err; } if (currentUploadedCount === uploadCount) { globalError ? reject(globalError) : resolve(); }}); }})}); } } module.exports = MyWebpackPlugin;Copy the code
// webpack.config.js plugins: [new QiniuWebpackPlugin({qiniu: {accessKey: '7 ', secretKey: 'exception ', bucket: 'static', keyPrefix: 'webpack-inaction/demo1/}})]Copy the code

Results:

The plugin is commonly used

Webpack plug-in, using different plugins to complete all kinds of different sexual requirements, hot update, CSS to reload and so on

1. ProvidePlugin: 2. Html-webpack-plugin can automatically generate HTML code from templates. Extract -text-webpack-plugin Separate styles referenced in JS files into CSS files. 4.DefinePlugin Configates global variables at compile time, which is very useful for development mode and publish mode construction to allow different behavior. 5. HotModuleReplacementPlugin hot update 6. Optimize the CSS – assets – webpack – plugin different components in repeat. CSS can quickly go to 7 webpack – bundle – analyzer A Bundle file analysis tool for WebPack that presents bundles as interactively scaled Treemaps. The production environment can use Gzip compression JS and CSS 9. Happypack: Accelerates code construction through the multi-process model 10. The clean-webpack-plugin cleans up unused files under each package