我们把沿用之前的例子,但是在那基础之上把导入的文件改成 ESModule。

  1. /** ./src/index.js **/
  2. let title = require('./title');
  3. console.log(title.default);
  4. console.log(title.age);
  1. /** ./src/title.js **/
  2. export default 'title_name';
  3. export const age = 'title_age';

现在我们来尝试实现以下打包后的源码。
参考 打包后文件分析 我们拷贝过来一些主要通用的代码。

  1. ;(()=>{
  2. var modules = {
  3. './src/title.js':(module, exports, require)=>{
  4. //... 导入的模块代码
  5. }
  6. }
  7. // 缓存
  8. var cache = {};
  9. // require 方法
  10. function require(moduleId){
  11. if(cache[moduleId]){
  12. return cache[moduleId].exports
  13. }
  14. var module = cache[moduleId] = {
  15. exports: {}
  16. };
  17. modules[moduleId](module, module.exports, require);
  18. return module.exports;
  19. }
  20. })()

声明模块定义

在模块中,一旦 webpack 检测到你的代码里有 export 和 import 关键字,它就认为这个事 ES Module,然后调用require上的 r 方法 和 d 方法,如下:

  1. ;(()=>{
  2. var modules = {
  3. './src/title.js':(module, exports, require)=>{
  4. // 一旦 webpack 检测到你的代码里有 export 和 import 关键字,它就认为这个事 ES Module
  5. require.r(exports);
  6. require.d(exports, {
  7. default: () => DEFAULT_EXPORT,
  8. age: () => age
  9. });
  10. const DEFAULT_EXPORT = 'title_name';
  11. const age = 'title_age';
  12. }
  13. }
  14. var cache = {};
  15. function require(moduleId){
  16. //如果缓存中有此模块对应的缓存数据
  17. if(cache[moduleId]){
  18. //直接返回模块的结果
  19. return cache[moduleId].exports
  20. }
  21. var module = cache[moduleId] = {
  22. exports: {}
  23. };
  24. modules[moduleId](module, module.exports, require);
  25. return module.exports;
  26. }
  27. })()

require.r 方法

这个方法接收导出对象为参数,主要有两个功能

  • 给导出对象设置 Symbol.toStringTag 为 Module,通过 Object 原型上的 toString 方法可以获取他的类型字符串 [object Module]
  • 给导出对象新增一个 __esModule 属性为true,表明他是一个 ES Module。 ```javascript ;(()=>{ var modules = { ‘./src/title.js’:(module, exports, require)=>{

    1. // 一旦 webpack 检测到你的代码里有 export 和 import 关键字,它就认为这个事 ES Module
    2. require.r(exports);
    3. require.d(exports, {
    4. default: () => DEFAULT_EXPORT,
    5. age: () => age
    6. });
    7. const DEFAULT_EXPORT = 'title_name';
    8. const age = 'title_age';

    } }

    var cache = {}; function require(moduleId){ //如果缓存中有此模块对应的缓存数据 if(cache[moduleId]){

    //直接返回模块的结果
    return cache[moduleId].exports
    

    } var module = cache[moduleId] = {

    exports: {}
    

    }; modulesmoduleId; return module.exports; }

    // exports.esModule = true 你可以通过它来判断转化前是不是 ES 模块。 require.r = (exports) => { Object.defineProperty(exports, Symbol.toStringTag, { value: ‘Module’ }); Object.defineProperty(exports, ‘esModule’, { value: true }) }

})()

<a name="Syj4y"></a>
## require.d 方法
这个方法接收两个入参

- exports 导出对象
- definition 模块导出的属性

主要的功能就是将被导入模块中导出的属性赋值给 exports。<br />ES 模块默认导出 export default 会挂载到 exports.default 上,age 会挂载到 exports.age 上<br />如下:
```javascript
;(()=>{
  var modules = {
    './src/title.js':(module, exports, require)=>{
      // 一旦 webpack 检测到你的代码里有 export 和 import 关键字,它就认为这个事 ES Module
      require.r(exports);
      // ES 模块默认导出 export default 会挂载到 exports.default 上,age 会挂载到 exports.age 上
      require.d(exports, {
        default: () => DEFAULT_EXPORT,
        age: () => age
      });
      const DEFAULT_EXPORT = 'title_name';
      const age = 'title_age';
    }
  }

  var cache = {};
  function require(moduleId){
    //如果缓存中有此模块对应的缓存数据
    if(cache[moduleId]){
      //直接返回模块的结果
      return cache[moduleId].exports
    }
    var module = cache[moduleId] = {
      exports: {}
    };
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }

  // exports.__esModule = true 你可以通过它来判断转化前是不是 ES 模块。
  require.r = (exports) => {
    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    Object.defineProperty(exports, '__esModule', { value: true })
  }

  require.d = (exports, definition) => {
    //这实际上是一个闭包。
    for(var key in definition){
      Object.defineProperty(exports, key, { get: definition[key] })
    }
  }

  let title = require('./src/title.js');
  console.log(title.default);
  console.log(title.age);
})()

结果

符合我们预期的打印结果。
image.png

webpack 打包源码

基本上和我们自己实现的差不多。但是在源码中还有个 o 方法,这个方法是判断属性是否是对象本身的,而不是通过原型链获取的,对结果影响不是很大。

(() => {
  var __webpack_modules__ = ({
    "./src/title.js":
      ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        "use strict";
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, {
          "age": () => (age),
          "default": () => (__WEBPACK_DEFAULT_EXPORT__)
        });
        const __WEBPACK_DEFAULT_EXPORT__ = ('title_name');
        const age = 'title_age';
      })
  });
  var __webpack_module_cache__ = {};
  function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = __webpack_module_cache__[moduleId] = {
      exports: {}
    };
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.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_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  })();
  (() => {
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  })();
  var __webpack_exports__ = {};
  (() => {
    let title = __webpack_require__("./src/title.js");
    console.log(title);
    console.log(title.age);
  })();
})()
  ;