Node.js:cjs、捆绑器和 esm 简史
介绍
如果您是 Node.js 开发人员,您可能听说过 cjs 和 esm 模块,但可能不确定为什么有两个模块以及它们如何在 Node.js 应用程序中共存。这篇博文将简要介绍 Node.js 中 JavaScript 模块的历史(带有示例?),以便您在处理这些概念时更加自信。
全球范围
最初,JavaScript 仅具有全局作用域,所有成员均已声明。共享代码时这是有问题的,因为两个独立的文件可能对成员使用相同的名称。例如:
greet-1.js
function greet(name) { return `Hello ${name}!`; }
greet-2.js
var greet = "...";
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Collision example</title> </head> <body> <!-- After this script, `greet` is a function --> <script src="greet-1.js"></script> <!-- After this script, `greet` is a string --> <script src="greet-2.js"></script> <script> // TypeError: "greet" is not a function greet(); </script> </body> </html>
CommonJS 模块
Node.js 通过 CommonJS(也称为 cjs)正式引入了 JavaScript 模块的概念。这解决了共享全局范围的冲突问题,因为开发人员可以决定导出什么(通过 module.exports)和导入(通过 require())。例如:
src/greet.js
// this remains "private" const GREETING_PREFIX = "Hello"; // this will be exported function greet(name) { return `${GREETING_PREFIX} ${name}!`; } // `exports` is a shortcut to `module.exports` exports.greet = greet;
src/main.js
// notice the `.js` suffix is missing const { greet } = require("./greet"); // logs: Hello Alice! console.log(greet("Alice"));
npm 包
由于 npm 包允许开发人员发布和使用可重用的 JavaScript 代码,Node.js 开发迅速流行。 npm 包默认安装在 node_modules 文件夹中。所有 npm 包中存在的 package.json 文件尤其重要,因为它可以通过“main”属性指示 Node.js 哪个文件是入口点。例如:
node_modules/greeter/package.json
{ "name": "greeter", "main": "./entry-point.js" // ... }
node_modules/greeter/entry-point.js
module.exports = { greet(name) { return `Hello ${name}!`; } };
src/main.js
// notice there's no relative path (e.g. `./`) const { greet } = require("greeter"); // logs: Hello Bob! console.log(greet("Bob"));
捆绑者
npm 包能够利用其他开发人员的工作,从而极大地提高了开发人员的工作效率。然而,它有一个主要缺点:cjs 与网络浏览器不兼容。为了解决这个问题,捆绑器的概念诞生了。 browserify 是第一个捆绑器,其本质上是通过遍历入口点并将所有 require() 代码“捆绑”到与 Web 浏览器兼容的单个 .js 文件中来工作的。随着时间的推移,其他具有附加功能和差异化因素的捆绑器也被引入。最值得注意的是 webpack、parcel、rollup、esbuild 和 vite(按时间顺序排列)。
ECMAScript 模块
随着 Node.js 和 cjs 模块成为主流,ECMAScript 规范维护者决定纳入模块概念。这就是为什么原生 JavaScript 模块也被称为 ESModules 或 esm(ECMAScript 模块的缩写)。
esm 定义了用于导出和导入成员的新关键字和语法,并引入了默认导出等新概念。随着时间的推移,esm 模块获得了新的功能,例如动态 import() 和顶级等待。例如:
src/greet.js
function greet(name) { return `Hello ${name}!`; }
src/part.js
var greet = "...";
src/main.js
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Collision example</title> </head> <body> <!-- After this script, `greet` is a function --> <script src="greet-1.js"></script> <!-- After this script, `greet` is a string --> <script src="greet-2.js"></script> <script> // TypeError: "greet" is not a function greet(); </script> </body> </html>
随着时间的推移,得益于捆绑程序和 TypeScript 等语言,esm 被开发人员广泛采用,因为它们能够将 esm 语法转换为 cjs。
Node.js cjs/esm 互操作性
由于需求不断增长,Node.js 在 12.x 版本中正式添加了对 esm 的支持。与 cjs 的向后兼容性实现如下:
- Node.js 将 .js 文件解释为 cjs 模块,除非 package.json 将“type”属性设置为“module”。
- Node.js 将 .cjs 文件解释为 cjs 模块。
- Node.js 将 .mjs 文件解释为 esm 模块。
当涉及到 npm 包兼容性时,esm 模块可以使用 cjs 和 esm 入口点导入 npm 包。然而,相反的情况也有一些警告。举个例子:
node_modules/cjs/package.json
// this remains "private" const GREETING_PREFIX = "Hello"; // this will be exported function greet(name) { return `${GREETING_PREFIX} ${name}!`; } // `exports` is a shortcut to `module.exports` exports.greet = greet;
node_modules/cjs/entry.js
// notice the `.js` suffix is missing const { greet } = require("./greet"); // logs: Hello Alice! console.log(greet("Alice"));
node_modules/esm/package.json
{ "name": "greeter", "main": "./entry-point.js" // ... }
node_modules/esm/entry.js
module.exports = { greet(name) { return `Hello ${name}!`; } };
以下运行正常:
src/main.mjs
// notice there's no relative path (e.g. `./`) const { greet } = require("greeter"); // logs: Hello Bob! console.log(greet("Bob"));
但是,以下命令无法运行:
src/main.cjs
// this remains "private" const GREETING_PREFIX = "Hello"; // this will be exported export function greet(name) { return `${GREETING_PREFIX} ${name}!`; }
不允许这样做的原因是因为 esm 模块允许顶级等待,而 require() 函数是同步的。可以重写代码以使用动态 import(),但由于它返回一个 Promise,因此它强制具有如下所示的内容:
src/main.cjs
// default export: new concept export default function part(name) { return `Goodbye ${name}!`; }
为了缓解此兼容性问题,一些 npm 包通过利用带有条件导出的 package.json 的“exports”属性来公开 cjs 和 mjs 入口点。例如:
node_modules/esm/entry.cjs:
// notice the `.js` suffix is required import part from "./part.js"; // dynamic import: new capability // top-level await: new capability const { greet } = await import("./greet.js"); // logs: Hello Alice! console.log(greet("Alice")); // logs: Bye Bob! console.log(part("Bob"));
node_modules/esm/package.json:
{ "name": "cjs", "main": "./entry.js" }
注意“main”如何指向 cjs 版本,以便向后兼容不支持“exports”属性的 Node.js 版本。
结论
这(几乎)是您需要了解的有关 cjs 和 esm 模块的全部信息(截至 2024 年 12 月?)。请在下面告诉我你的想法!
以上是Node.js:cjs、捆绑器和 esm 简史的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。
