As we all know, webpack, as a building tool, solves the problem of lack of modularity in front-end code. The code we write, after being built and packaged by webpack, can run in a modular way in the browser. These capabilities are due to the fact that webpack has a layer of packaging for our code. This paper starts with the code generated by webpack and analyzes how webpack realizes modularization.
PS: the module of webpack not only refers to js, but also css, pictures and other resources can be treated as modules, but this article only focuses on js.
prepare
First, we create a simple entry module index JS and a dependent module bar js:
//index.js 'use strict'; var bar = require('./bar'); function foo() { return bar.bar(); }
//bar.js 'use strict'; exports.bar = function () { return 1; }
The configuration of webpack is as follows:
var path = require("path"); module.exports = { entry: path.join(__dirname, 'index.js'), output: { path: path.join(__dirname, 'outs'), filename: 'index.js' }, };
This is the simplest configuration. It only specifies the module entry and output paths, but it has met our requirements.
Execute webpack in the root directory to get the following code packaged by webpack (remove unnecessary comments):
(function(modules) { // webpackBootstrap // The module cache var installedModules = {}; // 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; } // 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, { configurable: false, enumerable: true, get: getter }); } }; // 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 return __webpack_require__(__webpack_require__.s = 0); }) /************************************************************************/ ([ /* 0 */ (function(module, exports, __webpack_require__) { "use strict"; var bar = __webpack_require__(1); bar.bar(); }), /* 1 */ (function(module, exports, __webpack_require__) { "use strict"; exports.bar = function () { return 1; } }) ]);
analysis
The code packaged by the above webpack can be simplified into the following structure:
(function (modules) {/* Omit function content */}) ([ function (module, exports, __webpack_require__) { /* Module index JS code */ }, function (module, exports, __webpack_require__) { /* Module bar JS code */ } ]);
As you can see, the code generated by the whole package is an Iife (execute function immediately). We'll see the content of the function later. Let's analyze the parameters of the function first.
Function parameters are an array of modules we write, but our code is wrapped inside a function by webpack, that is, our module is a function here. The reason for this is that the browser itself does not support modularization, so webpack uses the function scope to hack the effect of modularization.
If you debug the node code, you will find the same hack method. The module in the node is also a function. The parameters related to the module are exports, require, or other parameters__ filename and__ dirname and so on pass values through functions as variables in the module. The access between the module and external modules is carried out through these parameters. Of course, this is transparent to developers.
In the same way, webpack also controls the module, exports and require of the module. Let's see how webpack implements these functions.
The following is the extracted function content, with some comments added:
// 1. Module cache object var installedModules = {}; // 2. require implemented by webpack function __webpack_require__(moduleId) { // 3. Determine whether the module has been cached if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // 4. Cache module var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // 5. Call module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 6. Mark module as loaded module.l = true; // 7. Return module exports return module.exports; } // 8. require first module return __webpack_require__(__webpack_require__.s = 0);
After the module array is passed into the IIFE function as a parameter, IIFE does some initialization:
- IIFE first defines installedModules, which is used to cache the loaded modules.
- Defined__ webpack_require__ For this function, the function parameter is the id of the module. This function is used to implement the module require.
- __ webpack_require__ The function first checks whether the loaded module is cached. If so, it directly returns the exports of the cached module.
- If there is no cache, that is, the first load, initialize the module first and cache the module.
- Then we call the module function, that is, the wrapping function of webpack in front of our module, module and module.. Exports and__ webpack_require__ Passed in as a parameter. Note that a dynamic binding is made here to bind the calling object of the module function as module Exports to ensure that this in the module points to the current module.
- When the call is complete, the module is marked as loaded.
- Returns the contents of the module exports.
- Using the previously defined__ webpack_require__ Function, require the 0th module, that is, the entry module.
When the entry module is require d, the entry module will receive three parameters. The following is the entry module code:
function(module, exports, __webpack_require__) { "use strict"; var bar = __webpack_require__(1); bar.bar(); }
The first parameter module passed in from webpack is the currently cached module, including the information and exports of the current module; The second parameter exports is module Exports, which also conforms to the specification of commonjs; Third__ webpack_require__ Is the implementation of require.
In our module, we can use module Exports or exports, using__ webpack_require__ Import the required modules, and the code is exactly the same as commonjs.
In this way, the requirement for the first module is completed, and then the first module will load other modules in turn according to its requirements for other modules, and finally form a dependency mesh structure. webpack manages the caching of these modules. If a module is required multiple times, there will only be one loading process, and the cached content will be returned, which is also the norm of commonjs.
conclusion
At this point, webpack hack s the commonjs code.
The principle is still very simple. In fact, it is to implement exports and require, and then automatically load the entry module and control the cache module, that's all.
If you are careful, you will find that this article only introduces the implementation of commonjs by webpack, so how is ES6 module implemented? Welcome to the second part of this series webpack modularization principle - ES6 module.