-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
CommonJS 是什么
CommonJS 是一个有志于构建 JavaScript 生态圈的组织。它有一个 邮件列表,很多开发者参与其中。 整个社区致力于提高 JavaScript 程序的可移植性和可交换性,无论是在服务端还是浏览器端。
CommonJS 模块是什么
JavaScript 并没有内置模块系统(反正现在没有,需要等到 ES6 的普遍支持,不知还需要多少年),于是 CommonJS 创造了自己的。 传统的 CommonJS 模块如下:
math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
increment.js
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
CommonJS 与浏览器
仔细看上面的代码,您会注意到 require
是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口。
然而, 这在浏览器端问题多多。
浏览器端,加载 JavaScript 最佳、最容易的方式是在 document
中插入<script>
标签。但脚本标签天生异步,传统 CommonJS 模块在浏览器环境中无法正常加载。
解决思路之一是,开发一个服务器端组件,对模块代码作静态分析,将模块与它的依赖列表一起返回给浏览器端。 这很好使,但需要服务器安装额外的组件,并因此要调整一系列底层架构。
另一种解决思路是,用一套标准模板来封装模块定义:
define(function(require, exports, module) {
// The module code goes here
});
这套模板代码为模块加载器提供了机会,使其能在模块代码执行之前,对模块代码进行静态分析,并动态生成依赖列表。
为了让静态分析可行,需要遵守一些简单的 规则。
把上面例子中的模块封装起来,可得到:
math.js
define(function(require, exports, module) {
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
});
increment.js
define(function(require, exports, module) {
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
});
program.js
define(function(require, exports, module) {
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
});
上面是一种封装方案,还有各种各样的封装方案,比如 AMD、Modules/Wrappings、CommonJS/Modules 2.0 等等模块定义规范。
Sea.js 的封装方案就是 CMD 规范:CMD 模块定义规范
CommonJS 与 Sea.js
从上面可以看出,Sea.js 的初衷是为了让 CommonJS Modules/1.1 的模块能运行在浏览器端,但由于浏览器和服务器的实质差异,实际上这个梦无法完全达成,也没有必要去达成。
更好的一种方式是,Sea.js 专注于 Web 浏览器端,CommonJS 则专注于服务器端,但两者有共通的部分。对于需要在两端都可以跑的模块,可以 有便捷的方案来快速迁移。
目前 Sea.js 的模块,如果没有用到浏览器环境下的特有属性,可以很方便跑在 NodeJS 端。只要在入口文件处,引入 Sea.js 的 Node.js 版本即可:
// 让 Node 环境可以加载执行 CMD 模块
require('seajs');
var a = require('./a');
这样,a.js
就可以是一个用 define
包裹起来的 CMD 模块了。
CommonJS 的模块需要跑在浏览器端时,通过简单封装就行:
a.js
define(function(require, exports, module) {
// a.js 原来的代码
});
这样 a.js
就可以在浏览器端通过 Sea.js 加载运行。当然前提是 a.js
没有利用到服务器特有属性和模块,比如 __dirname
、process
等。
通过上面的方案,我们就实现了 CommonJS 与 Sea.js 两个生态圈的融合,可以彼此互通,让我们书写的 JavaScript 模块可移植,可在不同平台上运行。
Activity
lifesinger commentedon Jul 11, 2012
有任何问题,欢迎留言交流。
注意:已解决的问题,会在整理后删除掉。
niceue commentedon Apr 30, 2013
SeaJS貌似是继承了RequireJS的方案(兼容CommonJS),但又不属于AMD,而且也不同于CommonJS。
确实没见过CMD规范,国外只有CommonJS和AMD,如果说CMD是为了和CommonJS以及RequireJS加以区分,那么我建议像AMD一样在define上加上识别,所有符合AMD规范的Module Loader都会有
define.amd
标示,那么符合CMD规范的SeaJS是否也应该有define.cmd
。加上识别的好处是很多第三方的模块可以做到通吃AMD和CMD以及CommonJS,放到哪个环境都可以运行。不用修改第三方插件就可以直接使用。
现在很多插件都在这样做了,但只会判断
typeof define === 'function' && define.amd
希望有一天这个判断可以变成下面这样:lifesinger commentedon Apr 30, 2013
@niceue CMD 的来历,可以看这篇文档: #588
define.cmd 曾经有过,但个人觉得加上并不好。应该尽量简单一致,而不是像 RequireJS 一样搞分裂。
niceue commentedon May 1, 2013
@lifesinger 嗯,我单是从第三方考虑了,就是因为现在这种模块加载器太多了,而且不乏一些使用量很多的工具,例如RequireJS和curl,define这个全局命名随处可见,要区分一个插件所处的环境,得有更多的判断。
seajs简单的外包一层就可以变身Sea模块,其实也是优势了。而且jquery插件适配器的配置更是靓点。
,SeaJS2.0,确实越来越好了
hax commentedon Jun 14, 2013
@lifesinger 不是分裂的问题,而是如果没有标志,影响为seajs写兼容。仅仅用 typeof define == 'function' 判断CMD似乎过于霸道。
antife-yinyue commentedon Jun 17, 2013
@hax @lifesinger
这样可否?
lifesinger commentedon Jun 17, 2013
写兼容其实很简单,
typeof seajs !== "undefined"
以及typeof requirejs !== "undefined"
,这样写,其实比typeof define
以及define.amd / cmd
更靠谱。hax commentedon Jun 18, 2013
通过判断seajs和requirejs是否存在虽然“靠谱”,但是不利于其他的兼容实现。这是为什么有define.amd。其他实现加上.amd表示它实现了AMD(当然是否实现完全无误是另一回事情)。如果我检查define.amd,表示我只预期一个 AMD 实现,而不依赖 requirejs 的特别特性。CMD也是一样的。目前至少司徒正美的mass号称兼容CMD(虽然他似乎也没有检测define.cmd)。
lifesinger commentedon Jun 19, 2013
@hax 现在问题是,大家对 AMD 的理解并不一致,即便有 define.amd,不同的实现,中间细节差异还不少。要不 CMD 要加下 define.cmd
define.amd 的初始用法,是一个
{}
,类似:支持 AMD 的组件类库,理论上都需要在 amd 对象中注册下,这太 BT 了。
目前大部分用
define.amd
是当布尔值用,非 AMD 的原意。lifesinger commentedon Jun 19, 2013
我 out 了,查找了下最新的 AMD 规范:https://github.com/amdjs/amdjs-api/wiki/AMD
对 define.amd 的用法已更新。现在的定位不错。我去加一个 define.cmd
whilefor commentedon May 26, 2014
有些地方不是很理解,希望能帮忙解释解释:
对模块代码作静态分析,静态分析是怎么运作的,怎么解决了对浏览器端对require方法的异步问题?
sunmenghua commentedon Nov 21, 2014
@niceue
if ( typeof define === 'function' && define.amd || define.cmd )
=>
if ( typeof define === 'function' && ( define.amd || define.cmd ) )
3 remaining items