seajs历史
作者:zccst
一、先来点最重要的
后端:CommonJS(原来叫ServerJS)
前端:RequireJS(AMD),Seajs(CMD)
CommonJS:JavaScript并没有内置模块系统,CommonJS致力于提高JavaScript程序的可移植性和可交换性,无论是在服务端还是浏览器端。
缺点:由于require是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口。然而在浏览器端困难多多。因为script天生异步,传统CommonJS模块在浏览器环境无法正常加载。
解决思路一:开发服务器端组件,对模块代码静态分析,将模块与它的依赖一起返回给浏览器端。
解决思路二:用一套标准模板来封装模块定义,这套模板代码为模块加载器提供了机会,使其能在模块代码之前,对模块代码进行静态分析,并动态生成依赖列表。(为了让静态分析可行,需要遵守一些简单的规则,即CMD规范)
Sea.js的初衷是为了让CommonJSModules/1.1的模块能运行在浏览器端,但由于浏览器和服务器的实质差异,实际上这个梦无法完全达成,也没有必要去达成。
更好的一种方式是,Sea.js专注于Web浏览器端,CommonJS则专注于服务器端,但两者有共通的部分。对于需要在两端都可以跑的模块,可以有便捷的方案来快速迁移。
目前Sea.js的模块,如果没有用到浏览器环境下的特有属性,可以很方便跑在NodeJS端。只要在入口文件处,引入Sea.js的Node.js版本即可:
//让Node环境可以加载执行CMD模块
require('seajs');
vara=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(传输Thereareavarietyofwaystotransportamodulefromaservertoabrowser.)规范,即在浏览器上运行前,先通过转换工具将模块转换为符合Transport规范的代码。主流代表是服务端的开发人员。现在值得关注的有两个实现:越来越火的component和走在前沿的es6moduletranspiler。
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上
首先$npminstallseajs-g
然后,在需要调用seajs模块的入口文件里,require下seajs
a.js
define(function(require,module,exports){
exports.name="A";
});
main.js
require("seajs");
vara=require("./a");
console.log(a.name);
最后,运行$nodemain.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里,模块文件里的returnxx会被忽略;Sea.js里,returnxx等价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更贴近CommonJSModules/1.1和NodeModules规范。
推广理念有差异。RequireJS在尝试让第三方类库修改自身来支持RequireJS,目前只有少数社区采纳。Sea.js不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
对开发调试的支持有差异。Sea.js非常关注代码的开发调试,有nocache、debug等用于调试的插件。RequireJS无这方面的明显支持。
插件机制不同。RequireJS采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js采取的是通用事件机制,插件类型更丰富。
还有不少差异,涉及具体使用方式和源码实现,欢迎有兴趣者研究并发表看法。
总之,如果说RequireJS是Prototype类库的话,则Sea.js致力于成为jQuery类库。
如果您觉得本文的内容对您的学习有所帮助,您可以微信: