Webpack, one of the most widely used packaging tools on the front end, supports CommonJS, AMD, and ES Module modularity. This article takes a closer look at the processes and principles of how Webpack5 helps you implement CommonJS support in your code by analyzing the source code.

The preparatory work

Here, the preparation of the source code to do a brief description:

Create a js folder in the SRC directory, create a math.js file in the js folder, use CommonJS modularize to export two simple functions, code as follows:

// src/js/math.js
const sum = (num1, num2) = > {
  return num1 + num2
}

const mul = (num1, num2) = > {
  return num1 * num2
}

module.exports = {
  sum,
  mul
}
Copy the code

2. Create an index.js file in the SRC directory as the entry of WebPack. In index.js, use CommonJS to introduce the two functions in Math.js. The code looks like this:

const { sum, mul } = require('./js/math.js')
console.log(sum(10.20))
console.log(mul(10.20))
Copy the code

3. In webpack.config.js, set mode to ‘development’ and devTool to ‘source-map’ to make our output source code easier to read and analyze after packaging. The webpack.config.js code is as follows:

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development'.entry: './src/index.js'.devtool: 'source-map'.output: {
    filename: 'js/bundle.js'.path: path.resolve(__dirname, './build')},plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'webpack'}})]Copy the code

Without showing the rest of the code directory and the packaging process in detail, let’s start looking at the bundled bundle.js code.

A brief description of bundle.js

After the code is packaged, bundle.js will insert and generate more comments, which I will delete in advance to reduce interference.

Let’s first take a brief look at the general situation of the code packaged out, a simple understanding, convenient for us to analyze in detail below. The code is as follows:

// The outermost layer is wrapped in instant-execute functions, which can be ignored for analysis; (() = > {
  // 1. Define a __webpack_Modules__ object
  var __webpack_modules__ = {
    './src/js/math.js': module= > {
      const sum = (num1, num2) = > {
        return num1 + num2
      }
      const mul = (num1, num2) = > {
        return num1 * num2
      }
      module.exports = {
        sum,
        mul
      }
    }
  }

  // 2. Define an empty __webpack_module_cache__ object
  var __webpack_module_cache__ = {}

  // 3. Define __webpack_require__ function
  function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId]
    if(cachedModule ! = =undefined) {
      return cachedModule.exports
    }
    var module = (__webpack_module_cache__[moduleId] = {
      exports: {}
    })

    __webpack_modules__[moduleId](module.module.exports, __webpack_require__)

    return module.exports
  }

  // Define __webpack_exports__ empty objects, but not used
  var __webpack_exports__ = {}

  // 4. An instant-execute function, where execution really begins after removing the outer layer of the instant-execute function; (() = > {
    const { sum, mul } = __webpack_require__('./src/js/math.js')
    console.log(sum(10.20))
    console.log(mul(10.20))
  })()
})()
Copy the code

In the packaged code, it is wrapped with an instant-execute function. The main code is divided into four sections, which have been briefly identified in the comments above.

A detailed analysis

After a brief review of the bundle.js code, we have a certain understanding of its main content and structure, so let’s analyze the execution process in detail.

Leaving aside the outermost function that executes immediately, the code is divided into four main parts:

  1. Defines a__webpack_modules__object
  2. Defines a__webpack_module_cache__An empty object
  3. define__webpack_require__ function
  4. An instant-execute function, and this is where the execution starts

Let’s first look at the code for Parts 1 and 2:

// 1. Define a __webpack_Modules__ object
  var __webpack_modules__ = {
    './src/js/math.js': module= > {
      const sum = (num1, num2) = > {
        return num1 + num2
      }
      const mul = (num1, num2) = > {
        return num1 * num2
      }
      module.exports = {
        sum,
        mul
      }
    }
  }
Copy the code

The first part of the code defines an __webpack_modules__ object, known as the name, which is an object that webPack holds the contents of modules. The key of this object is ‘./ SRC /js/math.js’, which is the path to the module we introduced in index.js. The value of this object is a function that takes a Module argument. The body of this function is the same as our own math.js, which defines the sum and mul functions and puts them in an object. Assign to the export property of the module parameter object.

var __webpack_module_cache__ = {}
Copy the code

The second part of the code only defines an empty object, which we can guess from its name is an object used for caching.

Let’s look at the code for the immediate function section.

// 4. An instant-execute function, where execution really begins after removing the outer layer of the instant-execute function; (() = > {
    const { sum, mul } = __webpack_require__('./src/js/math.js')
    console.log(sum(10.20))
    console.log(mul(10.20))
  })()
Copy the code
// Write your own index.js file
const { sum, mul } = require('./js/math.js')
console.log(sum(10.20))
console.log(mul(10.20))
Copy the code

As you can see, it’s basically the same code we wrote in index.js, mainly converting the require method in CommonJS syntax into webPack’s own __webpack_require__ method.

The __webpack_require__ method is used here, taking as an argument the path ‘./ SRC /js/math.js’ of the math.js module we imported into the function. Let’s take a look at what the third part of the code __webpack_require__ does.

function __webpack_require__(moduleId) {
    // From the call to the immediate function, moduleId is './ SRC /js/math.js', which is the path where we introduced the module
    
    // This line is in the __webpack_module_cache__ cache object
    // Take the moduleId module path as the key to get its corresponding value and assign it to cachedModele
    var cachedModule = __webpack_module_cache__[moduleId]
    
    // If cachedModule has a value, return the value of the cachedModule's export property directly
    if(cachedModule ! = =undefined) {
      return cachedModule.exports
    }
    
    // Exports :{}} = moduleId ('./ SRC /js/math.js'); // exports:{}} = moduleId ('./ SRC /js/math.js');
    // Because {exports:{}} is an object, module and __webpack_module_cache__[moduleId] have the same reference, and if they change, they change together
    var module = (__webpack_module_cache__[moduleId] = {
      exports: {}})// Here we use the __webpack_modules__ object from the first part. The object key is moduleId ('./ SRC /js/math.js').
    // Take the function __webpack_modules__['./ SRC /js/math.js'], pass in the following three arguments and execute. Here we enter the __webpack_modules__[moduleId] function from the first part above
    __webpack_modules__[moduleId](module.module.exports, __webpack_require__)
    
    // Return after execution
    return module.exports
  }
Copy the code

Let’s look at the execution of the __webpack_modules__[moduleId] function:

// The function is called with three arguments, but the function is executed with only the module argument, because this case is simple and the other two arguments are not used
module= > {// Here we wrap the two functions we defined in Math.js and assign them to the export property of the module variable passed in as an argument
    const sum = (num1, num2) = > {
        return num1 + num2
    }
    const mul = (num1, num2) = > {
        return num1 * num2
    }
    module.exports = {
        sum,
        mul
    }
}
Copy the code

After this function executes, we’ll look back and forth at the __webpack_require__ function:

function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId]
    if(cachedModule ! = =undefined) {
      return cachedModule.exports
    }
    var module = (__webpack_module_cache__[moduleId] = {
      exports: {}
    })
    __webpack_modules__[moduleId](module.module.exports, __webpack_require__)
    return module.exports
 }
Copy the code

The value of the module’s export property is an object {sum, mul}, and sum and mul are functions that we define. The continuous assignment above causes the __webpack_module_cache__[moduleId] cache object to have the same content, which will be retrieved directly from the cache object if used again elsewhere. Finally, return module.export.

Back to the immediate execution function we started with:

; (() = > {
    // So __webpack_require__('./ SRC /js/math.js') is completed and returns a very normal object
    // The object contains the sum and mul functions, and we can use them happily by deconstructing them from the return value
    const { sum, mul } = __webpack_require__('./src/js/math.js')
    console.log(sum(10.20))
    console.log(mul(10.20))
  })()
Copy the code

conclusion

We have analyzed bundle.js in great detail above, and make a simple summary:

Webpack packs our own SRC /index.js and SRC /js/math.js files into bundle.js.

Bundle. js uses an instant-execute function to wrap all the content. The main content is divided into four parts:

  • The __webpack_modules__ object whose key is the module path, whose value is a function, and whose function body is the contents of the module corresponding to the key

  • The __webpack_module_cache__ object is used for caching. It caches the processed content so that it can be returned directly if a module is imported for use elsewhere

  • The __webpack_require__ function handles our own ConmonJS require

  • The function executes immediately, where bundle.js actually starts executing, and the contents inside are the contents of index.js


    Bundle.js after packaging:

    1. The execution starts with the immediate execution function in the outer layer, and the actual execution of the inner code is the immediate execution function in the fourth part
    2. Set module path'./src/js/math.js'As a parameter, execute__webpack_require__methods
    3. __webpack_require__Function will be{exports:{}}Continuously assign to__webpack_module_cache__Cache objects andmoduleThe variable.moduleand__webpack_module_cache__The same reference, every change changes
    4. willmoduleVariables as parameters (as well as those not used in this casemodule.exports, __webpack_require__) to perform__webpack_modules__Object, after the function is executedmodule.exports__webpack_module_cache__Both cache objects have the original CommonJS module ('math.js')
    5. At this point__webpack_require__After execution, the inner layer immediately executes the function that has the contents of the module for subsequent operations
    6. If it’s quoted elsewheremath.jsModule, which will be taken directly from the cached object

This is the end of the article, hope to help you.