从CommonJS到Sea.js

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 没有利用到服务器特有属性和模块,比如 __dirnameprocess 等。

通过上面的方案,我们就实现了 CommonJS 与 Sea.js 两个生态圈的融合,可以彼此互通,让我们书写的 JavaScript 模块可移植,可在不同平台上运行。

posted on 2016-08-15 20:00  dataman  阅读(159)  评论(0编辑  收藏  举报

导航