Webpack build process

Webpack is the most popular front-end packaging build tool. Essentially, it is a module packer, which starts from the entry file to find the dependencies between modules through recursive analysis, and eventually outputs one or more bundle files.

Webpack construction is a sequential process that executes the following processes from start to finish:

  1. Initial Configuration

    The final configuration item was generated by reading and merging parameters from configuration file and command line, and the plug-in instantiation statement in configuration file was executed to generate the Apply method of Compiler passing in plugin and hang custom hooks for Webpack event stream.

  2. Begin to compile

    Generate compiler examples and execute Compiler.run to start compiling;

  3. Identify entry file

    Read all entry files from the configuration item;

  4. Compile the module

    Compile from the entry file, use the corresponding Loader to compile the module, and recursively compile the module on which the current module depends. After all modules are compiled, obtain the final content of all modules and the dependency relationship between modules. Finally, replace all modules’ require statements with __webpack_require__ to simulate modular operations.

  5. Resource output

    According to the dependency of the entry and module, the chunk is assembled one by one containing multiple modules, and then the chunk is converted into a separate file and added into the output list.

  6. Generate the file

    Output the generated content to the specified location according to the configuration generation file.

The core object of Webpack is Compile, which is responsible for listening and starting the compilation of files, inherited from Tapable[github.com/webpack/tap…] , enabling Compile instances to register and invoke plug-ins.

As WebPack executes the build process, WebPack broadcasts corresponding events at specific times, and the plug-in executes specific logic to modify the contents of the module when it listens for the events.

To get a better idea of the WebPack build process, use the following flow chart:

Webpack output file analysis

Next, we’ll see how the bundle files run in the browser by analyzing the bundle files that webPack outputs.

Single file analysis

Create SRC /index.js first and execute the simplest js statement:

  console.log('hello world')
Copy the code

Create webpack.config.js with the following configuration:

  const path = require('path')

  module.exports = {
    mode: 'none'.entry: './src/index.js'.output: {
      path: path.resolve(__dirname, 'dist')}}Copy the code

The version of Webpack used in this example is 4.35.3. In order to better analyze the output bundle file, mode is set to ‘None’ and webPack does not enable any plug-ins by default.

Mode has three optional values: ‘None’, ‘production’, and ‘development’. The default value is ‘production’. The following plug-ins are enabled by default:

  • FlagDependencyUsagePlugin: compile-time tag depends;

  • FlagIncludedChunksPlugin: flags child chunks to prevent multiple dependencies;

  • ModuleConcatenationPlugin: enhancing the scope (scope hosting), precompiled function, ascending or precompile all modules into a closure, improve the code in the browser speed of execution;

  • NoEmitOnErrorsPlugin: a compilation error is skipped during the output phase;

  • OccurrenceOrderPlugin: shorter values for frequently used IDS;

  • SideEffectsFlagPlugin: Recognizes the sideEffects flag of package.json or module.rules (pure ES2015 module) to safely remove unused export exports;

  • TerserPlugin: Zip code

    When mode is set to ‘development’, the following plug-ins are enabled by default:

  • NamedChunksPlugin: solidify chunkId by name;

  • NamedModulesPlugin: Solidify the moduleId by name

    Execute the webpack build command:

  $ webpack
Copy the code

The output to the main. Js file in the dist folder is as follows:

 (function(modules) { // webpackBootstrap
// Module cache
var installedModules = {};

// Module loading function
function __webpack_require__(moduleId) {

	// If the module has been loaded, it will be read directly from the cache
	if(installedModules[moduleId]) {
		return installedModules[moduleId].exports;
	}
	// Create a new module and cache it
	var module = installedModules[moduleId] = {
		i: moduleId,
		l: false.exports: {}};// executes the module function, setting module.exports
	modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);

	// Mark module as loaded
	module.l = true;

	// returns the set module.exports
	return module.exports;
}


/ / points to modules
__webpack_require__.m = modules;

// point to the cache
__webpack_require__.c = installedModules;

// Define the get method of exports
__webpack_require__.d = function(exports, name, getter) {
	if(! __webpack_require__.o(exports, name)) {Object.defineProperty(exports, name, { enumerable: true.get: getter }); }};// Set the ES6 module flag
__webpack_require__.r = function(exports) {
	if(typeof Symbol! = ='undefined' && Symbol.toStringTag) {
		Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
	}
	Object.defineProperty(exports, '__esModule', { value: true });
};

// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function(value, mode) {
	if(mode & 1) value = __webpack_require__(value);
	if(mode & 8) return value;
	if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
	var ns = Object.create(null);
	__webpack_require__.r(ns);
	Object.defineProperty(ns, 'default', { enumerable: true.value: value });
	if(mode & 2 && typeofvalue ! ='string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
	return ns;
};

// Compatible with CommonJS and ES6 modules
__webpack_require__.n = function(module) {
	var getter = module && module.__esModule ?
		function getDefault() { return module['default']; } :
		function getModuleExports() { return module; };
	__webpack_require__.d(getter, 'a', getter);
	return getter;
};

/ / Object. The prototype. The hasOwnProperty encapsulation
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

// PublicPath for webpack configuration
__webpack_require__.p = "";


// Load the module and return
return __webpack_require__(__webpack_require__.s = 0);
 })
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
 ([
/* 0 */
/ * * * / (function(module, exports) {

console.log('hello world')

/ * * * /})]);Copy the code

As you can see, the output code is an IIFE (execute now function), which can be simplified as follows:

(function(modules) {
  var installedModules = {};

  // webpack require statement
  // Load the module
  function __webpack_require__(moduleId) {}

  return __webpack_require__(0)
})([
  function(module, exports) {
    console.log('hello world')})Copy the code

The __webpack_require__ function in the simplified code is used to load the module. The IIFE function takes an array of arguments, and the 0th item is the code statement in SRC /index.js. The __webpack_require__ function loads and executes the module. Finally, print Hello World on the browser console.

How does the __webpack_reuqire__ function work inside the code

function __weboack_require__(moduleId) {
  // If the module is already loaded, it will be read directly from the cache
  if (installedModules[moduleId]) {
    return installedModules[moduleId].exports;
  }

  // If the module has not been loaded, create a new module and store it in the cache
  var module = installedModules[moduleId] = {
  	i: moduleId, // module id
  	l: false.// Whether false is loaded
  	exports: {} // Module export
  };

  // Execute the module
  // The first argument to the call method is modules.exports, which refers to the module this inside the module
  Module, module.exports, and __webpack_require__ module loading functions
  modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);

  // Set module to loaded
  module.l = true;

  // exports returns module.exports
  return module.exports; }}Copy the code

You can see that the __webpack_require__ function takes a module ID and, by executing that module, eventually returns the exports of that module and caches the module in memory. If the module is loaded again, it is read directly from the cache. The content of modules[modulesId] is the 0th item of the IIFE argument, namely:

function(module, exports) {
  console.log('hello world')}Copy the code

In the exported IIFE, in addition to the __webpack_require__ function, many attributes are mounted under __webpack_require__.

  • __webpack_require__.m: mounts all modules;
  • __webpack_require__.c: mounts cached modules.
  • __webpack_require__.dDefines the getters of exports;
  • __webpack_require__.r: Set module to es6 module.
  • __webpack_require__.t: Returns the module or value after processing according to different scenarios;
  • __webpack_require__.n: Returns the getter, internally differentiating whether it is an ES6 module.
  • __webpack_require__.o: Object. The prototype. The hasOwnProperty function encapsulation;
  • __webpack_require__.p: output publicPath property of the configuration item.

Multiple file reference analysis

In the previous example, the webPack bundle contains only a very simple entry file, with no references between modules.

SRC /index.js: SRC /math.js: SRC /index.js: SRC /index.js

// math.js
const add = function (a, b) {
  return a + b
}

export default add
Copy the code
// index.js
import add from './math'

console.log(add(1.2))
Copy the code

Re-execute the webpack command and you can see that the IIFE parameters in the output have changed to two:

([
/* 0 */
/ * * * / (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);


console.log(Object(_math__WEBPACK_IMPORTED_MODULE_0__["default"]) (1.2))


/ * * * / }),
/ * 1 * /
/ * * * / (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
const add = function (a, b) {
  return a + b
}

/* harmony default export */ __webpack_exports__["default"] = (add);


/ * * * /})]);Copy the code

The math.js module is defined in item 1 of the array and webpack is able to recognize the module as an ES6 module by executing __webpack_require__.r(__webpack_exports__). Finally, set the default attribute value of __webpack_exports__ to the function add.

The 0th element of the array is the output module of index.js packaged, The statement var _math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1) imports the add function exported from the math.js module, __webpack_require__(1) returns module.exports, where 1 is the chunkId generated by webpack on packaging, Log (Object(_math__WEBPACK_IMPORTED_MODULE_0__[“default”])(1, 2))).

Webpack stores the original independent modules into the parameters of IIFE to load, so that all modules can be executed with only one network request, avoiding the problem of long loading time caused by multiple network loading of each module. And within the IIFE function, Webpack also further optimizes the loading of modules by caching the modules that have been loaded and storing them in memory, so that the same module can be directly removed from memory when it is loaded a second time.

Asynchronous loading analysis

The above two examples load modules synchronously and execute them. However, in actual projects, in order to improve the loading speed of the page, modules that are not temporarily used during the initialization of the first screen are often asynchronously loaded, such as the routing module after the jump from the home page. Next we’ll load the math.js module asynchronously and execute its exported Add function.

import('./math').then((add) = > {
  console.log(add(1.2))})Copy the code

After repackaging, output main.js and 1.js, which are files that need to be loaded asynchronously.

The main. Js file contains the __webpack_require__. E and webpackJsonpCallback functions, and only one IIFE parameter:

/ * * * / (function(module, exports, __webpack_require__) {


__webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(null.1)).then((add) = > {
  console.log(add(1.2))})/ * * * / })
Copy the code

The module loads the file of module 1 by __webpack_require__.e(1), returns module 1 by executing __webpack_require__.bind(null, 1), and then executes the add function exported by the module.

__webpack_require__. E loads modules that need to be loaded asynchronously.

 __webpack_require__.e = function requireEnsure(chunkId) {
  var promises = [];

  var installedChunkData = installedChunks[chunkId];
  if(installedChunkData ! = =0) { // If the value is 0, the module has been loaded

    // If installedChunkData is not empty and not 0, the Chunk is being loaded on the network
    // Return the Promise object directly
    if (installedChunkData) {
      promises.push(installedChunkData[2]);
    } else {
      The chunk is never loaded. The return array contains resolve, Reject, and the created Promise object
      var promise = new Promise(function (resolve, reject) {
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
      });
      promises.push(installedChunkData[2] = promise);

      // Create a script tag and load the module
      var script = document.createElement('script');
      var onScriptComplete;

      script.charset = 'utf-8';
      script.timeout = 120;
      if (__webpack_require__.nc) {
        script.setAttribute("nonce", __webpack_require__.nc);
      }

      // jsonpScriptSrc returns the file path generated based on the configured publicPath and chunkId
      script.src = jsonpScriptSrc(chunkId);

      // Create an Error instance to catch when loading an Error
      var error = new Error(a); onScriptComplete =function (event) {
        // Prevent memory leaks
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var chunk = installedChunks[chunkId];

        if(chunk ! = =0) {
          if (chunk) {
            // Chunk failed to load, throwing an error
            var errorType = event && (event.type === 'load' ? 'missing' : event.type);
            var realSrc = event && event.target && event.target.src;
            error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ':' + realSrc + ') ';
            error.name = 'ChunkLoadError';
            error.type = errorType;
            error.request = realSrc;
            chunk[1](error);
          }
          installedChunks[chunkId] = undefined; }};// The maximum waiting time for asynchronous loading is 120s
      var timeout = setTimeout(function () {
        onScriptComplete({ type: 'timeout'.target: script });
      }, 120000);
      script.onerror = script.onload = onScriptComplete;

      // Insert the created script tag into the DOM
      document.head.appendChild(script); }}return Promise.all(promises);
};
Copy the code

Internally, the function checks whether the module has been loaded. If not, a script tag is created. The path of script is returned by the internal jsonpScriptSrc function generating the final SRC path according to webpack configuration. The final return is a Promise object, reject thrown when the JS file fails to load.

The bundle 1.js output from math.js is simple, with the following code:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1], [/* 0 */./ * 1 * /
/ * * * / (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
const add = function (a, b) {
  return a + b
}

/* harmony default export */ __webpack_exports__["default"] = (add);


/ * * * /})]]);Copy the code

As you can see, what this bundle does is push a new array into the Window [‘webpackJsonp’] array, where the first item [1] is the webPack-generated chunkId, and the second item is the converted module contents of math.js.

At the same time, in the IIFE section of main.js, the push method mounted in the global window[‘webpackJsonp’] array is overridden to refer to the webpackJsonpCallback function defined earlier:

function webpackJsonpCallback(data) {
	var chunkIds = data[0];
	var moreModules = data[1];
	// Add data item 1 to modules,
	// Then mark the corresponding chunkId as loaded
	var moduleId, chunkId, i = 0, resolves = [];
	for(; i < chunkIds.length; i++) { chunkId = chunkIds[i];if(installedChunks[chunkId]) {
			resolves.push(installedChunks[chunkId][0]);
		}
		installedChunks[chunkId] = 0;
	}

	// Add each module from the passed moreModules array in turn to the modules cached in IIFE
	for(moduleId in moreModules) {
		if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; }}ParentJsonpFunction is an array push method for window['webpackJsonp']
	ParentJsonpFunction to actually add data to the window['webpackJsonp'] array
	if(parentJsonpFunction) parentJsonpFunction(data);

	// Execute resolve with the previously created promise
	while(resolves.length) { resolves.shift()(); }};Copy the code

By analyzing the webpackJsonpCallback function, you can see that its main function is to mark the incoming chunkid as loaded and attach the incoming module to the modules object of the cache module. The resolve method of the promise object returned by the __webpack_require__.e function represents that the asynchronously loaded module is complete, at which point, The module can be loaded synchronously in __webpack_require__.e(1).then().

Rearrange the general process of loading asynchronous modules from entry main files:

  1. perform__webpack_require__.eLoad asynchronous module;
  2. Create a script tag corresponding to chunkid to load the script and return a promise;
  3. If the load fails, reject the promise; If the load is successful, asynchronous Chunk executes immediatelywindow[webpackJsonp]Mark the module as loaded and resolve to drop the corresponding promise;
  4. After success can be in__webpack_require__.e().thenTo load modules synchronously.

Summary of output file

In the webpack output file, pass all modules as parameters in the form of IIFE, simulate the import or require statement with __webpack_require__, and recursively execute the loading modules starting from the entry module, modules that need to be loaded asynchronously, It is loaded by inserting a new script tag into the DOM. And the internal module loading cache processing optimization.

In a real world project, the output bundle content would be much more complex than the demo in this article, with optimizations for chunkId Settings, common chunk extraction, and code compression obtrusions, but this basic demo will familiarize you with the runtime workflow of webPack output files. For better analysis during debugging.

Write a simple loader

Before writing a loader, let’s briefly introduce the function of WebPack Loader. In Webpack, loader can be understood as a converter that processes the input of a file and returns a new result, which is finally handed over to WebPack for further processing.

A loader is a nodeJS module, and its basic structure is as follows:

// The loader configuration options can be obtained from the loader-utils package
const loaderUtils = require('loader-utils')

// Export a function, source is the file source content that webpack passes to loader
module.exports = function(source) {
  // Obtain the loader configuration item
  const options = loaderUtils.getOptions(this)

  // Some transformation processing, and finally return the processed result.
  return source
}

Copy the code

When configuring webPack Loader, the loader installed through NPM is used. There are generally two ways to load the local Loader. The first way is to associate the Loader to the node_modules of the project through NPM link. Another way is to tell Webpack how to find a Loader by configuring wepack’s resolveloader. modules configuration. The first method requires the associated package.json configuration, and the second method is used in this example.

module.exports = {
  resolveLoader: {
    // Assume that the loader written locally is in the loaders folder
    modules: ['node_modules'.'./loaders/']}}Copy the code

Let’s write a loader to remove comments from the code. Name it remove-comment-loader:

module.exports = function(source) {
  // Matches the comment content in js
  const reg = new RegExp(/(\/\/.*)|(\/\*[\s\S]*? \*\/)/g)

  // Delete comments
  return source.replace(reg, ' ')}Copy the code

Then modify webpack.config.js:

const path = require('path')

module.exports = {
  mode: 'none'.entry: './src/index.js'.module: {
    rules: [{test: /\.js$/.loader: 'remove-comment-loader' // When a js file is matched, use the remove-comment-loader we wrote}},output: {
    path: path.resolve(__dirname, 'dist')},resolveLoader: {
    modules: ['node_modules'.'./loaders/'] // Configure the local loader to be loaded}}Copy the code

Then add some comments to the entry file code, repackage it and look at the output file to see that the comments in the code have been removed.

See the demo code in this article; Github.com/duwenbin031…

Here I would like to recommend apollo-Build, the front-end packaging and construction tool of Firefly mobile finance development platform of Minsheng Technology Company. Apollo-build includes debugging, packaging, testing, and packaging DLLS, as well as a very handy front-end interface Mock, with a command line experience consistent with create-React-app. We have encapsulated most of the common functions in Webpack and made a lot of internal optimization, extracting the most common configuration items. Even if you are not familiar with the configuration of Webpack, you can quickly get started, and you can also make advanced modifications in the way of webpack.config.js. Please visit the official website of Minsheng Science and Technology for more information.

reference


Simple Webpack – Wu Hao Lun

Webpack Unravelling – The path to the advanced front end

The authors introduce

Du Wenbin, Front-end development engineer of Firefly Mobile finance development platform, User Experience Technology Department, Minsheng Technology Co., LTD