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 process and principles of how Webpack5 helps you implement ES Module support in your code by analyzing the source code.
The preparatory work
Here, the preparation of the source code to do a brief description:
Js/js/js/js/js/js/js/js/js/js/js/js/js/js
// src/js/math.js
export const sum = (num1, num2) = > {
return num1 + num2
}
export const mul = (num1, num2) = > {
return num1 * num2
}
Copy the code
2. Create an index.js file in the SRC directory as the entry of WebPack. In index.js, use ES Module to introduce the two functions in Math.js. The code looks like this:
/ / SRC/index. Js entrance
import { sum, mul } from './js/math'
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; (() = > {
// Strict mode
'use strict'
// 1. Define a __webpack_Modules__ object
var __webpack_modules__ = {
'./src/js/math.js': (__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, {
sum: () = > sum,
mul: () = > mul
})
const sum = (num1, num2) = > {
return num1 + num2
}
const mul = (num1, num2) = > {
return num1 * num2
}
}
}
// 2. A cache object is defined
var __webpack_module_cache__ = {}
// 3. Define webPack's own 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
}
// 4. An immediate execution function; (() = > {
__webpack_require__.d = (exports, definition) = > {
for (var key in definition) {
if( __webpack_require__.o(definition, key) && ! __webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true.get: definition[key]
})
}
}
}
})()
// 5. An immediate execution function; (() = > {
__webpack_require__.o = (obj, prop) = >
Object.prototype.hasOwnProperty.call(obj, prop)
})()
// 6. An immediate execution function; (() = > {
__webpack_require__.r = exports= > {
if (typeof Symbol! = ='undefined' && Symbol.toStringTag) {
Object.defineProperty(exports.Symbol.toStringTag, { value: 'Module'})}Object.defineProperty(exports.'__esModule', { value: true})}}) ()// 7. Define the __webpack_exports__ variable, which initializes an empty object
var __webpack_exports__ = {}
// 8. An immediate execution function; (() = > {
__webpack_require__.r(__webpack_exports__)
var _js_math_js__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__('./src/js/math.js')
console.log((0, _js_math_js__WEBPACK_IMPORTED_MODULE_0__.sum)(10.20))
console.log((0, _js_math_js__WEBPACK_IMPORTED_MODULE_0__.mul)(10.20))
})()
})()
Copy the code
If you read my first CommonJS analysis, you’ll notice that the ES Module packs a little more, but that’s ok, let’s take a look at it.
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.
There are eight main parts, including:
- Defines three variables (
__webpack_modules__
.__webpack_module_cache__
.__webpack_exports__
) - Defines a function (
__webpack_require__
) - Four immediate execution functions
First let’s look at the three variables defined:
// 1. Define an __webpack_modules__ object.
var __webpack_modules__ = {
'./src/js/math.js': (__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {
__webpack_require__.r(__webpack_exports__)
__webpack_require__.d(__webpack_exports__, {
sum: () = > sum,
mul: () = > mul
})
const sum = (num1, num2) = > {
return num1 + num2
}
const mul = (num1, num2) = > {
return num1 * num2
}
}
}
Copy the code
The first variable: __webpack_modules__, whose key is the path to math.js, and whose value is a function. The first two methods are executed in the function, and here are the two functions exported from the original math.js.
// 2. A cache object is defined
var __webpack_module_cache__ = {}
Copy the code
The second variable, __webpack_module_cache__, is a cache object and is initialized as an empty object.
// 7. Define the __webpack_exports__ variable, which initializes an empty object
var __webpack_exports__ = {}
Copy the code
The third variable, __webpack_exports__, is initialized to an empty object and is currently unknown.
Next, let’s look at the three instant-execute functions labeled 4/5/6:
// 4. An immediate execution function; (() = > {
__webpack_require__.d = (exports, definition) = > {
for (var key in definition) {
if( __webpack_require__.o(definition, key) && ! __webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true.get: definition[key]
})
}
}
}
})()
Copy the code
This section assigns the d attribute of __webpack_require__ to a function, the contents of which we will leave untouched.
In JavaScript, a function is a special kind of object, so you can also add properties and their values to it.
// 5. An immediate execution function; (() = > {
__webpack_require__.o = (obj, prop) = >
Object.prototype.hasOwnProperty.call(obj, prop)
})()
Copy the code
This section assigns the o property of __webpack_require__ to a function that takes obj and prop and determines whether the prop is a property of object itself.
// 6. An immediate execution function; (() = > {
__webpack_require__.r = exports= > {
if (typeof Symbol! = ='undefined' && Symbol.toStringTag) {
Object.defineProperty(exports.Symbol.toStringTag, { value: 'Module'})}Object.defineProperty(exports.'__esModule', { value: true})}}) ()Copy the code
This section assigns the r attribute of __webpack_require__ to a function that takes an export parameter. This function assigns a flag to an ES Module. Easy to identify later.
Simple syntax analysis:
Symbol.toStringTag
Symbol. ToStringTag is a built-in Symbol that is usually used as a key for an object’s property. The corresponding property value should be a string that represents the object’s custom type tag. Usually only built-in Object. The prototype. The toString () method to read the label and put it in his own return values. – the MDN
- If the Symbol does not support ES6, it will pass
Symbol.toStringTag
Mode tag ofES Module
- If Symbol is not supported, add a key-value pair to the exports object where the key is ‘__esModule’ and the value is an object
Exports is an ES Module. Exports is an ES Module.
These are some definitions that are easy to read, but here’s where the actual implementation begins:
// 8. An immediate execution function; (() = > {
// Mark the __webpack_exports__ object as an ES Module
// Note that in this case, only the __webpack_exports__ variable is defined and marked. It is not used elsewhere
__webpack_require__.r(__webpack_exports__)
// Let's do some transformations to the contents of our own index.js, and then execute
// The variable name is too long, it doesn't matter, it means the ES Module modular math.js object
// Execute __webpack_require__ with the path of math.js as an argument, and return a value to the variable with the long name
var _js_math_js__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__('./src/js/math.js')
console.log((0, _js_math_js__WEBPACK_IMPORTED_MODULE_0__.sum)(10.20))
console.log((0, _js_math_js__WEBPACK_IMPORTED_MODULE_0__.mul)(10.20))
})()
Copy the code
(0, _js_math_js__webpack_imported_module_0__.sum)(10, 20) _js_math_js__webpack_imported_module_0__.sum (10, 20).
Var _js_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(‘./ SRC /js/math.js’) __webpack_require__ is the same as __webpack_require__ in CommonJS. If you don’t want to know more about __webpack_require__, see my CommonJS article.
function __webpack_require__(moduleId) {
// moduleId is './ SRC /js/math.js'
// This line of code in the __webpack_module_cache__ cache object takes moduleId as key to get its corresponding value and assigns 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:{}} = module;
// Because {exports:{}} is an object, module and __webpack_module_cache__[moduleId] have the same reference, and if they change, they change together
// In this case, __webpack_module_cache__['./ SRC /js/math.js'] and module are both {exports: {}}.
var module = (__webpack_module_cache__[moduleId] = {
exports: {}})// The __webpack_modules__ object from the first part is used. The key of the object is moduleId, which is './ SRC /js/math.js'.
// Take the function __webpack_modules__['./ SRC /js/math.js'], pass in the next 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
Next look at the execution of the __webpack_modules__[moduleId] function:
(__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
// The function takes three arguments (only the first argument is used in CommonJS, and both are used in ESModule).
// The first argument is module, the second is module.exports, and the third is the __webpack_require__ function
// Execute the R method of __webpack_require__, marked as the ES Module
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
__webpack_require__.r(__webpack_exports__)
// Execute the d method of __webpack_require__, passing two arguments
// The first parameter value is module.exports
// The second argument is an object, key is the name of the exported function, value is an arrow function, and the return value is the function corresponding to the key
__webpack_require__.d(__webpack_exports__, {
sum: () = > sum,
mul: () = > mul
})
// These are the two functions we exported in math.js
const sum = (num1, num2) = > {
return num1 + num2
}
const mul = (num1, num2) = > {
return num1 * num2
}
}
Copy the code
Let’s look at the execution of the d function:
// The d function of __webpack_require__
// Exports is actually module. Exports
{sum: () => sum, mul: () => mul}
(exports, definition) => {
// Iterate over the definition object
for (var key in definition) {
// __webpack_require__.o determines if prop is a property of obj itself
}}}}}}}}}}}}}}}}}}}}}}}
// exports[key] = definition[key]
if( __webpack_require__.o(definition, key) && ! __webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true.get: definition[key]
})
}
}
}
Copy the code
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
At this point, the __webpack_require__ function completes, and the module.exports it returns are received by the _js_math_js__WEBPACK_IMPORTED_MODULE_0__ variable, With the sum and mul functions derived from this variable, you can perform the following operations.
conclusion
The process of handling the ES Module is a bit more complex than the way Webpack5 handles CommonJS modularity, but the overall pattern is similar, especially the __webpack_require__ function and the cache object.
Let’s go through the process one more time
- Defines three variables (
__webpack_modules__
.__webpack_module_cache__
.__webpack_exports__
) - Defines a function (
__webpack_require__
) and add the function object with three immediately executing functionsd / o / r
Methods. - Set module path
'./src/js/math.js'
As a parameter, execute__webpack_require__
methods __webpack_require__
Function will be{exports:{}}
Continuously assign to__webpack_module_cache__
Cache objects andmodule
Variables,module
and__webpack_module_cache__
The same reference, every change changes- will
module
、module.exports
,__webpack_require__
As an argument__webpack_modules__
Object, after the function is executedmodule.exports
和__webpack_module_cache__
The cache object has the original ES Module ('math.js'
) - At this point
__webpack_require__
After execution, the inner layer immediately executes the function which has already taken the contents of the module to operate - If it’s quoted elsewhere
math.js
Module, which will be taken directly from the cached object
Compared to CommonJS
Similarities:
- The general idea and the way are the same (feel said nonsense, you can taste a product)
Difference:
- Added markup specifically to the ES Module
- A layer of proxy is made for the content exported by the module
Some possible difficulties
Object.defineProperty()
- Functions are special objects that can add properties just like normal objects
- Comma expression
- Symbol.toStringTag
If you are not clear about the above content, you can consult the relevant materials to study.
This is the end of this article, I hope it has been helpful to you, have questions or comments can be answered in the comments section.