Seajs

[摘要]前端模块化开发的价值

link

恼人的命名冲突

看不懂,也没必要深究历史,略过

烦琐的文件依赖

使用 Sea.js 来解决

使用 Sea.js,在书写文件时,需要遵守 CMD (Common Module Definition)模 块定义规范。

一个文件就是一个模块。

前面例子中的 util.js

1
2
3
4
5
6
7
8
9
10
11
var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};

org.CoolSite.Utils.each = function (arr) {
  // 实现代码
};

org.CoolSite.Utils.log = function (str) {
  // 实现代码
};

变成:

1
2
3
4
5
6
7
8
9
define(function(require, exports) {
  exports.each = function (arr) {
    // 实现代码
  };

  exports.log = function (str) {
    // 实现代码
  };
});

通过 exports 就可以向外提供接口。这样,dialog.js 的代码变成:

1
2
3
4
5
6
7
define(function(require, exports) {
  var util = require('./util.js');

  exports.init = function() {
    // 实现代码
  };
});

所以: define API

关键部分到了!我们通过 require('./util.js') 就可以拿到 util.js 中通过 exports 暴露的接口。

这里的 require 可以认为是 Sea.js 给 JavaScript 语言增加的一个 语法关键字,通过 require 可以获取其他模块提供的接口。

类似于 css 中的:

1
2
3
4
@import url("base.css");

#id { ... }
.class { ... }

Sea.js 增加的 require 语法关键字,就如 CSS 文件中的 @import 一样,给我们的源码赋予了依赖引入功能。

如果你是后端开发工程师,更不会陌生。Java、Python、C# 等等语言,都有 include、import 等功能。JavaScript 语言本身也有类似功能,但目前还处于草案阶段,需要等到 ES6 标准得到主流浏览器支持后才能使用。

这样,在页面中使用 dialog.js 将变得非常简单。

1
2
3
4
5
6
<script src="sea.js"></script>
<script>
seajs.use('dialog', function(Dialog) {
  Dialog.init(/* 传入配置 */);
});
</script>

首先要在页面中引入 sea.js 文件,这一般通过页头全局把控,也方便更新维护。

想在页面中使用某个组件时,只要通过 seajs.use 方法调用。

所以: seajs.use API

好好琢磨以上代码,我相信你已经看到了 Sea.js 带来的两大好处:

  1. 通过 exports 暴露接口。这意味着不需要命名空间了,更不需要全局变量。这是一种彻底的命名冲突解决方案。

  2. 通过 require 引入依赖。这可以让依赖内置,开发者只需关心当前模块的依赖,其他事情 Sea.js 都会自动处理好。对模块开发者来说,这是一种很好的 关注度分离,能让程序员更多地享受编码的乐趣。

[摘要] 5 分钟上手 Sea.js

目录结构

1
2
3
4
5
6
7
8
9
10
examples/
  |-- sea-modules      存放 seajs、jquery 等文件,这也是模块的部署目录
  |-- static           存放各个项目的 js、css 文件
  |     |-- hello
  |     |-- lucky
  |     `-- todo
  `-- app              存放 html 等文件
        |-- hello.html
        |-- lucky.html
        `-- todo.html

我们从 hello.html 入手,来瞧瞧使用 Sea.js 如何组织代码。

在页面中加载模块

1
2
3
4
5
6
7
8
9
10
// seajs 的简单配置
seajs.config({
  base: "../sea-modules/",
  alias: {
    "jquery": "jquery/jquery/1.10.1/jquery.js"
  }
})

// 加载入口模块
seajs.use("../static/hello/src/main")

seajs.config API seajs.use API

模块代码

这个小游戏有两个模块 spinning.js 和 main.js,遵循统一的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 所有模块都通过 define 来定义
define(function(require, exports, module) {

  // 通过 require 引入依赖
  var $ = require('jquery');
  var Spinning = require('./spinning');

  // 通过 exports 对外提供接口
  exports.doSomething = ...

  // 或者通过 module.exports 提供整个接口
  module.exports = ...

});

[摘要] API 快速参考

seajs.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
seajs.config({

  // 设置路径,方便跨目录调用
  paths: {
    'arale': 'https://a.alipayobjects.com/arale',
    'jquery': 'https://a.alipayobjects.com/jquery'
  },

  // 设置别名,方便调用
  alias: {
    'class': 'arale/class/1.0.0/class',
    'jquery': 'jquery/jquery/1.10.1/jquery'
  }

});

更多配置项请参考:#262

seajs.use

1
2
3
4
5
6
7
8
9
10
11
12
13
// 加载一个模块
seajs.use('./a');

// 加载一个模块,在加载完成时,执行回调
seajs.use('./a', function(a) {
  a.doSomething();
});

// 加载多个模块,在加载完成时,执行回调
seajs.use(['./a', './b'], function(a, b) {
  a.doSomething();
  b.doSomething();
});

更多配置项请参考:#260

define

用来定义模块。Sea.js 推崇一个模块一个文件,遵循统一的写法:

1
2
3
4
5
define(function(require, exports, module) {

  // 模块代码

});

也可以手动指定模块 id 和依赖,详情请参考:#242 require, exports 和 module 三个参数可酌情省略,具体用法如下。

require

require 用来获取指定模块的接口。

1
2
3
4
5
6
7
8
define(function(require) {

  // 获取模块 a 的接口
  var a = require('./a');

  // 调用模块 a 的方法
  a.doSomething();
});

注意,require 只接受字符串直接量作为参数,详细约定请阅读:#259

require.async

用来在模块内部异步加载一个或多个模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
define(function(require) {

  // 异步加载一个模块,在加载完成时,执行回调
  require.async('./b', function(b) {
    b.doSomething();
  });

  // 异步加载多个模块,在加载完成时,执行回调
  require.async(['./c', './d'], function(c, d) {
    c.doSomething();
    d.doSomething();
  });

});

详细说明请参考:#242

exports

用来在模块内部对外提供接口。

1
2
3
4
5
6
7
8
9
define(function(require, exports) {

  // 对外提供 foo 属性
  exports.foo = 'bar';

  // 对外提供 doSomething 方法
  exports.doSomething = function() {};

});

详细说明请参考:#242