前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ESM 是如何被 webpack 打包成 CommonJS 格式的

ESM 是如何被 webpack 打包成 CommonJS 格式的

作者头像
山月
发布2022-11-02 16:47:00
1.3K0
发布2022-11-02 16:47:00
举报

虽然现代主流浏览器已支持 ESM,但 webpack 仍然会将 ESM 转化为 CommonJS,并注入到运行时代码。

webpack 是如何将 ESM 转化为 CommonJS 的?或者说含有 ESM 的运行时代码是什么样子的?

源码见 node-examples:webpack/es1

1. 示例代码

在 esm 中,导入导出有两种方式:

  • 具名导出/导入: Named Import/Export
  • 默认导出/导入: Default Import/Export

以下是 index.js 文件内容:

代码语言:javascript
复制
import sum, { name } from './sum'
import * as s from './sum'

console.log(sum(3, 4))
console.log(name)
console.log(s)

以下是 sum.js 文件内容:

代码语言:javascript
复制
const sum = (...args) => args.reduce((x, y) => x + y, 0)

export default sum
export const name = 'sum'

2. 转化

由于在 esm 中,有两种导入导出的方式,但是在 cjs 中只有一种。

因此在转化时,将 default import/export 转化为 module.exports.default,而 named import/export 转化为 module.exports 对应的属性。

示例如下:

代码语言:javascript
复制
// esm 代码
import sum, { name } from './sum'
import * as s from './sum'

// 转化后的 cjs 代码
const s = require('./sum')
const sum = s.default
const name = s.name

3. ESM 运行时代码

分析其运行时代码,相对于 CommonJS 而言,在 __webpack_require__ 中多了几个属性,代码如下:

源码见 node-examples:webpack/es1,执行 node build.js 可见运行时代码。main.js 文件内容见 main.js2,总共 93 行,相对比 CommonJS 的 55 行,多了一半有余。

代码语言:javascript
复制
/* 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 });
 };
})();

多的三个函数属性总计如下:

  • __webpack_require__.d:使用 getter/setter 用以定义 exports 的属性
  • __webpack_require__.rexports.__esModule = true,用以标记一个 ESM 模块
  • __webpack_require__.oObject.prototype.hasOwnProperty 的简写

sum 模块的源码与 webpack 运行时的代码对比如下:

代码语言:javascript
复制
// 源码
const sum = (...args) => args.reduce((x, y) => x + y, 0)

export default sum
export const name = 'sum'

// 运行时代码
// 该模块被 module_wrapper 包裹
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
 // 1. 标记该模块是一个 ESM 模块
  __webpack_require__.r(__webpack_exports__);

 // 2. 导出所有的属性,即 __webpack_exports__,但通过 getter/setter 方式,可以懒加载属性
  __webpack_require__.d(__webpack_exports__, {
    "default": () => (__WEBPACK_DEFAULT_EXPORT__),
    "name": () => (/* binding */ name)
  });

 // 3. 执行代码,配置 getter/setter 的属性
  const sum = (...args) => args.reduce((x, y) => x + y, 0)

 // 即 export default
  const __WEBPACK_DEFAULT_EXPORT__ = (sum);

  const name = 'sum'
})
  1. 包裹函数同样由 (module, module.exports, __webpack_require__) 包裹而成
  2. __webpack_require__.r 标记其为 ESM 模块
  3. __webpack_require__.d 定义其所有导出的值
  4. __WEBPACK_DEFAULT_EXPORT__ 常量标记 export default

4. webpack 如何将 ESM 转成 CommonJS 的

TODO

5. 作业

  1. 对含 ESM 模块的 webpack 运行时代码进行调试与理解
  2. webpack 含 ESM 的运行时代码做了那些事情

参考资料

[1]

node-examples:webpack/es:https://github.com/shfshanyue/node-examples/tree/master/engineering/webpack/es

[2]

node-examples:webpack/es:https://github.com/shfshanyue/node-examples/tree/master/engineering/webpack/es

[3]

main.js:https://github.com/shfshanyue/node-examples/blob/master/engineering/webpack/es/example/main.js

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 全栈成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 示例代码
  • 2. 转化
  • 3. ESM 运行时代码
  • 4. webpack 如何将 ESM 转成 CommonJS 的
  • 5. 作业
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档