SeaJS 文摘

为何会有文摘?

我使用 SeaJS 的缘起比较复杂,要从去年3月份开始的项目说起。那个项目的架构师是 @lenel 。 项目的中心思想是前端代码复用,快速开发投到各处的广告牌,同时开放给用户定制广告牌的内容。

后者的解决办法是给广告牌统一数据接口,所有的广告牌模板使用统一的数据格式,不赘述。 前者,JavaScript 代码复用的解决办法,正是使用模块加载器。只不过,这个模块加载器叫做 kslite,是 @lenel 童鞋的作品。

在 kslite 里,模块的包装是这样的:

// cc/modules/mod1.js
KSLITE.declar(['cc/modules/dep1', 'cc/modules/dep2'], function(require, exports, module) {
    var dep1 = require('cc/modules/dep1');
    var dep2 = require('cc/modules/dep2');

    // expose methods
    exports.exe = function() {};

    // or this way
    module.exports = {
        app: function() {}
    };
});

SeaJS 对它的改进在于,dependencies 数组不再是必须的,可以使用相对路径 require 模块。 除了这两个好处,SeaJS 对低版本 IE 的支持更好,也形成了社区。因此这个项目从 kslite 转到了 SeaJS。

但之前使用 kslite 时的一些经验,异步模块加载、代码打包部署等,则沿袭了下来。这些经验,都汇集在此,成为文摘, 它们可能跟 http://seajs.org 上的方式有所不同,甚至迥异,但它们也都是可行的。

SeaJS 模块预处理

如何正确压缩、打包你的模块们

SPM(SeaJS Package Manager)正是为此而生的,但是本篇暂时不讨论这个,我只讲我所采用的办法。 需要少许 Ruby 以及命令行知识。

项目终于要发布上线,我们当然先得压缩一下代码,完了之后检查一下我们碉堡了的项目…… 等等,怎么报错了,莫非是我打开的方式不对?模块的依赖怎么都没加载?

发现模块代码竟然变成了:

define(function(a,b){ ... })

require “关键字” 变成了 a,SeaJS 不能根据 require 进行正则匹配来找到此模块的依赖了。

先温习一下 define,它其实可以接收三个参数,写全了的调用应该是这样的:

define(id,               // the module id
       ['dep1', 'dep2'], // the module dependencies
       function(require, exports) {
    ...
})

因此,解决办法很简单,有两种:

  • 告诉压缩工具 require 是关键字,不要替换
  • 利用工具把模块依赖给解析出来,并在 define 中写上

方法一我并没有尝试,主要的原因是,它并没有解决实际的问题,SeaJS 仍然需要从你的模块代码中解析依赖。 它采用的办法前文有述,正则表达式匹配出来的,势必带来一些线上的性能损耗(事实上不管用什么办法都会有损耗)。

而方法二则相当于用工具帮助你把模块依赖给写了出来:

define(['dep1', 'dep2'], function(require, exports) {
    ...
})

如此处理后,不管你用什么工具,YUI Compressor 或者 Google Compiler 或者 UglifyJS,都不会有问题了。 但是,我该怎么合并模块呢?写上 id 就好。

总结一下,开发阶段:

  1. 模块对应文件,因此不需要写模块 ID;
  2. 依赖由 SeaJS 自动解析,因此省却了手工写明依赖数组。

但上线之后:

  1. 文件会合并,因此需要提供 ID;
  2. 自动解析性能差,而且 require 还会被压缩,因此需要写明依赖数组。

所以,任务已经说明,我们需要预处理代码,把这俩更新到模块文件中去。

首先,我们需要安装工具,它是一个 Ruby Gem,叫做 rill。安装办法很简单:

$ gem install rill

目前的版本是 0.0.1,比较原始,但它的功能代码,已经在我的项目中跑了1年多,所以质量还是有保证的。 源代码在 dotnil/rill

装好之后,假设你的伟大项目在 /awsome/ 目录,代码结构如下:

.
├── app.js
├── lib
│   └── jquery.js
└── modules
    ├── deps
    │   ├── dep1.js
    │   └── dep2.js
    ├── mod1.js
    └── mod2.js

只要这么做就好:

$ cd /awesome/
$ rill --base . --files "modules/*.js"

它会把 /awesome/modules 目录下的模块文件都搞成这种样子:

define('mod1', ['./dep1', './dep2'], function(require, exports) {
    ...
})

变成这样之后,压缩与合并就不再有问题了。