This paper directly to fit the bundle, if for the development of modular development, modular specification also is not very familiar with, can use this link to learn about: segmentfault.com/a/119000001…

To start the analysis, create a small demo.

Create source directory:

mkdir demo 
cd demo 
mkdir src
Copy the code

Create four files in the SRC directory: entry file index.js, esM file module1.js, dynamic.js, commonjs file common-module.js, code as follows:

// index.js
import Module1 from './module1';
import CommonJsModule from './common-module';

import('./dynamic').then((module) = > {
  console.log(`Dynamic module: The ${module.default()}`);
});

console.log(`Module 1: ${Module1()}`);
console.log(`CommonJsModule: ${CommonJsModule()}`);

// module1.js
export default function() {
  return 'This is Module1 .';
}

// dynamic.js
export default function() {
  return `This is dynamic module`;
}

// common-module.js
module.exports = function() {
  return `This is commonjs module.`;
}
Copy the code

It’s not the code in the module that matters, it’s the way the module is introduced. In the entry file, static import is introduced into the modules module, dynamic import is introduced into the dynamic module, and common-Module is introduced into the import file.

cd .. Go back to the demo directory and create webpac4, WebPack5, and Roooolup directories to install different build tools.

Install WebPack4 and configure webpack.config.js.

mkdir webpack4
cd webpack4 
npm init -y
npm install -D webpack@^4 webpack-cli@^4
Copy the code
// webpack4/webpack.config.js
module.exports = {
  mode: "development".entry: '.. /src/index.js',}Copy the code

Set script in package.json:

{
	"scripts": {
    "build": "webpack"}}Copy the code

Install WebPack5 and configure webpack.config.js.

mkdir webpack5
cd webpack5
npm init -y
npm install -D webpack@^5 webpack-cli@^4
Copy the code
/ / webpack5 webpack. Config. Js configuration is the same as the webpac4
module.exports = {
  mode: 'development'.entry: '.. /src/index.js',}Copy the code

Set script in package.json, same as webpack4.

Install rollup and configure rollup.config.js.

mkdir roooolup
cd roooolup
npm init -y
npm install -D rollup
Copy the code
// rollup.config.js
import commonjs from '@rollup/plugin-commonjs';

export default {
  input: '.. /src/index.js'.output: {
    dir: './dist'.format: 'amd',},plugins: [
    commonjs()
  ]
};
Copy the code

Set script in package.json:

{
	"scripts": {
    "build": "rollup -c rollup.config.js"}}Copy the code

The following will analyze webpack4, WebPack5 and rollup from three aspects: static loading of ESM, loading of CommonJS module and dynamic loading of ESM. When analyzing a certain type, the introduction of other modules will be commented out and only the module type currently analyzed will be focused on.

I. Analysis of loading principle of Webpack4 module

(1) Load the ESM statically

// index.js
import Module1 from './module1';
console.log(`Module 1: ${Module1()}`);
Copy the code

After NPM run build, generate main.js file in webpack4/dist directory.

The generated code in default Development mode has many comments for formatting, as shown in the following figure:

To make it easier to see the compiled code, clean up the comments. After folding the code, look at the overall structure:

The body structure is a function(modules) that executes anonymously immediately.

Expand the value passed as an argument to the executing function, as an object with the module path as Key and a function that executes code using eval as value:

Expand the anonymous function and view the code inside the function. The basic logic is as follows:

The compiled code in development mode is highly readable and clearly annotated.

First declare the installedModules variable to store the module cache, then declare the __webpack_require__ function, then hang some data and utility methods on the __webpack_require__ function object, Finally, the __webpack_require__ method is called to load the entry module.

Create the index.html file and import main.js. Debug through breakpoints to see how the module loads.

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Webpack 4</title>
</head>
<body>
  <script src="./dist/main.js"></script>
</body>
</html>
Copy the code

Open the Sources panel in Chrome Developer Tools and find the main.js file. As you can see from the whole analysis above, the module is loaded from the __webpack_require__ method, so we break a breakpoint in that method, then refresh the page, and re-execute JS.

When the code executes at the breakpoint, it first determines whether the module exists in the cache object.

When loading the entry module, installedModules object is empty, so skip the logic in if, execute Line 11, create a module object, and use the moduleId as the installedModules object key. The module object contains three properties: I is the module ID, L is set to false, and exports is an empty object {}.

The code then executes to Line 17, and gets the module function via Modules [moduleId]module, module.exports, __webpack_require__Pass it as an argument to the function, and perform the eval method.

Click the “Step” button to see the code for Eval:

The first line of the module code calls the r method. Now let’s see what the R method does. The code is shown below. Exports {}} Defines two properties for exports: / / exports; / / exports; / / exports; / / exports; / / exports; / / exports; / / exports; / / exports; / / Exports;

  • Symbol. ToStringTag: exports type;
  • __esModule: Indicates whether it is esM.

The code continues because the module1 module was introduced in the entry file and is then called__webpack_require__Function, load../src/module1.jsModule, starting the logic of loading the module again.

The eval code in module1 is shown in the figure below. Since it is an ES module, the r method is also used to initialize the exports object property, and then the default property is added to the exports object, assigning the default exported code of the module to the exports.default property.

Then the module1 module code completes, continues the logic of the __webpack_require__ function, and returns the module’s exports object.

The code continues, returning to the entry module logic, at which point the module1 module returns to the exports object and assigns a value to_module1__WEBPACK_IMPORTED_MODULE_0__Variable, continue to execute the entry module code, the entry module function is completed.

Continue with the import module’s load logic, set the load state to true and return the import module exports property:

This completes the whole process of statically loading the ES module. Make a simple summary, through the above analysis of module loading process, we can know:

  • The module code is compiled into strings that are executed in module functions as arguments to the eval method;
  • Each module has an exports attribute, which holds the exported content of the module and marks the module type and whether it is an ES module.
  • The import method is compiled to__webpack_required__Method, which is used to load the module content;

(2) Dynamic loading of ESM

// index.js
import('./dynamic').then((module) = > {
  console.log(`Dynamic module: The ${module.default()}`);
});
Copy the code

Because of the asynchronous loading module, we need to start the Web server, not just the file browser, so we’ll simply use the HTTP-server module to start the Web server.

In addition, since the index. HTML and dist directories are siblings, webpack.config.js needs to add the output.publicPath property:

module.exports = {
  mode: "development".entry: '.. /src/index.js'.output: {
    publicPath: '/dist/'}}Copy the code

Re-execute the NPM run build command to compile the source code, and find two files in the dist directory: main.js, 0.js.

When you look at the compiled code, it is still an immediate anonymous function with modules. Compared to the example of statically loading ES modules, there are some more jSONP-related properties and methods. The initialized environment variables are shown below. Modules only has the entry module at this time. The dynamic.js module is not currently present.

After initialization is complete, the call is still called first__webpack_require__(__webpack_require__.s = ".. /src/index.js");Load the entry module,

We’ll focus on how the entry module asynchronously loads the dynamic.js module, so we’ll type the breakpoint in the entry module’s eval function.

The compiled code for the entry module is as follows:

First call__webpack_require__.e(0)Load the module with chunkId 0, which is the key method for loading chunk. Method E after sorting is as follows;

This method first checks whether the Chunk has been installed in the installedChunks object, and returns if it hasPromise.all([]).

This is the first time that Chunk 0 is loaded, so the loading logic will continue. First, create a Promise object, assemble the callback functions in the Promise object into arrays [resolve, reject], assign values to InstalledChunkid and installedChunkData, respectively. Assign the promise object to installedChunkData[2] and push the promise object into promises array.

Next, create the script tag. The SRC of the resource is obtained through the jsonpScriptSrc method, which is simple but critical, passing in chunkId and concatenating it with publicPath to form the final requested resource URL.

Finally, set the timeout, load the event callback, add the script tag to the head, and the browser starts loading 0.js.

When 0. Js is loaded, execute the code in 0. Js.

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0] and {".. /src/dynamic.js": / *! * * * * * * * * * * * * * * * * * * * * * * * * *! * \! * * *.. /src/dynamic.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * /
    / *! exports provided: default */
    (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        eval("__webpack_require__.r(__webpack_exports__); \n/* harmony default export */ __webpack_exports__[\"default\"] = (function() {\n return `This is dynamic module`; \n}); \n\n//# sourceURL=webpack:///.. /src/dynamic.js?"); }}));Copy the code

In the main.js immediate function, the webpackJsonp object has been initialized with the following value:

So the push method in line 0. Js actually calls the webpackJsonpCallback(data) function.

The specific code of this method is as follows:

The data parameter is an array. Data [0] represents the chunkId corresponding to the module list. Data [1] is the module list. / SRC /dynamic.js”, chunkId is 0. ChunkId is then added to the installedChunks object to mark that the current chunk has been installed, and the list of modules is added to the Modules object.

Finally, we assign data to window[“webpackJsonp”], execute the resolove method of the chunk promise object created in __webpack_require__.e(0), and the chunk load ends here.

Continue back to the entry module and execute the first THEN method. This method calls the __webpack_require__ method to install the module, and the execution of this method has been analyzed in the statically loaded ESM and won’t be described here.

__webpack_require__.bind(null./ *! ./dynamic */ ".. /src/dynamic.js")
Copy the code

After executing the __webpack_require__ method, execute the second then method of the entry module, which is the code executed after the entry module loads the dynamic.js module, as well as the entry module.

The above demo is relatively simple, and the relationship between chunk and module is single. You can increase the complexity of demo, for example, one chunk contains multiple modules, and one bundle contains multiple chunks.

Dynamic loading of ESM summary:

  • For modules loaded asynchronously, WebPack will build the modules into independent bundles and dynamically load them by creating script tags;

(3) Load CommonJS module

Modify the entry module code to load the CommonJS module:

// index.js
import CMJ from './common-module';
console.log(CMJ());
Copy the code

After executing the build command and observing the compiled code in main.js, the code inside the function immediately executed is the same as that in the statically loaded ESM, and we directly examine the differences between the compiled code in the module.

Still type the breakpoint on the line where the entry module eval function is located.

The compiled code of the entry module is:

Common-module.js compiled code looks like this:

Perform __webpack_require__ (“.. / SRC /common-module.js”) modules[‘../ SRC /common-module.js’].exports attributes are functions exported by this module.

CMJ entry module and static load ESM compiled code comparison, compared to ESM, load CMJ module, more than a __webpack_require.n method call.

__webpack_require.nThe method code is as follows:

This method returns a method to getModuleExports, while the CMJ module returns the getModuleExports() method, which returns module directly.

Load CommonJS module summary:

  • The CommonJS module does not need to be called in compiled code__webpack_require__.r(__webpack_exports__);Functions;
  • When CommonJS modules are installed, the getter function assigns module.exports to the installedModules[moduleId].exports property;
  • Called when calling a method exported by the CommonJS module__webpack_require__.nFunction, get the getter function, get the corresponding method through the getter function;

Ii. Analysis of loading principle of Webpack5 module

With the analysis foundation of Webpack4, it is much easier for us to understand the module loading principle of Webpack5.

First, get the demo code ready.

// index.js
import CMJ from './common-module';
import module1 from './module1';
console.log(module1());
console.log(CMJ());

import('./dynamic').then(module= > {
  console.log(module.default());
})

// webpack.config.js
module.exports = {
  mode: "development".entry: '.. /src/index.js'.output: {
    publicPath: '/dist/'}}Copy the code

The file structure of Webpack5 Demo is as follows:

The compiled entry bundle of WebPack5 is folded as follows:

It is still an immediate function, but no longer takes a list of modules as an argument. The function is mainly divided into the following parts:

(1) Initialize the list of synchronous loading modules

(2) Initialize module loading related properties and methods

(3) Initialize webPack runtime properties and methods

(4) Start loading the entry module

(5) Loading module method

Loading modules __webpack_require__(moduleId) Loading modules is basically the same logic as webpack4, but simplifies module objects:

The compiled entry module code is as follows:

This is the same code as webpack4 compiled.

The common-module-js module is compiled with the same code as webpack4:

Module1.js compiled code, unlike Webpack4, in Webpack5, is called__webpack_require__.dMethods:

This method assigns the module export variable to the module.exports object, when in fact it does the same thing as WebPack4.

If the module is loaded asynchronously, the __webpack_require__.e method is loaded as follows:

When initializing the WebPack run,__webpack_require.fObject, added j method, which is an asynchronous loading module method, with webpack4__webpack_require__.eThe code is as follows:

When src_dynamic_js.js is loaded, the following code is returned:

The following logic is basically the same as Webpack4.

Summary of webpack5 module loading:

  • Module loading principle is basically the same as WebPack4, webpack5 mainly optimize the code compilation process and results;
  • Webpack5 optimization content is not the focus of this article, you can refer to this link for further information: webpack.js.org/blog/2020-1…

Iii. Loading principle analysis of Rollup module

Webpack is different from Rollup. The first difference is that Rollup does not provide a module loading mechanism as Webpack does. Instead, Rollup is implemented through the browser-supported ESM loading mechanism and third-party AMD, System, and other module loading libraries.

Now, let’s create demo code in the Roooolup directory to take a closer look at the differences between code that builds different module specifications.

Create index.html file in rooolup directory:

(1) ESM specification

To modify the rollup.config.js file, we first build the es specification module, using the browser ESM loading mechanism:

The compiled code is as follows:

Rollup places synchronously loaded modules in index.js, and asynchronously loaded modules are built as separate files and dynamically loaded using the import method, which is almost seamless with our source code.

However, since Rollup does not support non-ES modules by default, the CommonJS plug-in is used in the rollup.config.js configuration, which compiles the CommonJS module into ESM. Because Rollup compiles synchronously loaded modules into index.js, commonJS exported methods are compiled directly into index.js methods by default.

If you change the CommonJS module to dynamically loaded, the code will look like this after recompiling:

At this point, you can see that Rollup compiles the CommonJS module into the ES module.

(2) AMD specifications

Change output.format in rollup.config.js to AMD and re-execute the NPM run build build code.

Modify index.html as follows:

The code after construction is as follows:

Summary of loading principle of Rollup module:

  • Rollup does not provide the default module loading function, requiring developers to add specific module loading plug-ins according to the module specification used by the code after construction.
  • If output.format is set to UMD or Iife, code-splitting is not supported by Rollup by default, which means asynchronous module loading mechanism cannot be used and an error will be reported during compilation.
  • Rollup is much simpler than webPack because Rollup does not provide additional module loading logic.

Four,

This article analyzes webPack 4/5 and Rollup build bundles to learn about their module loading mechanism. Webpack implements a custom module loading mechanism, but Rollup does not. Understanding the loading mechanism of modules helps us to understand how to realize lazy loading of modules and how to optimize chunk, and also provides a direction for us to think about when choosing building tools.