Packing principle
- In short, it is to generate ast syntax tree, and generate corresponding JS code according to the syntax tree
- Here we only analyze the results of the packaged output, and analyze what webpack has done to our code from the results
analysis
// main.js
//Import through commonjs specification
const show = require('./show.js');
//Execute the show function
show('Webpack');
// show.js
//Operate the DOM element to display the content on the web page
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
//Export show function through commonjs specification
module.exports = show;
// bundle.js
(
//Webbackbootstrap startup function
//Modules is an array of all modules. Each element in the array is a function
function (modules) {
//All the installed modules are stored here
//The function is to cache the loaded modules in memory to improve performance
var installedModules = {};
//To load a module in the array, moduleid is the index of the module to be loaded in the array
//Role and Node.js Similar to the require statement in
function __webpack_require__(moduleId) {
//If the module to be loaded has been loaded, it will be returned directly from the memory cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
//If there is no module to load in the cache, create a new module and store it in the cache
var module = installedModules[moduleId] = {
//Index of module in array
i: moduleId,
//Has the module been loaded
l: false,
//The exported value of the module
exports: {}
};
//Get the function corresponding to the module whose index is moduleid from modules
//Call the function again, and pass in the required parameters of the function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
//Mark this module as loaded
module.l = true;
//Returns the exported value of this module
return module.exports;
}
//The public path in the webpack configuration is used to load the separated asynchronous code
__webpack_require__.p = "";
//Use__ webpack_ require__ To load the module with index 0 and return the exported content of the module
//The module with index 0 is main.js The corresponding file is the execution entry module
// __ webpack_ require__ . s means the index corresponding to the startup module
return __webpack_require__(__webpack_require__.s = 0);
})(
//All modules are stored in an array, and the modules are distinguished and located according to the index of each module in the array
[
/* 0 */
(function (module, exports, __webpack_require__) {
//Through__ webpack_ require__ Import the show function in the specification, show.js The corresponding module index is 1
const show = __webpack_require__(1);
//Execute the show function
show('Webpack');
}),
/* 1 */
(function (module, exports) {
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
//Export show function through commonjs specification
module.exports = show;
})
]
);
//The above seemingly complex code is actually an immediate execution function, which can be abbreviated as follows:
(function(modules) {
//Simulate require statement
function __webpack_require__() {
}
//Execute the 0 th module in all modules array
__webpack_require__(0);
}) ([/ * array of all modules * /])
- You can see that
bundle.js
Is a self executing function, the input parameter ismain.js
andshow.js
After the transformation of the code block formed by the array
- It runs in the self executing function
__webpack_require__
In this function, the input parameter is 0. 0 is actually the corresponding input parameter in the code block array, representing the first code block
- Let’s see
__webpack_require__
Function, the first execution is the cache judgment, through themoduleId
Judge whether it has been loaded before. If it has been loaded, return the direct loading result directlyexports
,mouduleId
It is the index of different code modules in the input parameter group
- If it has not been loaded, create a new object. The most important thing is the
exports
Attribute, which stores the things exported by the corresponding module after loading the module
- And then use this
exports
Execute the corresponding code block as the context, and pass the parameters as the newly created module, the exports in the module, and the__webpack_require__
The method itself
- And then you see
main.js
The requirement in is transformed into__webpack_require__
,__webpack_require__(1)
Represents loading the second code block
- In the second code block, the show method is defined, and then show will act as the module.exports The export of, that is, the assignment to
installedModules[0].module.exports
In other words, this export has been cached. Next time it is used in other places, it will be exported directly
- This is the general packaging idea of webpack, which transforms each individual module into an array as an input parameter, passes it to the self executing function, and maintains a
installedModules
Record the loaded module, use the index in the module array as the key value, and exports record the exported object
Load on demand
- Because single page applications also have the concept of routing, before switching to the corresponding routing, you may not want the browser to download JS of this part of the page, so as to improve the speed of opening the home page, which involves a lazy loading, that is, on-demand loading
- Webpack is loaded on demand through
import(XXX)
Implemented, import () is a proposal, and webpack supports it
//Asynchronous loading show.js
import(/* webpackChunkName: 'show' */ './show').then((module) => {
//Execute the show function
const show = module.default;
show('Webpack');
});
- By packaging in this way, we can find that the final packaged file is divided into two parts,
bundle.js
andshow.xxx.js
- among
/* webpackChunkName: 'show' */
It is specially annotated to webpack to specify the name of the package to be loaded on demand. At the same time, remember to configure thechunkFilename: '[name].[hash].js'
Otherwise, the designation will not take effect
- Let’s look at the entry file first. Hide all the functions that are not used for the time being as follows:
(function (modules) {
//Webpackjsonp is used to install modules from asynchronously loaded files
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
//... omitted first
};
//Cache installed modules
var installedModules = {};
//Store the loading status of each chunk;
//The key is the ID of the chunk, and a value of 0 indicates that it has been loaded successfully
var installedChunks = {
1: 0
};
//Simulate the require statement, which is consistent with the above
function __webpack_require__(moduleId) {
//... omit the same as above
}
//It is used to load the file corresponding to the chunk that is split and needs to be loaded asynchronously
__webpack_require__.e = function requireEnsure(chunkId) {
//... omitted first
};
//Load and execute the entry module, as described above
return __webpack_require__(__webpack_require__.s = 0);
})
(
//Store all modules that have not been loaded asynchronously and are loaded with the entry file
[
// main.js Corresponding module
(function (module, exports, __webpack_require__) {
//Through__ webpack_ require__ . e to load asynchronously show.js Corresponding chunk
__webpack_require__.e('show').then(__webpack_require__.bind(null, 'show')).then((show) => {
//Execute the show function
show('Webpack');
});
})
]
);
- You can see that
import(xxx).then
It was replaced by__webpack_require__.e(0).then
,__webpack_require__.e(0)
A promise is returned
- The first then is equivalent to execution
__webpack_require__(1)
But it is obvious that there is only one element in the input parameter group of the self executing function, and there is no [1]. When was this [1] inserted
- to glance at
__webpack_require__.e
The realization of
__webpack_require__.e = function requireEnsure(chunkId) {
//Get the loading status of the chunk corresponding to the chunk ID from the installedchunks defined above
var installedChunkData = installedChunks[chunkId];
//If the loading status is 0, it means that the chunk has been loaded successfully, and directly returns to resolve promise
if (installedChunkData === 0) {
return new Promise(function (resolve) {
resolve();
});
}
//Installedchunk data is not empty and not 0, indicating that the chunk is being loaded on the network
if (installedChunkData) {
//Returns the promise object stored in the installedchunk data array
return installedChunkData[2];
}
//If the installedchunk data is empty, it means that the chunk has not been loaded. Load the corresponding file of the chunk
var promise = new Promise(function (resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise;
//Through DOM operation, insert a script tag into HTML head to load the JavaScript file corresponding to chunk asynchronously
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.async = true;
script.timeout = 120000;
//The path of the file is composed of the configured publicpath and chunkid
script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
//Set the maximum timeout for asynchronous loading
var timeout = setTimeout(onScriptComplete, 120000);
script.onerror = script.onload = onScriptComplete;
//Callback when script loading and execution complete
function onScriptComplete() {
//Prevent memory leaks
script.onerror = script.onload = null;
clearTimeout(timeout);
//To check whether the chunk corresponding to the chunk ID is successfully installed, it will exist in installedchunks only when the installation is successful
var chunk = installedChunks[chunkId];
if (chunk !== 0) {
if (chunk) {
chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
}
installedChunks[chunkId] = undefined;
}
};
head.appendChild(script);
return promise;
};
- First, judge whether the chunk ID has been loaded. If so, return a resolve promise directly
- If it is neither empty nor 0, it means that it is being loaded
installedChunks[chunkId]
It’s an array that holds[resovle, reject]
, which is assigned when the network request is initiated
- If the above two judgments are not hit, it means that it has not been loaded. Next, we will start to construct the loading method, mainly in the form of jsonp
- First, create a new promise, and
installedChunks[chunkId]
Assign a value and save the project and its resolve and reject in it, which is why the above can be judgedinstalledChunks[chunkId]
If it is neither empty nor 0, it is in the middle of a request. The third value of the array, the new promise, is returned directly, so that subsequent operations can register callbacks on this promise
- Then, the following method is to construct a script tag and insert it into the head to ensure that the code can be downloaded immediately. At the same time, it defines the callback when the code is executed. It judges that the code has been loaded. If the loading is successful, it clears the listening. If the loading fails, it throws an exception
- Finally, the promise is returned for external registration callback
- Here, the code loaded through jsonp is another file separated from the package
show.xx.js
That is, it is loaded asynchronouslyshow.js
Related code
webpackJsonp(
//The ID of the module stored in other files
['show'],
//Modules included in this document
{// show.js Corresponding module
show: (function (module, exports) {
function show(content) {
window.document.getElementById('app').innerText = 'Hello,' + content;
}
module.exports = show;
})
}
);
- Next, let’s see how the webbackjsonp method is defined
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
//Add moremodules to the modules object
//Mark all modules corresponding to chunkids as loaded successfully
var moduleId, chunkId, i = 0, resolves = [], result;
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (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];
}
}
while (resolves.length) {
resolves.shift()();
}
};
chunkIds
It represents the ID name of the file, because when loading dynamically, it uses the dynamically loaded file name to form a script tag for downloading. This ID is passed in here to trigger the subsequent resolve of promise, mark the module and be loaded
moreModules
Is the corresponding code module set
executeModules
It is the index of the module to be executed after loading
- First, traverse
installedChunks
As mentioned earlierinstalledChunks[chunkId]
When downloading through the network, three values are given back to represent its corresponding promise. Here, the first resolve is taken out and saved. At the same time, the load flag is set to 0, indicating that it has been loaded
- Then traverse the dynamically loaded module and insert the code block into the modules array
- Finally, execute the previously saved resolve function to trigger the
__webpack_require__.e(0).then
Implementation of
- In this way, the dynamically loaded code is downloaded by constructing jsonp, and the corresponding code is transferred to the
bundle.js
In the then function__webpack_require__
Execution module, cache output
- Here, in order to facilitate understanding, we have to make some adjustments to the code. The real output can be viewed through the specific packaging output. Here, we only describe the specific packaging idea