Webpack is a module packer in which every file is a module.
Whether you are developing using CommonJS or ES6 module specifications, the packaged files use webPack’s custom module specifications to manage and load modules. This article will start with a simple example to explain how the WebPack module loads.
CommonJS specification
Suppose we now have the following two files:
// index.js
const test2 = require('./test2')
function test() {}
test()
test2()
Copy the code
// test2.js
function test2() {}
module.exports = test2
Copy the code
The above two files use the CommonJS specification to import and export files. The packaged code looks like this (unnecessary comments have been removed) :
(function(modules) { // webpackBootstrap
// The module cache
// The module caches objects
var installedModules = {};
// The require function
// Webpack implements require()
function __webpack_require__(moduleId) {
// Check if module is in cache
// If the module is already loaded, return to cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
// Create a new module and place it in the cache
var module = installedModules[moduleId] = {
i: moduleId,
l: false.exports: {}};// Execute the module function
// Execute the module function
modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);
// Flag the module as loaded
// Mark the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// expose the modules object (__webpack_modules__)
// Mount all modules to the require() function
__webpack_require__.m = modules;
// expose the module cache
// Mount the cache object to the require() function
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(! __webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true.get: getter }); }};// define __esModule on exports
__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;
};
// getDefaultExport function for compatibility with non-harmony 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.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// Load entry module and return exports
// Load the entry module and return the module object
return __webpack_require__(__webpack_require__.s = "./src/index.js"); ({})"./src/index.js": (function(module.exports, __webpack_require__) {
eval("const test2 = __webpack_require__(/*! ./test2 */ \"./src/test2.js\")\r\n\r\nfunction test() {}\r\n\r\ntest()\r\ntest2()\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/test2.js": (function(module.exports) {
eval("function test2() {}\r\n\r\nmodule.exports = test2\n\n//# sourceURL=webpack:///./src/test2.js?"); })});Copy the code
As you can see, the module loading system implemented by WebPack is very simple, with only a hundred lines of code.
The packaged code is really an immediate function, passing in an object as an argument. This object takes the file path as key, the file content as value, and contains all packaged modules.
{
"./src/index.js": (function(module.exports, __webpack_require__) {
eval("const test2 = __webpack_require__(/*! ./test2 */ \"./src/test2.js\")\r\n\r\nfunction test() {}\r\n\r\ntest()\r\ntest2()\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/test2.js": (function(module.exports) {
eval("function test2() {}\r\n\r\nmodule.exports = test2\n\n//# sourceURL=webpack:///./src/test2.js?"); })}Copy the code
To simplify the immediate function, it is equivalent to:
(function(modules){
// ...({})path1: function1,
path2: function2
})
Copy the code
Now look at what the immediate function does:
- Defines a module cache object
installedModules
Is used to cache modules that have been loaded. - Defines a module loading function
__webpack_require__()
. - . Leave out some other code.
- use
__webpack_require__()
Load the entry module.
At the heart of this is the __webpack_require__() function, which takes the moduleId, which is the file path.
Its execution process is as follows:
- Check whether the module has a cache, if so, return the cache module
export
Objects, that is,module.exports
. - Create a new module
module
And put it into the cache. - Execute the module function corresponding to the file path.
- Mark the newly created module as loaded.
- After executing the module, returns the
exports
Object.
// The require function
// Webpack implements require()
function __webpack_require__(moduleId) {
// Check if module is in cache
// If the module is already loaded, return to cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
// Create a new module and place it in the cache
var module = installedModules[moduleId] = {
i: moduleId,
l: false.exports: {}};// Execute the module function
// Execute the module function
modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);
// Flag the module as loaded
// Mark the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
Copy the code
As you can see from the code above, the module function is passed three arguments: module, module.exports, and __webpack_require__.
Module, module.exports, __webpack_require__ is equivalent to require in CommonJS.
At the end of the immediate function, the entry module is loaded using __webpack_require__(). / SRC /index.js for the entry module.
__webpack_require__(__webpack_require__.s = "./src/index.js");
Copy the code
Let’s look at the contents of the entry module again.
(function(module.exports, __webpack_require__) {
eval("const test2 = __webpack_require__(/*! ./test2 */ \"./src/test2.js\")\r\n\r\nfunction test() {}\r\n\r\ntest()\r\ntest2()\n\n//# sourceURL=webpack:///./src/index.js?");
})
Copy the code
The entry module takes exactly the same three arguments, and the eval function looks like the following after being beautified:
const test2 = __webpack_require__("./src/test2.js")
function test() {}
test()
test2()
//# sourceURL=webpack:///./src/index.js?
Copy the code
Comparing the packaged module code with the original module code, there is only one change: require becomes __webpack_require__.
Take a look at the test2.js code again:
function test2() {}
module.exports = test2
//# sourceURL=webpack:///./src/test2.js?
Copy the code
__webpack_require__() executes the module’s corresponding function and returns the module’s exports object. Module.exports of test2.js is the test2() function. So the entry module can introduce and execute test2() with __webpack_require__().
So far you can see that webPack’s custom module specification fits perfectly with the CommonJS specification.
ES6 module
Replace the two files you just wrote with the CommonJS specification with the ES6 Module specification, and then do the packaging.
// index.js
import test2 from './test2'
function test() {}
test()
test2()
Copy the code
// test2.js
export default function test2() {}
Copy the code
The code packaged using the ES6 Module specification is largely the same as the code packaged using the CommonJS specification.
The same place refers to the webpack custom module specification code is the same, the only difference is the above two files packaged code is different.
{
"./src/index.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _test2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test2 */ \"./src/test2.js\"); \n\r\n\r\nfunction test() {}\r\n\r\ntest()\r\nObject(_test2__WEBPACK_IMPORTED_MODULE_0__[\"default\"])()\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/test2.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return test2; }); \nfunction test2() {}\n\n//# sourceURL=webpack:///./src/test2.js?"); })}Copy the code
You can see that the second argument passed in is __webpack_exports__, while the CommonJS specification corresponds to exports as the second argument. To make the contents of these two modules prettier:
// index.js
__webpack_require__.r(__webpack_exports__);
var _test2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/test2.js");
function test() {}
test()
Object(_test2__WEBPACK_IMPORTED_MODULE_0__["default") ()//# sourceURL=webpack:///./src/index.js?
Copy the code
// test2.js
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "default".function() { return test2; });
function test2() {}
//# sourceURL=webpack:///./src/test2.js?
Copy the code
You can see that a __webpack_require__.r(__webpack_exports__) statement is executed at the beginning of each module. Test2.js also has a __webpack_require__.d() function.
Let’s start by looking at what __webpack_require__.r() and __webpack_require__.d() are.
webpack_require.d()
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(! __webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true.get: getter }); }};Copy the code
__webpack_require__.d() is used to define export variables for __webpack_exports__. For example, this line of code:
__webpack_require__.d(__webpack_exports__, "default".function() { return test2; });
Copy the code
This is equivalent to __webpack_exports__[“default”] = test2. The “default” is because you use export default to export functions. If you export functions like this:
export function test2() {}
Copy the code
D (__webpack_exports__, “test2”, function() {return test2; });
webpack_require.r()
// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol! = ='undefined' && Symbol.toStringTag) {
Object.defineProperty(exports.Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports.'__esModule', { value: true });
};
Copy the code
The __webpack_require__.r() function adds an __esModule attribute of true to __webpack_exports__, indicating that it is an ES6 module.
What is the use of adding this property?
This is mainly to handle mixed use of ES6 Modules and CommonJS.
Export = test2. ES6 module import test2 from ‘./test2.
The packaged code looks like this:
// index.js
__webpack_require__.r(__webpack_exports__);
var _test2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/test2.js");
var _test2__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_test2__WEBPACK_IMPORTED_MODULE_0__);
function test() {}
test()
_test2__WEBPACK_IMPORTED_MODULE_0___default()()
//# sourceURL=webpack:///./src/index.js?
Copy the code
// test2.js
function test2() {}
module.exports = test2
//# sourceURL=webpack:///./src/test2.js?
Copy the code
As you can see from the code above, there is an additional __webpack_require__.n() function:
__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;
};
Copy the code
Let’s first analyze the processing logic of the entry module:
- will
__webpack_exports__
The exported object is identified as ES6 Module. - loading
test2.js
Module, and pass in the exported object of that module as a parameter__webpack_require__.n()
Function. __webpack_require__.n
Analysis of theexport
Object is an ES6 Module, if somodule['default']
即export default
Corresponding variable. If it is not ES6 Module, return it directlyexport
.
According to the need to load
Load on demand, also called asynchronous load, dynamic import, that is, only when there is a need to download the corresponding resource file.
Import and require.ensure can be used in Webpack to introduce code that needs to be dynamically imported, as in this example:
// index.js
function test() {}
test()
import('./test2')
Copy the code
// test2.js
export default function test2() {}
Copy the code
The test2.js file imported with import is packaged as a separate file instead of bundle.js with index.js.
this0.bundle.js
The corresponding code is dynamically importedtest2.js
The code.
Let’s look at the contents of these two packages:
// bundle.js
(function(modules) { // webpackBootstrap
// install a JSONP callback for chunk loading
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [];
for(; i < chunkIds.length; i++) { chunkId = chunkIds[i];if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; }}if(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) { resolves.shift()(); }};// The module cache
var installedModules = {};
// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
var installedChunks = {
"main": 0
};
// script path function
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + "" + chunkId + ".bundle.js"
}
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false.exports: {}};// Execute the module function
modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if(installedChunkData ! = =0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error(a); onScriptComplete =function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk ! = =0) {
if(chunk) {
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; }};var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout'.target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script); }}return Promise.all(promises);
};
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(! __webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true.get: getter }); }};// define __esModule on exports
__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;
};
// getDefaultExport function for compatibility with non-harmony 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.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// on error function for async loading
__webpack_require__.oe = function(err) { console.error(err); throw err; };
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] | | [];var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js"); ({})"./src/index.js": (function(module.exports, __webpack_require__) {
eval("function test() {}\r\n\r\ntest()\r\n__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./test2 */ \"./src/test2.js\"))\n\n//# sourceURL=webpack:///./src/index.js?"); })});Copy the code
// 0.bundle.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push(
[
[0] and {"./src/test2.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__); \n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return test2; }); \nfunction test2() {}\n\n//# sourceURL=webpack:///./src/test2.js?"); }}));Copy the code
The bundle.js code is a bit bloated this time, with 200 lines of bundle.js code. Let’s look at how it differs from the synchronously loaded WebPack module specification:
- Defines an object
installedChunks
, is used to cache dynamic modules. - Defines a helper function
jsonpScriptSrc()
To generate a URL based on the module ID. - Two new core functions are defined
__webpack_require__.e()
和webpackJsonpCallback()
. - Defines a global variable
window["webpackJsonp"] = []
, which stores modules that need to be dynamically imported. - rewrite
window["webpackJsonp"]
An array ofpush()
Methods forwebpackJsonpCallback()
. That is to say,window["webpackJsonp"].push()
What we’re actually doing iswebpackJsonpCallback()
.
As you can see from the 0.bundle.js file, it uses window[“webpackJsonp”].push() to put the dynamic module. The dynamic module data item has two values. The first is [0], which is the ID of the module. The second value is the module’s pathname and module content.
Then let’s look at the code for the packaged entry module, after beautifying it:
function test() {}
test()
__webpack_require__.e(0).then(__webpack_require__.bind(null."./src/test2.js"))
//# sourceURL=webpack:///./src/index.js?
Copy the code
The import(‘./test2’) in the original module code is translated to __webpack_require__.e(0).then(__webpack_require__.bind(null, “./ SRC /test2.js”)).
So what does __webpack_require__.e() do?
webpack_require.e()
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if(installedChunkData ! = =0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error(a); onScriptComplete =function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk ! = =0) {
if(chunk) {
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; }};var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout'.target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script); }}return Promise.all(promises);
};
Copy the code
Its processing logic is as follows:
- Check whether the cache value corresponding to the module ID is 0. 0 indicates that the module is successfully loaded. The value for the first time is
undefined
. - If it’s not zero and it’s not
undefined
Represents the state that is already being loaded. Then push the loaded Promise inpromises
The array. - If it’s not zero and it’s zero
undefined
Create a new Promise for loading modules that need to be dynamically imported. - To generate a
script
Tag, URL usedjsonpScriptSrc(chunkId)
Generated, that is, the URL that needs to be dynamically imported into the module. - For this
script
The TAB sets a 2-minute timeout and sets oneonScriptComplete()
Function to handle timeout errors. - Then add it to the page
document.head.appendChild(script)
To start loading the module. - return
promises
The array.
When the JS file is downloaded, the file content is automatically executed. This means that window[“webpackJsonp”].push() will be executed after 0.bundle.js is downloaded.
Because window[“webpackJsonp”].push() has been reset to webpackJsonpCallback(). So the action is to execute webpackJsonpCallback(), and let’s see what webpackJsonpCallback() does.
webpackJsonpCallback()
Execute resolve() on the Promise corresponding to the module ID, setting the value in the cache object to 0 to indicate that the load is complete. This function is fairly straightforward compared to __webpack_require__.e().
summary
In general, the logic for dynamic imports is as follows:
- rewrite
window["webpackJsonp"].push()
Methods. - Use of entry module
__webpack_require__.e()
Download dynamic resources. - Execute after downloading the resource
window["webpackJsonp"].push()
, i.e.,webpackJsonpCallback()
. - If the resource is marked as 0, the load is complete. Since the loading module uses a Promise, it executes
resolve()
. - Look again at the loading code for the entry module
__webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))
Run the command after the download is completethen()
Method, call__webpack_require__()
Actually start loading the code,__webpack_require__()
This has been explained above, and if you don’t understand it, I suggest you read it again.