SeaJS实现模块化JavaScript开发
使用SeaJS实现模块化JavaScript开发
前言
SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。与jQuery等JavaScript框架不同,SeaJS不会扩展封装语言特性,而只是实现JavaScript的模块化及按模块加载。SeaJS的主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的JavaScript文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑。SeaJS可以与jQuery这类框架完美集成。使用SeaJS可以提高JavaScript代码的可读性和清晰度,解决目前JavaScript编程中普遍存在的依赖关系混乱和代码纠缠等问题,方便代码的编写和维护。
SeaJS的作者是淘宝前端工程师玉伯。
SeaJS本身遵循KISS(Keep It Simple, Stupid)理念进行开发,其本身仅有个位数的API,因此学习起来毫无压力。在学习SeaJS的过程中,处处能感受到KISS原则的精髓——仅做一件事,做好一件事。
本文首先通过一个例子直观对比传统JavaScript编程和使用SeaJS的模块化JavaScript编程,然后详细讨论SeaJS的使用方法,最后给出一些与SeaJS相关的资料。
前言
传统模式 vs SeaJS模块化
传统开发
SeaJS模块化开发
使用SeaJS
下载及安装
SeaJS基本开发原则
模块的定义及编写
模块的载入及引用
SeaJS的全局配置
SeaJS如何与现有JS库配合使用
SeaJS项目打包部署
一个完整的例子
主要参考文献&SeaJS学习资源
传统模式 vs SeaJS模块化
假设我们现在正在开发一个Web应用TinyApp,我们决定在TinyApp中使用jQuery框架。TinyApp的首页会用到module1.js,module1.js依赖module2.js和module3.js,同时module3.js依赖module4.js。
传统开发
使用传统的开发方法,各个js文件代码如下:
//module1.js
var module1 = {
run: function() {
return $.merge(['module1'], $.merge(module2.run(), module3.run()));
}
}
//module2.js
var module1 = {
run: function() {
return ['module2'];
}
}
//module3.js
var module3 = {
run: function() {
return $.merge(['module3'], module4.run());
}
}
//module4.js
var module4 = {
run: function() {
return ['module4'];
}
}
此时index.html需要引用module1.js及其所有下层依赖(注意顺序):
随着项目的进行,js文件会越来越多,依赖关系也会越来越复杂,使得js代码和html里的script列表往往变得难以维护。
SeaJS模块化开发
下面看看如何使用SeaJS实现相同的功能。
首先是index.html:
可以看到html页面不再需要引入所有依赖的js文件,而只是引入一个sea.js,sea.js会处理所有依赖,加载相应的js文件,加载策略可以选择在渲染页面时一次性加载所有js文件,也可以按需加载(用到时才加载响应js),具体加载策略使用方法下文讨论。
index.html加载了init模块,并使用此模块的initPage方法初始化页面数据,这里先不讨论代码细节。
下面看一下模块化后JavaScript的写法:
//jquery.js
define(function(require, exports, module) = {
//原jquery.js代码...
module.exports = $.noConflict(true);
});
//init.js
define(function(require, exports, module) = {
var $ = require('jquery');
var m1 = require('module1');
exports.initPage = function() {
$('.content').html(m1.run());
}
});
//module1.js
define(function(require, exports, module) = {
var $ = require('jquery');
var m2 = require('module2');
var m3 = require('module3');
exports.run = function() {
return $.merge(['module1'], $.merge(m2.run(), m3.run()));
}
});
//module2.js
define(function(require, exports, module) = {
exports.run = function() {
return ['module2'];
}
});
//module3.js
define(function(require, exports, module) = {
var $ = require('jquery');
var m4 = require('module4');
exports.run = function() {
return $.merge(['module3'], m4.run());
}
});
//module4.js
define(function(require, exports, module) = {
exports.run = function() {
return ['module4'];
}
});
乍看之下代码似乎变多变复杂了,这是因为这个例子太简单,如果是大型项目,SeaJS代码的优势就会显现出来。不过从这里我们还是能窥探到一些SeaJS的特性:
一是html页面不用再维护冗长的script标签列表,只要引入一个sea.js即可。
二是js代码以模块进行组织,各个模块通过require引入自己依赖的模块,代码清晰明了。
通过这个例子朋友们应该对SeaJS有了一个直观的印象,下面本文具体讨论SeaJS的使用。
使用SeaJS
下载及安装
要在项目中使用SeaJS,你所有需要做的准备工作就是下载sea.js然后放到你项目的某个位置。
SeaJS项目目前托管在GitHub上,主页为https://github.com/seajs/seajs/。可以到其git库的build目录下(https://github.com/seajs/seajs/tree/master/build)下载sea.js(已压缩)或sea-debug.js(未压缩)。
下载完成后放到项目的相应位置,然后在页面中通过
这种写法会令html更加简洁。
require
require是SeaJS主要的模块加载方法,当在一个模块中需要用到其它模块时一般用require加载:
var m = require('/path/to/module/file');
这里简要介绍一下SeaJS的自动加载机制。上文说过,使用SeaJS后html只要包含sea.js即可,那么其它js文件是如何加载进来的呢?SeaJS会首先下载入口模块,然后顺着入口模块使用正则表达式匹配代码中所有的require,再根据require中的文件路径标识下载相应的js文件,对下载来的js文件再迭代进行类似操作。整个过程类似图的遍历操作(因为可能存在交叉循环依赖所以整个依赖数据结构是一个图而不是树)。
明白了上面这一点,下面的规则就很好理解了:
传给require的路径标识必须是字符串字面量,不能是表达式,如下面使用require的方法是错误的:
require('module' + '1');
require('Module'.toLowerCase());
这都会造成SeaJS无法进行正确的正则匹配以下载相应的js文件。
require.async
上文说过SeaJS会在html页面打开时通过静态分析一次性记载所有需要的js文件,如果想要某个js文件在用到时才下载,可以使用require.async:
require.async('/path/to/module/file', function(m) {
//code of callback...
});
这样只有在用到这个模块时,对应的js文件才会被下载,也就实现了JavaScript代码的按需加载。
SeaJS的全局配置
SeaJS提供了一个seajs.config方法可以设置全局配置,接收一个表示全局配置的配置对象。具体使用方法如下:
seajs.config({
base: 'path/to/jslib/',
alias: {
'app': 'path/to/app/'
},
charset: 'utf-8',
timeout: 20000,
debug: false
});
其中base表示基址寻址时的基址路径。例如base设置为”http://example.com/js/3-party/”,则
var $ = require('jquery');
会载入”http://example.com/js/3-party/jquery.js”。
alias可以对较长的常用路径设置缩写。
charset表示下载js时script标签的charset属性。
timeout表示下载文件的最大时长,以毫秒为单位。
debug表示是否工作在调试模式下。
SeaJS如何与现有JS库配合使用
要将现有JS库如jQuery与SeaJS一起使用,只需根据SeaJS的的模块定义规则对现有库进行一个封装。例如,下面是对jQuery的封装方法:
define(function() {
//{{{jQuery原有代码开始
/*!
* jQuery JavaScript Library v1.6.1
* http://jquery.com/
*
* Copyright 2011, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2011, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Thu May 12 15:04:36 2011 -0400
*/
//...
//}}}jQuery原有代码结束
return $.noConflict();
});
SeaJS项目的打包部署
SeaJS本来集成了一个打包部署工具spm,后来作者为了更KISS一点,将spm拆出了SeaJS而成为了一个单独的项目。spm的核心思想是将所有模块的代码都合并压缩后并入入口模块,由于SeaJS本身的特性,html不需要做任何改动就可以很方便的在开发环境和生产环境间切换。但是由于spm目前并没有发布正式版本,所以本文不打算详细介绍,有兴趣的朋友可以参看其github项目主页https://github.com/seajs/spm/。
其实,由于每个项目所用的JS合并和压缩工具不尽相同,所以spm可能并不是完全适合每个项目。在了解了SeaJS原理后,完全可以自己写一个符合自己项目特征的合并打包脚本。
一个完整的例子
上文说了那么多,知识点比较分散,所以最后我打算用一个完整的SeaJS例子把这些知识点串起来,方便朋友们归纳回顾。这个例子包含如下文件:
index.html——主页面。
sea.js——SeaJS脚本。
init.js——init模块,入口模块,依赖data、jquery、style三个模块。由主页面载入。
data.js——data模块,纯json数据模块,由init载入。
jquery.js——jquery模块,对 jQuery库的模块化封装,由init载入。
style.css——CSS样式表,作为style模块由init载入。
sea.js和jquery.js的代码属于库代码,就不赘述,这里只给出自己编写的文件的代码。
html:
javascript:
//init.js
define(function(require, exports, module) {
var $ = require('./jquery');
var data = require('./data');
var css = require('./style.css');
$('.author').html(data.author);
$('.blog').attr('href', data.blog);
});
//data.js
define({
author: 'ZhangYang',
blog: 'http://leoo2sk.cnblogs.com'
});
css:
.author{color:red;font-size:10pt;}
.blog{font-size:10pt;}
主要参考文献&SeaJS学习资源
[1] SeaJS主页 – http://seajs.com
[2] SeaJS的GitHub库(可获取源码) – https://github.com/seajs/seajs
[3] SeaJS作者玉伯的博客 - http://lifesinger.wordpress.com/