CommonJS、AMD和RequireJS之间的关系是什么?

899

尽管我阅读了很多,但我仍然对 CommonJS、AMD 和 RequireJS 感到非常困惑。

我知道 CommonJS(曾经被称为 ServerJS)是一个定义一些 JavaScript 规范(即模块)的组,当该语言在浏览器之外使用时,可以使用 CommonJS 模块规范的实现,例如 Node.jsRingoJS,是吗?

CommonJS、Asynchronous Module Definition(AMD)和 RequireJS 之间有什么关系呢?

RequireJSCommonJS 模块定义的一种实现吗?如果是,那 AMD 又是什么呢?


36
阅读http://requirejs.org/docs/whyamd.html会更加清晰,因为它提到了所有这些。 (我将其发布为评论,因为我不认为这是一个完整的答案)。 - mmutilva
6
我可以问或添加更多内容吗?ES2015的导入语句在这些中如何适用?例如:从'ember'导入Ember。 - copenndthagen
6个回答

812

RequireJS 实现了 AMD API (来源)

CommonJS 是一种通过 exports 对象来定义模块内容的方式。简单来说,CommonJS 的实现可能像这样:

// someModule.js
exports.doSomething = function() { return "foo"; };

//otherModule.js
var someModule = require('someModule'); // in the vein of node    
exports.doSomethingElse = function() { return someModule.doSomething() + "bar"; };
基本上,CommonJS 指定你需要一个 require() 函数来获取依赖项,一个 exports 变量来导出模块内容,并且还需要一个模块标识符(它描述了与该模块有关的模块位置),该模块标识符用于要求依赖项 (来源:source)。 CommonJS 有各种实现,包括你提到的 Node.js。
CommonJS 并没有特别考虑浏览器环境,所以它在浏览器环境中不太适用(*我真的找不到任何来源来证明这一点-到处都是这么说,包括 the RequireJS site.*) 显然,这与异步加载等有关。
另一方面,RequireJS 实现了 AMD,它旨在适应浏览器环境 (source)。 显然,AMD 起初是 CommonJS 传输格式的一个分支,并发展成其自己的模块定义 API。 因此两者之间存在相似之处。 AMD 中的新功能是 define() 函数,允许模块在加载之前声明其依赖项。 例如,定义可以是:
define('module/id/string', ['module', 'dependency', 'array'], 
function(module, factory function) {
  return ModuleContents;  
});

所以,CommonJS 和 AMD 是 JavaScript 模块定义的 API ,虽然它们有不同的实现方式,但它们都源于相同的起源。

  • AMD 更适合浏览器,因为它支持异步加载模块依赖项。
  • RequireJS 是 AMD 的一种实现,同时试图保持 CommonJS 的精神(主要在模块标识符方面)。

更令人困惑的是,虽然 RequireJS 是 AMD 的实现,但它提供了一个 CommonJS 包装器,这样 CommonJS 模块几乎可以直接导入并与 RequireJS 一起使用。

define(function(require, exports, module) {
  var someModule = require('someModule'); // in the vein of node    
  exports.doSomethingElse = function() { return someModule.doSomething() + "bar"; };
});

7
请查看 http://uRequire.org 项目,它能够弥合两种格式之间的差距——可以使用任意一种(或两种)进行编写,然后部署到其中任何一种格式中,或者简单地使用 <script> 进行部署。 - Angelos Pikoulas
52
FYI,Browserify现在可以让你在浏览器中使用CommonJS了。 - Eruant
9
但是,它仍然没有像AMD那样异步的特性。 - Inanc Gumus
9
根据RequireJS文档所述,CommonJS不适用于浏览器的原因是:“CommonJS require()是同步调用,预计会立即返回模块。这在浏览器中无法很好地工作”。更多信息请参见此处 - maheshsenni
4
你可能需要根据用户请求启用一些功能;因此,AMD的异步性质可能会很方便。 - Inanc Gumus
显示剩余5条评论

212

CommonJS不仅仅是一个项目,它还定义了JavaScript的通用API和生态系统。其中一个部分是Module规范。Node.js和RingoJS是服务器端JavaScript运行时,它们都基于CommonJS模块规范实现了模块。

AMD(异步模块定义)是另一种模块规范。RequireJS可能是最流行的AMD实现。与CommonJS的一个主要区别是,AMD指定模块是异步加载的 - 这意味着模块是并行加载的,而不是通过等待加载来阻塞执行。

由于这个原因,AMD通常更多地用于客户端(浏览器内部)JavaScript开发,而CommonJS模块通常用于服务器端。然而,在任何环境中都可以使用任何模块规范 - 例如,RequireJS提供了在Node.js中运行的指导,而browserify是一个CommonJS模块实现,可以在浏览器中运行。


21
为什么CommonJS的官网如此糟糕...我只是想查看官方规范。它有语法错误,文档不完整,维基页面也无法访问。 - taco
7
这并不是异步加载模块的意思。你可能在谈论动态/惰性加载。使用异步,你建议加载一个文件,然后过一段时间它会回调当它加载完成。使用同步,你建议加载一个文件,然后整个线程将阻塞直到该文件加载完成;在文件加载完成之前不执行任何其他代码。前者可以在可预测性成本下获得更好的性能,而后者则可以每次产生相同的结果,因此更加可预测。请注意这些怪异行为可以通过各种优化措施来减轻。 - perry
谢谢你的回答。现在JS已经在ES2015中正式支持模块了,这是否意味着它们比AMD或Common JS更受欢迎? - Akhoy
1
这并不意味着它们是首选。这完全取决于开发者的需求。我认为没有留下任何选择,直接使用ES6模块并不是一个特别好的想法。然而,使用良好的UMD,您可以解决这个问题。在一般情况下,加载与AMD同步的CommonJS包是一个好(最好)的想法(为了性能改进)。如果你觉得你应该有更多的控制,显然。而且你应该。 - Maciej Sitko
这应该是被接受的答案。你的措辞更加精确简洁。 - VimNing

201

简短回答如下:

CommonJSAMD 是关于如何在JavaScript应用程序中声明模块及其依赖关系的规范(或格式)。

RequireJS 是一个符合 AMD 规范的脚本加载器库,curljs 是另一个例子。

符合 CommonJS 规范:

取自Addy Osmani 的书

// package/lib is a dependency we require
var lib = require( "package/lib" );

// behavior for our module
function foo(){
    lib.log( "hello world!" );
}

// export (expose) foo to other modules as foobar
exports.foobar = foo;

符合 AMD 标准:

// package/lib is a dependency we require
define(["package/lib"], function (lib) {

    // behavior for our module
    function foo() {
        lib.log( "hello world!" );
    }

    // export (expose) foo to other modules as foobar
    return {
        foobar: foo
    }
});

这个模块还可以在其他地方使用:

require(["package/myModule"], function(myModule) {
    myModule.foobar();
});

一些背景信息:

CommonJS 实际上不仅仅是 API 声明,只有部分与此相关。AMD 最初作为 CommonJS 列表上模块格式的草案规范,但没有达成完全一致意见,进一步发展转向 amdjs group。对于哪种格式更好的争议在于,CommonJS 尝试涵盖更广泛的问题,适合于其同步性质的服务器端开发,而 AMD 更适合于客户端(浏览器)开发,因为它具有异步性质,并且它在 Dojo 的模块声明实现中有自己的根源。

来源:


2
看代码比看描述更有帮助!:) AMD兼容实际上是指RequireJS,对吗? - Asim K T
我是否漏掉了什么,或者有些内容打错了?你定义了“package/lib”,但是却要求“package/myModule”。 - RullDawg
我总是喜欢了解一些关于为什么事情会变成现在这个样子的历史背景!感谢您提供这些背景信息! - Andru
@RullDawg 不,这里没有定义“package/lib”,它是一个第三方依赖项,在此被使用。 - Robert Siemer

30

引用

AMD:

  • 采用面向浏览器的方法
  • 选择异步行为和简化的向后兼容性
  • 没有任何文件 I/O 的概念。
  • 它支持对象、函数、构造函数、字符串、JSON 和许多其他类型的模块。

CommonJS:

  • 采用面向服务器的方法
  • 假设同步行为
  • 涵盖更广泛的关注点,如 I/O、文件系统、Promises 等等。
  • 支持非封装模块,可以感觉更接近ES.next/Harmony 规格,摆脱了 AMD 强制的 define() 封装器。
  • 只支持对象作为模块。

19

将JavaScript程序模块化为几个文件并从主js模块调用子模块是很正常的。

问题是JavaScript本身没有提供这种功能,即使在最新版本的Chrome和FF浏览器中也没有。

但是,在JavaScript中有关键字可以调用另一个JavaScript模块吗?

对于许多人来说,这个问题可能是世界末日,因为答案是否定的。


在ES5(2009年发布)中,JavaScript没有像importincluderequire这样的关键字。

ES6在2015年发布,提出了import关键字(https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/import),现在所有现代浏览器都支持它。

如果您使用Babel 6.18.0并仅使用ES2015选项进行转换

import myDefault from "my-module";

您将再次获得require

"use strict";
var _myModule = require("my-module");
var _myModule2 = _interopRequireDefault(_myModule);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

这是因为require意味着模块将从Node.js中加载。Node.js将处理从系统级文件读取到将函数包装成模块所需的一切。

因为在JavaScript中,函数是唯一的包装器来表示模块。

我对CommonJS和AMD很困惑?

CommonJS和AMD都只是两种不同的技术,如何克服JavaScript“缺陷”,以智能方式加载模块。


2

AMD

  • JavaScript中引入的一种技术,用于将JavaScript项目划分为多个文件
  • 主要用于基于浏览器的应用程序和库
  • 流行的实现包括RequireJSDojo Toolkit

CommonJS:

  • 它是处理大型项目的大量函数、文件和模块的规范
  • 最初被Mozilla在2009年1月引入时命名为ServerJS
  • 在2009年8月更名为CommonJS以展示API的广泛适用性
  • 最初的实现是面向服务器、nodejs和桌面应用程序的库

示例

upper.js文件

exports.uppercase = str => str.toUpperCase()

主要的.js文件

const uppercaseModule = require('uppercase.js')
uppercaseModule.uppercase('test')

摘要

  • AMD - 最古老的模块系统之一,最初由库require.js实现。
  • CommonJS - 为Node.js服务器创建的模块系统。
  • UMD - 另一个模块系统,建议作为通用模块系统,兼容AMD和CommonJS。

资源:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接