一步步學會使用SeaJS 2.0
本文分爲如下8步,熟悉以後就可以熟練使用SeaJS,今後以後你的生活會變得更加輕鬆愉悅!
javascript
一、SeaJS是什麼?css
二、下載並檢閱SeaJShtml
三、創建工程和各類目錄前端
四、引入SeaJS庫java
五、編寫本身的代碼node
六、引入本身的代碼jquery
七、壓縮合並git
八、總結展望程序員
--------------------------------------------------github
一、SeaJS是什麼?
你必定聽過前端模塊化開發吧?神馬,你沒聽過?我只能說你out了……
你應該知道Java的import吧?神馬,你又不知道?那你應該知道CSS中的import吧……
在這裏我不想展開說前端模塊化的含義和價值,由於這裏有一篇好文(https://github.com/seajs/seajs/issues/547),詳細說明了前端模塊化。
我知道你看到那麼長的文章確定會望而卻步,也許你是但願可以快速開始敲代碼(程序員的通病……)。不要緊,若是實在讀不下去,只要記住模塊化要解決的問題便可:命名衝突、文件依賴關係。
這兩個鬧心的問題應該遇到過吧,若是沒遇到過……我只能說你太牛X了
好了,前端模塊化就扯到這裏,寫過前端的人應該基本都知道JavaScript自身是不支持模塊化開發的,因此就有了SeaJS這款神器,爲前端從業者提供了一個強大易用的模塊化開發工具。
二、下載並檢閱SeaJS
SeaJS如今已是2.0版本啦,到這裏下載:https://github.com/seajs/seajs
解壓後會看到下列目錄:
其中:
dist —— 壓縮好的、用於瀏覽器端的SeaJS代碼
docs —— 文檔
src —— 源代碼
package.json + Gruntfile.js —— Grunt構建工具所須要的文件,這個在第七步壓縮合並會介紹到
其餘目錄或文件能夠暫且無論
三、創建工程和各類目錄
準備工做已經完成,咱們終於能夠開始進行開發啦!來,跟我走:
a. 創建工程
用你最喜歡的IDE創建工程,名字爲HelloSeaJS
b. 準備各類目錄
在這裏把JavaScript、Image、CSS都放在統一的資源文件(assets)中,建好以後的目錄以下:
(我使用了Sublime2.0,在這裏強烈推薦)
c. 把剛剛下好的seajs/dist中的文件都放在scripts/seajs目錄下
注意:SeaJS會根據自身的URI來決定URL base,而SeaJS在加載其餘模塊的時候會根據這個URL base來計算路徑。SeaJS會忽略掉seajs、seajs/2.0.0/seajs這兩種目錄,照上述的目錄結構,此處的URL base就是HelloSeaJS/assets/scripts,這樣其餘模塊就能夠與seajs目錄並行存放。
至此,工程和文件都已準備完成。
四、引入SeaJS庫
與引入其餘js庫並沒有太大區別:
<script src="assets/scripts/seajs/sea.js" id="seajsnode"></script>
你可能注意到,這裏加上了id="seajsnode",緣由以下:
a. SeaJS加載自身的script標籤的其餘屬性(如data-config、data-main)等來實現不一樣的功能
b. SeaJS內部經過document.getElementById("seajsnode")來獲取這個script標籤(其實SeaJS內部還有一種方式,不過另外一種方式的效率比較低,因此不推薦,若是有興趣,能夠看一下源碼https://github.com/seajs/seajs/blob/master/src/util-path.js)
五、編寫本身的代碼
這裏做爲示範,只作了一個很是簡單的效果,點擊查看:http://liuda101.github.io/HelloSeaJS/
在編寫本身代碼的時候,要時刻記住」模塊化「,而操做起來也很是簡單,由於在SeaJS中一個文件就是一個模塊。
下面是代碼邏輯的模塊application.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
define(
function
(require,exports,module){
var
util = {};
var
colorRange = [
'0'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'A'
,
'B'
,
'C'
,
'D'
,
'E'
,
'F'
];
util.randomColor =
function
(){
return
'#'
+
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)];
};
var
helloSeaJS = document.getElementById(
'hello-seajs'
);
helloSeaJS.style.color = util.randomColor();
window.setInterval(
function
(){
helloSeaJS.style.color = util.randomColor();
},1500);
});
|
咱們看到,全部代碼都放在define(function(require,exports,module){});函數體裏面。
define是SeaJS定義的一個全局函數,用來定義一個模塊。
至於require,exports,module都是什麼,能夠暫且無論,到此,咱們的代碼已經完成,很簡單吧。嗯,花個幾十秒鐘,看一下代碼。
……
看完以後,你會說,這算什麼啊!這就完了麼?
不要怪我,爲了簡單易懂,咱們就按照」一步步「的節奏慢慢來。
隨着代碼的增多,你確定會遇到util愈來愈多的狀況。很好,這樣看來,咱們就有了兩個模塊:util模塊和application模塊。SeaJS中,文件即模塊,因此固然要將其分爲兩個文件。先看util.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
define(
function
(require,exports,module){
var
util = {};
var
colorRange = [
'0'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'A'
,
'B'
,
'C'
,
'D'
,
'E'
,
'F'
];
util.randomColor =
function
(){
return
'#'
+
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)] +
colorRange[Math.floor(Math.random() * 16)];
};
module.exports = util;
});
|
除了define以外,咱們看到module.exports = util;這一句比較特殊。這句是在說,我util模塊向外暴露的接口就這些,其餘全部的東西都是我內部用的,爾等就無需擔憂了,我會照顧好的。
再看application.js:
1
2
3
4
5
6
7
8
9
10
|
define(
function
(require,exports,module){
var
util = require(
'./util'
);
var
helloSeaJS = document.getElementById(
'hello-seajs'
);
helloSeaJS.style.color = util.randomColor();
window.setInterval(
function
(){
helloSeaJS.style.color = util.randomColor();
},1500);
});
|
咱們看到var util = require('./util');這句比較特殊。這句就是在說,我application模塊因爲業務須要,想請util模塊來幫忙,因此把util給require進來。
至此,咱們經歷了一個模塊到兩個模塊的轉變,在往後漫長的日子中,咱們的模塊也許會愈來愈多,不過不用擔憂,有了SeaJS提供的define、require、module.exports,咱們均可以方便的應對。
六、引入本身的代碼
你看到這個小標題,你可能會極力的鄙視我,這等工做還須要你來示範?因而,你啪啪啪啪,在引入SeaJS的script標籤後引入了util.js和application.js:
<script src="assets/scripts/application/util.js"></script>
<script src="assets/scripts/application/application.js"></script>
而後你不停的F5……
你看不到效果吧?這就是這個小節存在的理由。
SeaJS提供了模塊化的能力,前面咱們已經看到了SeaJS定義模塊、引用模塊的方法,而這裏就要用到SeaJS加載並啓動模塊的兩種方式:
a、使用data-main
爲<script src="assets/scripts/seajs/sea.js" id="seajsnode"></script>添加data-main="application/application"屬性便可:
<script src="assets/scripts/seajs/sea.js" id="seajsnode" data-main="application/application"></script>
SeaJS會根據data-main指定的模塊來做爲整個應用的入口模塊。SeaJS找到這個模塊以後,就會加載執行這個模塊對應的文件。
那麼,SeaJS又是怎麼找到這個文件呢?也就是說,這個模塊對應的加載路徑是多少?
「算法」是:SeaJS_URL_base + data-main
如上文,該例子的SeaJS_URL_base是HelloSeaJS/assets/scripts/
那麼,加載路徑就是HelloSeaJS/assets/scripts/application/application.js(SeaJS會自動加上.js後綴)
b、使用seajs.use
在<script src="assets/scripts/seajs/sea.js" id="seajsnode">後面加上:
<script> seajs.use("application/application"); </script>
其實這兩種效果在這個例子中是同樣的,data-main一般用在只有一個入口的狀況,use能夠用在多個入口的狀況,具體用法,看這裏:https://github.com/seajs/seajs/issues/260
若是你對你的程序有徹底的控制權,建議使用data-main的方式,這樣整個頁面就只有一段script標籤!做爲一名前端開發人員,我不得不驚歎:乾淨、完美!
不管使用哪一種方式,跟着我一塊兒F5一下!
在打開Chrome的debug工具,查看Network這個tab:
咱們看到,SeaJS已經幫咱們加載好了application.js和util.js,舒服吧~
嗯,我第一次試用SeaJS的時候,到這裏也感到了無比的舒心
七、壓縮合並
正當我伸伸懶腰,打算上個廁所的時候,忽然想到一件事情:若是模塊愈來愈多,那麼多文件都要分開加載?那豈不嚴重影響性能!?(啥,你不知道爲啥?)
要壓縮合並JavaScript呀!因而,我強忍住那股液體,開始用YUICompressor來壓縮,並手動合併了兩個文件。
這裏就不展現結果了,由於很蛋疼,徹底對不住我剛纔忍住液體的勇氣!結果固然是,失敗。
爲何會失敗呢?本身想了想,同時打開壓縮後的代碼一看,才發現緣由:
壓縮後以後,require變量變成了a變量。SeaJS是經過require字面來判斷模塊之間的依賴關係的,因此,require變量不能被簡化。
嗯,SeaJS已經替咱們想到了這個問題,因而咱們就採用SeaJS提供的方式來合併壓縮吧(固然你也能夠本身用別的方式壓縮)。
SeaJS在2.0以前,是採用SPM做爲壓縮合並工具的,到了2.0,改成Grunt.js,SPM變爲包管理工具,相似NPM(不知道NPM?Google一下吧)
自動化不只是科技帶給社會的便利,也是Grunt帶給前端的瑞士軍刀。使用Grunt,能夠很方便的定製各類任務,如壓縮、合併等。使用Grunt以前,須要安裝node環境和grunt工具,Google一下,十分鐘後回來。
……
Grunt最核心的就兩個部分,package.json、Gruntfile.js。
a. package.json
Grunt把一個項目/目錄視爲一個npm模塊,package.json就是用來描述這個模塊的信息,包括name、version、author等等。
這裏強調一下,Grunt既然將該目錄視爲一個模塊,那麼該模塊固然能夠依賴其餘模塊。
咱們看本示例的:
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"name" : "HelloSeaJS",
"version" : "1.0.0",
"author" : "Qifeng Liu",
"devDependencies" : {
"grunt" : "0.4.1",
"grunt-cmd-transport" : "0.1.1",
"grunt-cmd-concat" : "0.1.0",
"grunt-contrib-uglify" : "0.2.0",
"grunt-contrib-clean" : "0.4.0"
}
}
|
devDependencies就是用來描述自身所依賴的模塊
其中:
grunt模塊用來跑Gruntfile.js中定義的任務
grunt-cmd-transport模塊用來對SeaJS定義的模塊進行依賴提取等任務
grunt-cmd-concat模塊用來對文件進行合併
grunt-contrib-uglify模塊用來壓縮JavaScript
grunt-contrib-clean模塊用來清除臨時目錄
b. Gruntfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
module.exports =
function
(grunt){
grunt.initConfig({
transport : {
options : {
format :
'application/dist/{{filename}}'
//生成的id的格式
},
application : {
files : {
'.build'
: [
'application.js'
,
'util.js'
]
//將application.js、util.js合而且提取依賴,生成id,以後放在.build目錄下
}
}
},
concat : {
main : {
options : {
relative :
true
},
files : {
'dist/application.js'
: [
'.build/application.js'
],
// 合併.build/application.js文件到dist/application.js中
'dist/application-debug.js'
: [
'.build/application-debug.js'
]
}
}
},
uglify : {
main : {
files : {
'dist/application.js'
: [
'dist/application.js'
]
//對dist/application.js進行壓縮,以後存入dist/application.js文件
}
}
},
clean : {
build : [
'.build'
]
//清除.build文件
}
});
grunt.loadNpmTasks(
'grunt-cmd-transport'
);
grunt.loadNpmTasks(
'grunt-cmd-concat'
);
grunt.loadNpmTasks(
'grunt-contrib-uglify'
);
grunt.loadNpmTasks(
'grunt-contrib-clean'
);
grunt.registerTask(
'build'
,[
'transport'
,
'concat'
,
'uglify'
,
'clean'
])
};
|
定義好兩個文件以後,就能夠進入到application目錄下,首先運行:
npm install
該命令會下載好package.json中依賴的模塊
而後運行
grunt build
該命令會運行grunt.registerTask方法中指定的任務
不出差錯的話,會在application目錄下生成一個dist目錄,裏面包含了合併但沒壓縮的application-debug.js和合而且壓縮好的application.js。
修改index.html的
<script src="assets/scripts/seajs/sea.js" id="seajsnode" data-main="application/application"></script>
爲
<script src="assets/scripts/seajs/sea.js" id="seajsnode" data-main="application/dist/application"></script>
大功告成!
八、總結展望
SeaJS秉着簡單實用的原則,API設計職責單一,使用起來駕輕就熟。
SeaJS爲前端提供了模塊化能力,能夠簡單優雅的解決命名衝突、依賴關係等常見且棘手的問題。經過使用SeaJS,個人生活質量一下次上升好幾個檔次。
固然,除了本文介紹的,SeaJS還有一些其餘功能,相信只要你入了門,確定可以迅速掌握,這裏給個連接http://seajs.org/docs/#docs
其實,許多語言自身已經有了模塊化的功能,如Java的import,C++的#include等,可是因爲各類緣由,JavaScript自身並無這個功能。雖然藉助於SeaJS能夠很方便的進行模塊化開發了,但總以爲這個能力應該是語言自身的。好在,下個版本的JavaScript貌似在計劃引入模塊化的概念,讓咱們拭目以待吧!
最後,感謝SeaJS做者玉伯。
PS,本文參考了SeaJS提供的使用範例https://github.com/seajs/examples/tree/master/static/hello
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相關的資料。
假設咱們如今正在開發一個Web應用TinyApp,咱們決定在TinyApp中使用jQuery框架。TinyApp的首頁會用到module1.js,module1.js依賴module2.js和module3.js,同時module3.js依賴module4.js。
使用傳統的開發方法,各個js文件代碼以下:
//module1.jsvar module1 = { run: function() { return $.merge(['module1'], $.merge(module2.run(), module3.run())); }} //module2.jsvar module2 = { run: function() { return ['module2']; }} //module3.jsvar module3 = { run: function() { return $.merge(['module3'], module4.run()); }} //module4.jsvar module4 = { run: function() { return ['module4']; }}
此時index.html須要引用module1.js及其全部下層依賴(注意順序):
<!DOCTYPE HTML><html lang="zh-CN"><head> <meta charset="UTF-8"> <title>TinyApp</title> <script src="./jquery-min.js"></script> <script src="./module4.js"></script> <script src="./module2.js"></script> <script src="./module3.js"></script> <script src="./module1.js"></script></head><body> <p class="content"></p> <script> $('.content').html(module1.run()); </script></body></html>
隨着項目的進行,js文件會愈來愈多,依賴關係也會愈來愈複雜,使得js代碼和html裏的script列表每每變得難以維護。
下面看看如何使用SeaJS實現相同的功能。
首先是index.html:
<!DOCTYPE HTML><html lang="zh-CN"><head> <meta charset="UTF-8"> <title>TinyApp</title></head><body> <p class="content"></p> <script src="./sea.js"></script> <script> seajs.use('./init', function(init) { init.initPage(); }); </script></body></html>
能夠看到html頁面再也不須要引入全部依賴的js文件,而只是引入一個sea.js,sea.js會處理全部依賴,加載相應的js文件,加載策略能夠選擇在渲染頁面時一次性加載全部js文件,也能夠按需加載(用到時才加載響應js),具體加載策略使用方法下文討論。
index.html加載了init模塊,並使用此模塊的initPage方法初始化頁面數據,這裏先不討論代碼細節。
下面看一下模塊化後JavaScript的寫法:
//jquery.jsdefine(function(require, exports, module) = { //原jquery.js代碼... module.exports = $.noConflict(true);}); //init.jsdefine(function(require, exports, module) = { var $ = require('jquery'); var m1 = require('module1'); exports.initPage = function() { $('.content').html(m1.run()); }}); //module1.jsdefine(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.jsdefine(function(require, exports, module) = { exports.run = function() { return ['module2']; }}); //module3.jsdefine(function(require, exports, module) = { var $ = require('jquery'); var m4 = require('module4'); exports.run = function() { return $.merge(['module3'], m4.run()); }}); //module4.jsdefine(function(require, exports, module) = { exports.run = function() { return ['module4']; }});
乍看之下代碼彷佛變多變複雜了,這是由於這個例子太簡單,若是是大型項目,SeaJS代碼的優點就會顯現出來。不過從這裏咱們仍是能窺探到一些SeaJS的特性:
一是html頁面不用再維護冗長的script標籤列表,只要引入一個sea.js便可。
二是js代碼以模塊進行組織,各個模塊經過require引入本身依賴的模塊,代碼清晰明瞭。
經過這個例子朋友們應該對SeaJS有了一個直觀的印象,下面本文具體討論SeaJS的使用。
要在項目中使用SeaJS,你全部須要作的準備工做就是下載sea.js而後放到你項目的某個位置。
SeaJS項目目前託管在GitHub上,主頁爲 https://github.com/seajs/seajs/ 。能夠到其git庫的build目錄下下載sea.js(已壓縮)或sea-debug.js(未壓縮)。
下載完成後放到項目的相應位置,而後在頁面中經過<script>標籤引入,你就可使用SeaJS了。
在討論SeaJS的具體使用前,先介紹一下SeaJS的模塊化理念和開發原則。
使用SeaJS開發JavaScript的基本原則就是:一切皆爲模塊。引入SeaJS後,編寫JavaScript代碼就變成了編寫一個又一個模塊,SeaJS中模塊的概念有點相似於面向對象中的類——模塊能夠擁有數據和方法,數據和方法能夠定義爲公共或私有,公共數據和方法能夠供別的模塊調用。
另外,每一個模塊應該都定義在一個單獨js文件中,即一個對應一個模塊。
下面介紹模塊的編寫和調用。
SeaJS中使用「define」函數定義一個模塊。由於SeaJS的文檔並無關於define的完整參考,因此我閱讀了SeaJS源代碼,發現define能夠接收三個參數:
/*** Defines a module.* @param {string=} id The module id.* @param {Array.|string=} deps The module dependencies.* @param {function()|Object} factory The module factory function.*/fn.define = function(id, deps, factory) { //code of function…}
上面是我從SeaJS源碼中摘錄出來的,define能夠接收的參數分別是模塊ID,依賴模塊數組及工廠函數。我閱讀源代碼後發現define對於不一樣參數個數的解析規則以下:
若是隻有一個參數,則賦值給factory。
若是有兩個參數,第二個賦值給factory;第一個若是是array則賦值給deps,不然賦值給id。
若是有三個參數,則分別賦值給id,deps和factory。
可是,包括SeaJS的官方示例在內幾乎全部用到define的地方都只傳遞一個工廠函數進去,相似與以下代碼:
define(function(require, exports, module) { //code of the module...});
我的建議遵循SeaJS官方示例的標準,用一個參數的define定義模塊。那麼id和deps會怎麼處理呢?
id是一個模塊的標識字符串,define只有一個參數時,id會被默認賦值爲此js文件的絕對路徑。如example.com下的a.js文件中使用define定義模塊,則這個模塊的ID會賦值爲 http://example.com/a.js ,沒有特別的必要建議不要傳入id。deps通常也不須要傳入,須要用到的模塊用require加載便可。
工廠函數是模塊的主體和重點。在只傳遞一個參數給define時(推薦寫法),這個參數就是工廠函數,此時工廠函數的三個參數分別是:
require——模塊加載函數,用於記載依賴模塊。
exports——接口點,將數據或方法定義在其上則將其暴露給外部調用。
module——模塊的元數據。
這三個參數能夠根據須要選擇是否須要顯示指定。
下面說一下module。module是一個對象,存儲了模塊的元信息,具體以下:
module.id——模塊的ID。
module.dependencies——一個數組,存儲了此模塊依賴的全部模塊的ID列表。
module.exports——與exports指向同一個對象。
第一種定義模塊的模式是基於exports的模式:
define(function(require, exports, module) { var a = require('a'); //引入a模塊 var b = require('b'); //引入b模塊 var data1 = 1; //私有數據 var func1 = function() { //私有方法 return a.run(data1); } exports.data2 = 2; //公共數據 exports.func2 = function() { //公共方法 return 'hello'; }});
上面是一種比較「正宗」的模塊定義模式。除了將公共數據和方法附加在exports上,也能夠直接返回一個對象表示模塊,以下面的代碼與上面的代碼功能相同:
define(function(require) { var a = require('a'); //引入a模塊 var b = require('b'); //引入b模塊 var data1 = 1; //私有數據 var func1 = function() { //私有方法 return a.run(data1); } return { data2: 2, func2: function() { return 'hello'; } };});
若是模塊定義沒有其它代碼,只返回一個對象,還能夠有以下簡化寫法:
define({ data: 1, func: function() { return 'hello'; }});
第三種方法對於定義純JSON數據的模塊很是合適。
上文說過一個模塊對應一個js文件,而載入模塊時通常都是提供一個字符串參數告訴載入函數須要的模塊,因此就須要有一套從字符串標識到實際模塊所在文件路徑的解析算法。SeaJS支持以下標識:
絕對地址——給出js文件的絕對路徑。
如
require("http://example/js/a");
就表明載入 http://example/js/a.js 。
相對地址——用相對調用載入函數所在js文件的相對地址尋找模塊。
例如在 http://example/js/b.js 中載入
require("./c");
則載入 http://example/js/c.js 。
基址地址——若是載入字符串標識既不是絕對路徑也不是以」./」開頭,則相對SeaJS全局配置中的「base」來尋址,這種方法稍後討論。
注意上面在載入模塊時都不用傳遞後綴名「.js」,SeaJS會自動添加「.js」。可是下面三種狀況下不會添加:
載入css時,如
require("./module1-style.css");
路徑中含有」?」時,如
require(<a href="http://example/js/a.json?cb=func">http://example/js/a.json?cb=func</a>);
路徑以」#」結尾時,如
require("http://example/js/a.json#");
根據應用場景的不一樣,SeaJS提供了三個載入模塊的API,分別是seajs.use,require和require.async,下面分別介紹。
seajs.use主要用於載入入口模塊。入口模塊至關於C程序的main函數,同時也是整個模塊依賴樹的根。上面在TinyApp小例子中,init就是入口模塊。seajs.use用法以下:
//單一模式seajs.use('./a'); //回調模式seajs.use('./a', function(a) { a.run();}); //多模塊模式seajs.use(['./a', './b'], function(a, b) { a.run(); b.run();});
通常seajs.use只用在頁面載入入口模塊,SeaJS會順着入口模塊解析全部依賴模塊並將它們加載。若是入口模塊只有一個,也能夠經過給引入sea.js的script標籤加入」data-main」屬性來省略seajs.use,例如,上面TinyApp的index.html也能夠改成以下寫法:
<!DOCTYPE HTML><html lang="zh-CN"><head> <meta charset="UTF-8"> <title>TinyApp</title></head><body> <p class="content"></p> <script src="./sea.js" data-main="./init"></script></body></html>
這種寫法會令html更加簡潔。
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文件。
上文說過SeaJS會在html頁面打開時經過靜態分析一次性記載全部須要的js文件,若是想要某個js文件在用到時才下載,可使用require.async:
require.async('/path/to/module/file', function(m) { //code of callback...});
這樣只有在用到這個模塊時,對應的js文件纔會被下載,也就實現了JavaScript代碼的按需加載。
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表示是否工做在調試模式下。
要將現有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原本集成了一個打包部署工具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:
<!DOCTYPE HTML><html lang="zh-CN"><head> <meta charset="UTF-8"> <title></title></head><body><div id="content"> <p class="author"></p> <p class="blog"><a href="#">Blog</a></p></div> <script src="./sea.js" data-main="./init"></script></body></html>
javascript:
//init.jsdefine(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.jsdefine({ author: 'ZhangYang', blog: 'http://blog.codinglabs.org'});
css:
.author{color:red;font-size:10pt;}.blog{font-size:10pt;}
運行效果以下: