webpack打包产物 是如何实现cjs和esm的相互转换?

639 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

webpack 打包分析

commonjs

写一个很简单的代码: title.js

module.exports = "title";

index.js:

const title = require("./title");
console.log(title);

打包后的代码:去除注释后

(() => {
  var __webpack_modules__ = {
    "./src/title.js": (module) => {
      module.exports = "title";
    },
  };
  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;
  }
  var __webpack_exports__ = {};
  (() => {
    const title = __webpack_require__("./src/title.js");
    console.log(title);
  })();
})();

整理,分析

(() => {
  var modules = {
    // 模块名 路径 模块内容就是一个函数
    "./src/title.js": (module) => {
      module.exports = "title";
    },
  };
  // 模块缓存
  var cache = {};
  function require(moduleId) {
    // 加载模块前先查找缓存
    var cachedModule = cache[moduleId];
    if (cachedModule !== undefined) {
      // 有缓存直接返回
      return cachedModule.exports;
    }
    // 没有缓存 创建缓存对象 然后查找模块并执行
    var module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }
  var exports = {};
  (() => {
    // 导入模块其实就是去我们的modules对象中找路径对应的函数并执行,拿到执行完的module.exports的结果
    const title = require("./src/title.js");
    console.log(title);
  })();
})();

ES Module to commonjs

导出的是ES Module规范,导入是commonjs规范: name.js:

export const name = "zs";
export const obj = {
  name,
  age: 22,
};
export default "name";

index.js:

const nameDefault = require("./name");
console.log(nameDefault);

**打包产物分析:**有点妙了

(() => {
  var modules = {
    "./src/name.js": (module, exports, require) => {
      "use strict";
      require.r(exports);
      // 将导出的属性通过函数返回值的形式拿到 意味着每次取值都是动态的获取 可以获取到最新值 妙了!
      require.d(exports, {
        default: () => _DEFAULT_EXPORT__,
        name: () => name,
        obj: () => obj,
      });
      const name = "zs";
      const obj = {
        name,
        age: 22,
      };
      // 定义默认导出
      const _DEFAULT_EXPORT__ = "name";
    },
  };
  var cache = {};
  function require(moduleId) {
    var cachedModule = cache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }
  (() => {
    require.d = (exports, definition) => {
      for (var key in definition) {
        // 需要导出的属性 不能是已经在exports上定义过的属性
        if (require.o(definition, key) && !require.o(exports, key)) {
          Object.defineProperty(exports, key, {
            // 可枚举 可以获取值 但是不允许在导入后更改
            enumerable: true,
            get: definition[key],
          });
        }
      }
    };
  })();
  (() => {
    // 判断属性是否是对象自身的属性
    require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
  })();
  (() => {
    require.r = (exports) => {
      // 定义es module 标识
      // exports[Symbol.toStringTag] = "Module"
      // exports["__esModule"] = true
      if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
      }
      Object.defineProperty(exports, "__esModule", { value: true });
    };
  })();
  var exports = {};
  (() => {
    // commonjs 加载 es module的导出
    const nameDefault = require("./src/name.js");
    console.log(nameDefault);
  })();
})();

esModule to esModule

基本上没什么特殊的。取默认值的时候就是通过["default"]取值而已。

commonjs to esModule

commonjs导出,es module导入common.js:

module.exports = {
  name: "zs",
  age: 22,
  friends: ["ls", "zl"],
};

index.js:

import common, { name, age, friends } from "./common";
console.log(common, name, age, friends);

打包产物分析:

(() => {
  var modules = {
    "./src/common.js": (module) => {
      module.exports = {
        name: "zs",
        age: 22,
        friends: ["ls", "zl"],
      };
    },
  };
  var cache = {};
  function require(moduleId) {
    var cachedModule = cache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }
  (() => {
    // 取模块的默认导出内容 如果是es模块 就直接取default然后做成getter 如果是commonjs形式 也做成getter的形式 返回模块本身
    require.n = (module) => {
      var getter =
        // es的默认导出已经是getter了
        module && module.__esModule ? () => module["default"] : () => module;
      // getter的a属性就是getter函数的返回值???
      // 也就是说:我们不执行getter 直接通过 getter.a 也能拿到结果
      // 为什么是 a ? emmm 好像无所谓吧
      require.d(getter, { a: getter });
      return getter;
    };
  })();
  (() => {
    require.d = (exports, definition) => {
      for (var key in definition) {
        if (require.o(definition, key) && !require.o(exports, key)) {
          Object.defineProperty(exports, key, {
            enumerable: true,
            get: definition[key],
          });
        }
      }
    };
  })();
  (() => {
    require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
  })();
  (() => {
    require.r = (exports) => {
      if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
      }
      Object.defineProperty(exports, "__esModule", { value: true });
    };
  })();
  var exports = {};
  (() => {
    "use strict";
    // 只要打包前的模块是一个 es module 就会调用 r方法进行处理 这里index就是es
    require.r(exports);
    var _common_0__ = require("./src/common.js");
    var _common_0___default = /*#__PURE__*/ require.n(_common_0__);
    console.log(
      _common_0___default(),
      // 通过 a 属性就可以拿到default返回值
      _common_0___default.a,
      _common_0__.name,
      _common_0__.age,
      _common_0__.friends
    );
  })();
})();