首页/文章/javascript

CommonJS 和 ES Module 终于要兼容了?

2024-08-02
11574 分钟
...

最近网上看到一个挺有意思的消息,让我不禁想跟大家聊聊。那就是 ECMAScript ModuleCommonJS 的互操作性问题终于有望解决了!是不是很惊喜,这可是困扰咱们开发者多年的老大难问题啊。说到这儿,你是不是又想起了 ERR_REQUIRE_ESM

CJS 和 ESM 的前世今生

JavaScript 的世界里,模块化是构建大型应用程序的基础。模块化可以帮助开发者在不影响全局命名空间的前提下管理代码,便于功能分离、代码复用和依赖管理。CommonJSECMAScript Module 就是这两大模块化方案的代表。

CommonJS(CJS)

CommonJSNode.js 原生支持的模块系统,使用 require 函数加载模块,用 module.exportsexports 对象将代码暴露为模块。其特点是同步加载,意味着代码会在模块被加载完成后立即执行:

`// math.js
function add(x, y) {
  return x + y;
}
module.exports = { add };

// app.js
const math = require('./math.js');
console.log(math.add(0, 17)); // 打印出 17

`

在服务器环境中,同步加载通常不是问题,因为文件大都在本地。然而,在浏览器环境中,同步加载可能会导致性能问题,因为它会阻塞浏览器的事件循环,直到脚本完全下载和解析。

ECMAScript Module(ESM)

ESM 是现代 JavaScript 的官方标准模块系统,使用 importexport 语句进行模块的导入和导出,支持异步加载:

`// math.js
export function add(x, y) {
  return x + y;
}

// app.js
import { add } from './math.js';
console.log(add(0, 17)); // 打印出17

`

ESM 的设计允许浏览器优化加载和解析过程,如通过 HTTP/2 进行有效的并行加载,以及进行 tree shaking 以剔除未使用的代码,从而增强性能和效率。但是,在 Node.js 中,ESM 的异步特性与现有的大量 CommonJS 模块存在不兼容问题。

当前在 Node.js 中启用 ESM 的方法要复杂一些,因为代表性的 .js 文件扩展名默认与 CommonJS 模块关联。为了解决此问题,Node.js 允许使用 .mjs 文件扩展名或在 package.json 中明确指定 "type": "module" 属性来表示 ESM 模块。

为啥不能兼容?

自然地,人们可能会问:为什么 require() 就不能支持加载 ESM 呢?

很长一段时间以来,Node.js 项目的答案总是这样:

使用 require 来加载 ES 模块是不被支持的,因为 ES 模块是异步执行的。

这也是为什么 ERR_REQUIRE_ESM 这个错误总是让人难受的原因之一。

早期的尝试

其实,社区早在 2019 年就开始探讨如何支持 ESMCommonJS 之间的互操作性。期间,不少开发人员提交了 Pull Requests,提出不同的实现方案和改进措施。

当时一个具有里程碑意义的 PR 讨论集中在如何在 Node.js 中支持 .mjs 后缀的文件,以及如何实现一个双模块系统,可以同时支持 CommonJSESM 。不过,这些尝试都因为各种技术和安全性问题未能成功。

支持同步 require(esm)

在去年年末,joyeecheung 发现根据语法,ESM 可以是同步的。而且,只有当代码中包含顶级 await 时才会异步。这意味着我们可以支持同步 require(esm),只要确保不包含顶级 await

这就有了 joyeecheung 最近提交的关键 Pull Request

https://github.com/nodejs/node/pull/51977

这个 PR 尝试使 require(esm) 的范围保持小,并且只支持加载同步 ESM。事实证明,这在技术指导委员会(TSC)中根本不是一个有争议的想法,并且没有遭到多少争议。

目前,这个特性仍然在实验阶段,需要使用 --experimental-require-module 标志来启用。而且,只支持那些显式标记为 ESM 的模块,比如 .mjs 扩展名的文件或者在 package.json 中指定 "type": "module" 的包。尽管如此,这已经为许多困扰开发者的问题提供了解决方案。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2026-02-24
navigator.sendBeacon全指南
在前端开发中,埋点系统是必不可少的一环。我们经常需要在用户 关闭页面 、 刷新 或 跳转路由 时,向服务器发送最后一条统计数据(比如用户停留时长、页面跳出率)。 但这看似简单的需求,在实现时却危机四伏:请求发不出去?页面跳转卡顿?今天我们就来聊聊这个问题的终极解决方案 —— 。 一、 痛点与传统方案的挣扎 场景还原 当用户点击关闭按钮时,浏览器会触发生命周期事件( 或 )。如果我们直接使用普通的异步 AJAX ( 或 ) 发送请求,浏览...
学习
javascript2025-11-02
理解浏览器事件系统,从用户点击到事件对象的完整旅程
深入理解浏览器事件系统:从用户点击到事件对象的完整旅程 “当我点击页面按钮时,背后发生了什么?为什么回调函数能收到一个包含丰富信息的event对象?今天,让我们一起揭开浏览器事件系统的神秘面纱。” 一个令人困惑的现象 作为前端开发者,我们每天都在写这样的代码: 这段代码如此熟悉,以至于我们很少停下来思考:​ ​这个 对象到底从哪里来?它为什么能知道点击的精确坐标?为什么能识别是哪个元素被点击了?​ ​ 更神奇的是,当我们手动创建事件时:...
学习
javascript2025-10-01
实现大文件上传全流程详解
在日常开发中,大文件上传是个绕不开的坎——动辄几百 MB 甚至 GB 级的文件,直接上传不仅容易超时,还会让用户体验大打折扣。最近我用 Vue+Express 实现了一套完整的大文件上传方案,支持分片上传、断点续传、秒传和手动中。 一、先看效果:我们要实现什么? 先上核心功能清单,确保大家明确目标,知道我们要解决哪些实际问题: 大文件分片上传 :将文件切成固定大小的小片段分批上传,避免单次请求超时 秒传 :服务器已存在完整文件时,直接返...
学习
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-15
一张 8K 海报差点把首屏拖垮
你给后台管理系统加了一个「企业风采」模块,运营同学一口气上传了 200 张 8K 宣传海报。首屏直接飙到 8.3 s,LCP 红得发紫。 老板一句「能不能像朋友圈那样滑到哪看到哪?」——于是你把懒加载重新翻出来折腾了一轮。 解决方案:三条技术路线,你全踩了一遍 1\. 最偷懒:原生 一行代码就能跑,浏览器帮你搞定。 🔍 关键决策点 2020 年后现代浏览器全覆盖,IE 全军覆没。 必须写死 ,否则 CLS 会抖成 PPT。 适用场景...
学习
javascript2025-09-10
🚀 Web Worker让你的应用丝滑
🌟 引言 在日常的前端开发中,你是否遇到过这样的困扰: 大数据处理时页面卡死 :处理几万条数据时,页面直接卡成PPT,用户点击毫无反应 复杂计算阻塞UI :图片处理、数据分析等计算密集型任务让整个应用假死 文件上传/下载卡顿 :大文件操作时,其他功能完全无法使用 实时数据处理性能差 :WebSocket接收大量数据时,页面渲染严重滞后 今天分享6个Web Worker的核心技巧,让你的应用告别卡顿,用户体验丝滑如德芙! 💡 核心技巧...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录