Modular Processing of Webpack (1)

Preface and Preparation:

  • This series of articles aims to educate the reader about WebPack modularity;

    • We’ll explore WebPack’s approach to modularity in terms of packaging results; However, the compilation details of WebPack (such as ACorn doing AST transformations, etc.) and the underlying build method are not covered.

    • This article uses the current latest WebPack (5.66.0); Before reading this article, it is recommended to clone the code and then read the article for better understanding:

  • Code store address

  • As we know, WebPack supports a variety of module syntax styles, including ES6, CommonJS and AMD.

    • So what is the magic of Webpack that makes it compatible with various modular styles?
    • The import function can be loaded asynchronously in browsers that do not support es Module.

Start our WebPack exploration with these questions in mind.

Before we look at webPack’s modular approach, let’s look at our file structure:

The modules marked in red are asynchronously loaded modules, commonJs synchronously loaded modules, esModule synchronously loaded modules entry files (*_index) and referenced files.

Look again at the WebPack configuration:

Our webpack.config.js configuration:

const pathLib = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const options = {
    mode:"development".entry: {
        commonJs_sync_index: pathLib.resolve(__dirname, "./src/commonJs_sync_index.js"),
        es_sync_index: pathLib.resolve(__dirname, "./src/es_sync_index.js"),
        async_index: pathLib.resolve(__dirname, "./src/async_index.js")},output: {
        path: pathLib.resolve(__dirname, "./dist"),  // Exit position
        publicPath: ' './ / initial named after the chunk
        filename: 'js/[name].initial.js'./ / no - initial named after the chunk
        chunkFilename: 'js/async/[name].chunk.[id].[contenthash].js'.clean: true,},watch: true.watchOptions: {
        poll: 1000.// How many times per second
        aggregateTimeout: 500.// How many milliseconds will it take to trigger again
        ignored: /node_modules/ // Ignore real-time listening
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: 'Analyzing WebPack modules: CommonJs Sync Mode'.filename: 'commonJs_sync_index.html'.template: 'index.html'.chunks: ['commonJs_sync_index'].minify: {
                removeComments: true.// Delete comments
                collapseWhitespace: false.// Cancel to remove whitespace
                removeAttributeQuotes: true // Remove attribute quotes}}),new HtmlWebpackPlugin({
            title: 'Analyzing WebPack module: Es Sync Mode'.filename: 'es_sync_index.html'.template: 'index.html'.chunks: ['es_sync_index'].minify: {
                removeComments: true.collapseWhitespace: false.removeAttributeQuotes: true}}),new HtmlWebpackPlugin({
            title: 'Analyzing the Webpack module: Import () pattern'.filename: 'async_index.html'.template: 'index.html'.chunks: ['async_index'].minify: {
                removeComments: true.collapseWhitespace: false.removeAttributeQuotes: true}}})]module.exports = options;
Copy the code

Here we have configured multiple entry (three), respectively corresponding to the three modularization modes mentioned earlier, easy to distinguish and debug. Let’s start by exploring CommonJs’s modular approach

Modularization of CommonJs (corresponding to commonJs_sync)

Let’s take a look at how modular Webpack is handled for CommonJs; In commonJs_sync_index.js, we write our code as follows:

const sync = require('./commonJs_sync');;
console.log('commonJs_sync', sync);
Copy the code

In commonJs_sync. In js:

module.exports = 'sync';
Copy the code

After compiling our webpack, we found commonJs_sync_index.initial.js file in our dist/js folder (this is our compiled distribution code, which is the final code to run in the browser), with the following content:

/* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to  create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */
/ * * * * * * / (() = > { // webpackBootstrap
/ * * * * * * / 	var __webpack_modules__ = ({

/ * * * / "./src/commonJs_sync.js":
/ *! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/commonJs_sync.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * / ((module) = > {

eval("module.exports = 'sync'; \n\n//# sourceURL=webpack://webpack_module_analysis/./src/commonJs_sync.js?");

/ * * * / }),

/ * * * / "./src/commonJs_sync_index.js":
/ *! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/commonJs_sync_index.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * / ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) = > {

eval("const sync = __webpack_require__(/*! ./commonJs_sync */ \"./src/commonJs_sync.js\");; \nconsole.log('commonJs_sync', sync); \n\n//# sourceURL=webpack://webpack_module_analysis/./src/commonJs_sync_index.js?");

/ * * * / })

/ * * * * * * / 	});
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * / 	// The module cache
/ * * * * * * / 	var __webpack_module_cache__ = {};
/ * * * * * * / 	
/ * * * * * * / 	// The require function
/ * * * * * * / 	function __webpack_require__(moduleId) {
/ * * * * * * / 		// Check if module is in cache
/ * * * * * * / 		var cachedModule = __webpack_module_cache__[moduleId];
/ * * * * * * / 		if(cachedModule ! = =undefined) {
/ * * * * * * / 			return cachedModule.exports;
/ * * * * * * / 		}
/ * * * * * * / 		// Create a new module (and put it into the cache)
/ * * * * * * / 		var module = __webpack_module_cache__[moduleId] = {
/ * * * * * * / 			// no module.id needed
/ * * * * * * / 			// no module.loaded needed
/ * * * * * * / 			exports: {}
/ * * * * * * / 		};
/ * * * * * * / 	
/ * * * * * * / 		// Execute the module function
/ * * * * * * / 		__webpack_modules__[moduleId](module.module.exports, __webpack_require__);
/ * * * * * * / 	
/ * * * * * * / 		// Return the exports of the module
/ * * * * * * / 		return module.exports;
/ * * * * * * / 	}
/ * * * * * * / 	
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * / 	
/ * * * * * * / 	// startup
/ * * * * * * / 	// Load entry module and return exports
/ * * * * * * / 	// This entry module can't be inlined because the eval devtool is used.
/ * * * * * * / 	var __webpack_exports__ = __webpack_require__("./src/commonJs_sync_index.js");
/ * * * * * * / 	
/ * * * * * * / })()
;
Copy the code

We can clearly see that the generated content is an immediate execution function (IIFE), which has many advantages, such as not exposing private attributes (such as __webpack_require__), encapsulating module business logic, etc. Let’s not worry about the detailed look, let’s first analyze the general structure of packaging results:

These four parts also lead to four corresponding questions:

  1. What exactly is __webPack__modules__? Where is the source code we wrote? What does the so-called __webpack__modules__ have to do with my source code?
  2. What is __webpack_module_cache__? What does it do?
  3. What does the __webpack_require__ function do? What does it do?
  4. When we run the packaged file, we will immediately start loading execution from the loading entry file. How is this entry confirmed?

Next we take our question step by step analysis: first of all we first solve the first question, __webpack__modules__ is what, we first analyze __webpack__modules__.

  • Our packaging result consists of two modules (functions) :
    • One is the module (function) corresponding to the entry file, and the module ID is./src/commonJs_sync.js(In development mode, the id name is the relative path to the file);
    • One is the module ID of the entry synchronization reference file (“./ commonjs_sync.js “)./src/commonJs_sync_index.jsModule (function) of;

Modules generally correspond to source files one by one; The __webpack_modules__ object holds all modules. These modules (functions) are generated by iterating through the referenced file, starting with the entry file.

Webpack module is actually a webpack processing after the function; The reason for encapsulating it as a function is to use function characteristics to emulate modules, isolate context, and create private variables and methods without breaking the global namespace. The most important thing is that you can simulate calling modules by calling functions; Only when the module is called is it actually executed.

Answer question 1: What exactly is __webpack__modules__? Where is the source code we wrote? What does the so-called __webpack__modules__ have to do with my source code?

Answer: __webpack_modules__ is an object that caches all current modules (functions). How do we call the module? Use __webpack_require__; We then parse the __webpack_require__ call. What do we do?

As you can see, we use a caching proxy (__webpack_module_cache__) to hold the export of each module; Calling __webpack_reqruie__ goes through the following process:

  1. Look in __webpack_module_cache__ and return the cached result if the module has a cache;

  2. Modules without caching generate new modules (objects) and cache __webpack_module_cache__. Note that the modules (objects) are different from the modules (functions) mentioned above; Here the module (object) refers to the output of the module (function) after execution;

  3. Find the corresponding module function in __webpack_modules__ and execute the module function

  4. Return module export;

As you can see, __webpack_require__ is very similar to the use of require in CommonJs, and in fact is very similar to the modularity of CommonJs.

Answer question 2: What is __webpack_module_cache__? What does it do?

Answer: __webpack_module_cache__ is used to cache the results of module loading.

Answer question 3: What does the __webpack_require__ function do? What does it do?

Answer: __wepback_require__ is the method webpack uses to load modules (similar to require).

How does Webpack determine entry? This problem is actually easy to fix, remember we configured entry in webpack.config.js? We have the following configuration:

commonJs_sync_index: pathLib.resolve(__dirname, "./src/commonJs_sync_index.js")
Copy the code

Q4: when we run the packaged file, we will immediately start loading and executing from the loading entry file. How is this entry confirmed?

Wepback relies on the entry configuration to determine the entry.

Chunk

Of course, one of the phases of WebPack building to produce our final code, which is not mentioned here, is the construction of chunk, the so-called chunk, which is a concept within the webPack runtime; It’s a collection of modules; It usually corresponds to the final bundle, but it doesn’t have to. For example, if you configure the Source map, it will produce two final files, which fall into the following three categories:

  1. An entry file: consists of an entry file and its synchronously loaded dependencies (as in this chapter);
  2. Asynchronous loading: consists of modules that are loaded in asynchronously (e.g., import() asynchronously; More on that in the next section);
  3. Code segmentation: dependentoptimization.splitChunksConfiguration production.

Here’s how webPack is packaged;

This is the end of Webpack’s explanation of CommonJs’s modular handling

Modularization handling of esModules (corresponding to ES_sync)

You may wonder how webPack supports esModule modularity if it implements commonJs modularity:

  1. How does WebPack make it so that if the original value changes, the import loaded value also changes?
  2. How about importing variables that do not allow overwriting?

With these two questions in mind, we will start our code analysis. First, we will write our code in the modular style of esModule

In commonJs_sync_index. In js:

import {sync} from './es_sync';
console.log('es_sync', sync);

setTimeout(() = > {
    console.log('es_sync_change', sync);
}, 3000);
Copy the code

In es_sync. Js. In js:

setTimeout(() = > {
    sync = 'sync_change'
}, 1000);
export let sync = 'sync';

Copy the code

After compiling our webpack, we found that es_sync_index.initial.js was generated in our dist/js folder, with the following contents:

/* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to  create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */
/ * * * * * * / (() = > { // webpackBootstrap
/ * * * * * * / 	"use strict";
/ * * * * * * / 	var __webpack_modules__ = ({

/ * * * / "./src/es_sync.js":
/ *! * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/es_sync.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * / ((__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {

eval("__webpack_require__.r(__webpack_exports__); \n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"sync\": () => (/* binding */ sync)\n/* harmony export */ }); \nsetTimeout(() => {\n sync = 'sync_change'\n}, 1000); \nlet sync = 'sync'; \n\n\n//# sourceURL=webpack://webpack_module_analysis/./src/es_sync.js?");

/ * * * / }),

/ * * * / "./src/es_sync_index.js":
/ *! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *! * \! *** ./src/es_sync_index.js ***! A \ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * / ((__unused_webpack_module, __webpack_exports__, __webpack_require__) = > {

eval("__webpack_require__.r(__webpack_exports__); \n/* harmony import */ var _es_sync__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./es_sync */ \"./src/es_sync.js\"); \n\nconsole.log('es_sync', _es_sync__WEBPACK_IMPORTED_MODULE_0__.sync); \n\nsetTimeout(() => {\n console.log('es_sync_change', _es_sync__WEBPACK_IMPORTED_MODULE_0__.sync); \n}, 3000); \n\n//# sourceURL=webpack://webpack_module_analysis/./src/es_sync_index.js?");

/ * * * / })

/ * * * * * * / 	});
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * / 	// The module cache
/ * * * * * * / 	var __webpack_module_cache__ = {};
/ * * * * * * / 	
/ * * * * * * / 	// The require function
/ * * * * * * / 	function __webpack_require__(moduleId) {
/ * * * * * * / 		// Check if module is in cache
/ * * * * * * / 		var cachedModule = __webpack_module_cache__[moduleId];
/ * * * * * * / 		if(cachedModule ! = =undefined) {
/ * * * * * * / 			return cachedModule.exports;
/ * * * * * * / 		}
/ * * * * * * / 		// Create a new module (and put it into the cache)
/ * * * * * * / 		var module = __webpack_module_cache__[moduleId] = {
/ * * * * * * / 			// no module.id needed
/ * * * * * * / 			// no module.loaded needed
/ * * * * * * / 			exports: {}
/ * * * * * * / 		};
/ * * * * * * / 	
/ * * * * * * / 		// Execute the module function
/ * * * * * * / 		__webpack_modules__[moduleId](module.module.exports, __webpack_require__);
/ * * * * * * / 	
/ * * * * * * / 		// Return the exports of the module
/ * * * * * * / 		return module.exports;
/ * * * * * * / 	}
/ * * * * * * / 	
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * / 	/* webpack/runtime/define property getters */
/ * * * * * * / 	(() = > {
/ * * * * * * / 		// define getter functions for harmony exports
/ * * * * * * / 		__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] });
/ * * * * * * / 				}
/ * * * * * * / 			}
/ * * * * * * / 		};
/ * * * * * * /}) ();/ * * * * * * / 	
/ * * * * * * / 	/* webpack/runtime/hasOwnProperty shorthand */
/ * * * * * * / 	(() = > {
/ * * * * * * / 		__webpack_require__.o = (obj, prop) = > (Object.prototype.hasOwnProperty.call(obj, prop))
/ * * * * * * /}) ();/ * * * * * * / 	
/ * * * * * * / 	/* webpack/runtime/make namespace object */
/ * * * * * * / 	(() = > {
/ * * * * * * / 		// define __esModule on exports
/ * * * * * * / 		__webpack_require__.r = (exports) = > {
/ * * * * * * / 			if(typeof Symbol! = ='undefined' && Symbol.toStringTag) {
/ * * * * * * / 				Object.defineProperty(exports.Symbol.toStringTag, { value: 'Module' });
/ * * * * * * / 			}
/ * * * * * * / 			Object.defineProperty(exports.'__esModule', { value: true });
/ * * * * * * / 		};
/ * * * * * * /}) ();/ * * * * * * / 	
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * / 	
/ * * * * * * / 	// startup
/ * * * * * * / 	// Load entry module and return exports
/ * * * * * * / 	// This entry module can't be inlined because the eval devtool is used.
/ * * * * * * / 	var __webpack_exports__ = __webpack_require__("./src/es_sync_index.js");
/ * * * * * * / 	
/ * * * * * * / })()
;
Copy the code

Let’s compare commonJs-style compiled code; The __webpack_require__, __webpack_require__. O, and __webpack_require__. And we export modules differently. Let’s take a look at what these three methods do:

__webpack_require__. D: D is short for definePropertyGetters; Used to define getter properties; __webpack_require__.o: o is short for hasOwnProperty; Determines whether the object has the property; __webpack_require__.r: r is short for makeNamespaceObject; To define the export of the esModule module.

The “./ SRC /es_sync_index.js” module is replaced by __webpack_require__. Note The method of importing modules has not changed. The _webpack_require__.r(__webpack_exports__) sentence is used to simulate the definition of esModule;

The key treatment lies in this sentence:

__webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"sync\": () => (/* binding */ sync)\n/* harmony export */ });
Copy the code

Question 1: How does WebPack make it so that if the original value changes, the import loaded value also changes?

Answer: Exprts. Exprts defines a getter sync in the export module module.exports, which caches references to the sync variable using closures. Ensure that every time you access the sync property, you get the latest value (variable sync). After 1000ms sync variable is assigned to ‘sync_change’; After another 2000ms, _ES_SYNc__webpack_imported_module_0__. sync is read to obtain the getter, which is the latest value ‘sync_change’. It is this clever manipulation that allows WebPack to change the original value and import load values as well

Question 2: how does Webpack make it so that imported variables cannot be overwritten?

Solution: And in strict mode, there is no set assignment. And that’s it. You can’t overwrite the variables that you import. Webpack mimics the modular approach of esModules in this way.

Conclusion:

Webpack can handle different styles of modularity because WebPack compiles source code, processes entry files and iterates through the various files it synchronously relies on, compiling these files into modules (functions) and caching them in __webpack_modules__. Immediately invoke the module (function) corresponding to the entry file and, during execution, invoke each dependent module (function) via __webpack_require__. In general, Webpack is through the compilation of source code, to achieve its own modular way, unified processing of each modular style.

This is the end of synchronous load modularization and the next lecture will cover the asynchronous load modularization of WebPack modularization.