作者:zccst
一、先来点最重要的
后端:CommonJS(原来叫ServerJS)
前端:RequireJS(AMD),Seajs(CMD)
CommonJS:JavaScript 并没有内置模块系统,CommonJS 致力于提高 JavaScript 程序的可移植性和可交换性,无论是在服务端还是浏览器端。
缺点:由于require是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口。然而在浏览器端困难多多。因为script天生异步,传统CommonJS 模块在浏览器环境无法正常加载。
解决思路一:开发服务器端组件,对模块代码静态分析,将模块与它的依赖一起返回给浏览器端。
解决思路二:用一套标准模板来封装模块定义,这套模板代码为模块加载器提供了机会,使其能在模块代码之前,对模块代码进行静态分析,并动态生成依赖列表。(为了让静态分析可行,需要遵守一些简单的规则,即CMD规范)
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 模块可移植,可在不同平台上运行。
二、seajs的历史
https://github.com/seajs/seajs/issues/588
大概 09 年 - 10 年期间,CommonJS 社区大牛云集。CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践。
09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端,于是将社区改名叫 CommonJS,同时激烈争论 Modules 的下一版规范。分歧和冲突由此诞生,逐步形成了三大流派:
Modules/1.x 流派。这个观点觉得 1.x 规范已经够用,只要移植到浏览器端就好。要做的是新增 Modules/Transport (传输 There are a variety of ways to transport a module from a server to a browser.) 规范,即在浏览器上运行前,先通过转换工具将模块转换为符合 Transport 规范的代码。主流代表是服务端的开发人员。现在值得关注的有两个实现:越来越火的 component 和走在前沿的 es6 module transpiler。
Modules/Async 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范。这个观点下的典型代表是 AMD 规范及其实现 RequireJS。这个稍后再细说。
Modules/2.0 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范,但应该尽可能与 Modules/1.x 规范保持一致。这个观点下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者对 CommonJS 的社区的贡献很大,这份 Modules/2.0-draft 规范花了很多心思。FlyScript 的作者提出了 Modules/Wrappings 规范,这规范是 CMD 规范的前身。可惜的是 BravoJS 太学院派,FlyScript 后来做了自我阉割,将整个网站(flyscript.org)下线了。这个故事有点悲壮,下文细说。
三、seajs与nodejs
原文:
https://github.com/seajs/seajs/issues/275
1,让 Sea.js 的模块跑在 Node 上
首先 $npm install seajs -g
然后,在需要调用seajs模块的入口文件里,require下seajs
a.js
define(function(require, module, exports){
exports.name = "A";
});
main.js
require("seajs");
var a = require("./a");
console.log(a.name);
最后,运行 $node main.js
#打印 A
2,让 node 模块跑在浏览器端
这个也很简单,将 Node 的模块封装成 CMD 模块即可:
a.js
exports.name = 'A';
封装成
define(function(require, exports) {
exports.name = 'A';
});
这样在浏览器端就可以通过 Sea.js 来加载使用了:
seajs.use('./a', function(a) {
console.log(a.name);
});
3,通用的前提条件
通过上面的例子可以看出,只要遵循 CMD 规范来书写模块代码,就可以非常方便的同时运行在浏览器端和服务器端。这是 CMD 中 C 字母的含义:Common(通用)。
这比 RequireJS 的 AMD 规范好很多。
但是,无论是 CMD 还是 AMD 规范,或者是未来的某个 Modules/Cool 规范,一个模块要想同时在不同环境下执行,就得遵循以下前提条件:
不能调用浏览器端的私有特性。比如 attachEvent 之类的,除非你在 Node 端提前模拟好。
不能调用服务器端的私有特性。比如 process 对象,除非你在浏览器端自己实现一个类似的 process 对象。
只用不同规范中的交集。比如 CMD 中有 module.uri 属性,但 Node 中没有,要通用,就不能去调用这些有差异的接口。类似的,Node 端的 __filename 在浏览器端不存在,要通用的话,也不能调用。
其实上面这些严格要求,是 非常自然的。这就如浏览器兼容一样,要写出所有浏览器下都能跑的代码,最好的方式是只用那些所有浏览器都支持的特性,不然你就得用 if ... else ... 去搞啦。
4,附录 Node.js 与 Sea.js 在模块接口上的主要差异如下:
Node.js 里,模块文件里的 this === module.exports;Sea.js 里,this === window。
Node.js 里,模块文件里的 return xx 会被忽略;Sea.js 里,return xx 等价 module.exports = xx。
Node.js 里,require 是懒加载 + 懒执行的。在 Sea.js 里是提前加载好 + 懒执行。
Sea.js 里,require(id) 中的 id 必须是字符串直接量。Node.js 里没这个限制。
四、seajs与requireJS
相同:
RequireJS 和 Sea.js 都是模块加载器,倡导模块化开发理念,核心价值是让 JavaScript 的模块化开发变得简单自然。
不同之处 两者的主要区别如下:
定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。
插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。
还有不少差异,涉及具体使用方式和源码实现,欢迎有兴趣者研究并发表看法。
总之,如果说 RequireJS 是 Prototype 类库的话,则 Sea.js 致力于成为 jQuery 类库。
如果您觉得本文的内容对您的学习有所帮助,您可以微信: