published on
tags: webpack

webpack 模块化原理

定义 mark.js

export const val = 'sd';

export default () => {
  console.log('jaja');
};

index.js

import mark from './mark';

const name = 'jack';
mark();
export default name;

下面的代码是 webpack 打包后生成的代码

(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, { 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 && typeof value != '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
  return __webpack_require__((__webpack_require__.s = './src/index.js'));
})(
  /************************************************************************/
  {
    /***/ './src/index.js':
      /*!**********************!*\
      !*** ./src/index.js ***!
      \**********************/
      /*! exports provided: default */
      /***/ function(module, __webpack_exports__, __webpack_require__) {
        'use strict';
        eval(
          '__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _mark__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./mark */ "./src/mark.js");\n\n\nconst name = \'jack\';\nObject(_mark__WEBPACK_IMPORTED_MODULE_0__["default"])();\n/* harmony default export */ __webpack_exports__["default"] = (name);\n\n\n//# sourceURL=webpack:///./src/index.js?',
        );

        /***/
      },

    /***/ './src/mark.js':
      /*!*********************!*\
      !*** ./src/mark.js ***!
      \*********************/
      /*! exports provided: default */
      /***/ function(module, __webpack_exports__, __webpack_require__) {
        'use strict';
        eval(
          '__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__["default"] = (() => {\n  console.log(\'jaja\')\n});\n\n\n//# sourceURL=webpack:///./src/mark.js?',
        );

        /***/
      },
  },
);

一步一步来看, 整体是个立即执行函数, 精简下如下

(function(modules) {
  // ...
})({
  '.src/index.js': function(module, __webpack_exports__, __webpack_require__) {
    'use strict';
    eval();
    // ...
    //...
  },
});

传入立即执行函数的 modules 对应的就是一个对象,对象的 key 是文件路径,value 是函数,函数参数为 module, webpack_exports, webpack_require. 函数内部是用使用 eval 执行,单独拿出来看下。

eval index

__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _mark__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
  /*! ./mark */ './src/mark.js',
);
const name = 'jack';
Object(_mark__WEBPACK_IMPORTED_MODULE_0__['default'])();
/* harmony default export */
__webpack_exports__['default'] = name;
//# sourceURL=webpack:///./src/index.js?

eval mark

__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */
__webpack_require__.d(__webpack_exports__, 'val', function() {
  return val;
});
const val = 'sd';
/* harmony default export */
__webpack_exports__['default'] = () => {
  console.log('jaja');
};
//# sourceURL=webpack:///./src/mark.js?

可以看到,是通过webpack_require 去加载模块,并且将我们的变量 name 赋值给 webpack_exports 的 default 属性上。至于 webpack_require.r 我们稍后分析。

接下来 看看 立即执行函数内部做了什么

// 内部定义的 module 缓存, 存放加载过的模块
var installedModules = {};
function __webpack_require__(moduleId) {
  // 如果加载过, 直接返回 installedModules里对应模块的 exports
  if (installedModules[moduleId]) {
    return installedModules[moduleId].exports;
  }
  // 没有加载过,则创建一个 moudle 变量
  var module = (installedModules[moduleId] = {
    i: moduleId, // i 代表 moduleId,也就是 './src/index.js'
    l: false, // l (loaded) 代表是否加载完毕
    exports: {}, // exports 后面导出的内容
  });

  // 执行 modules 里的方法,就是上面提到过的 立即执行函数传入的 ./src/index 对应的 value
  // function (module, __webpack_exports__, __webpack_require__) {...}
  modules[moduleId].call(
    module.exports,
    module,
    module.exports,
    __webpack_require__,
  );

  // 执行完毕,设置模块对应的 l 为 true
  module.l = true;

  // 返回 module.exports
  return module.exports;
}

eval 里主要使用了 webpack_require.r(webpack_exports)

// 在__webpack_exports__上定义 __esModule 为 true
__webpack_require__.r = function(exports) {
  if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  }
  Object.defineProperty(exports, '__esModule', { value: true });
};

这里的 webpack_exports 就是 webpack_require 函数里的 module.exports

webpack_require.d

__webpack_require__.d = function(exports, name, getter) {
  if (!__webpack_require__.o(exports, name)) {
    Object.defineProperty(exports, name, { enumerable: true, get: getter });
  }
};

通过webpack_require.d 对 module.exports 对象上赋值

也就是 eval 里的执行完以后,我们的 module.exports.default = name

分别打印 index.js 和 mark.js 对应的 module

image

image

至于 bundle.js 中的其他内容,都是些辅助函数,暂不分析了。

梳理下 webpack bundle.js 里的流程

  1. 运行立即执行函数,并传入 modules 对象,key 对应文件路径,value 对应 eval 函数
  2. 立即执行函数返回 wepack_require(‘./src/index.js’) 加载入口文件对应模块并返回相应的 exports
  3. webpack_require 里先判断是否加载过,如果加载过直接返回闭包里 installedModules 里对应的模块
  4. 如果没有,则先创建 module 对象,设置 loaded false, exports 为空对象
  5. 执行传入立即执行函数相应模块对应的 eval 方法
  6. eval 去执行 webpack_require 去加载依赖的模块,通过webpack_require.d 给 exports 对象赋值, _webpack_require** .r 定义 **esModule 为 true, 直接给 module.exports.default 赋值默认导出。
  7. 模块加载过后,设置 installedModules 里对应模块的 l flag 为 true,返回 module.exports