seajs之grunt應用

一步步學會使用SeaJS 2.0

本文分爲以下8步,熟悉之後就能夠熟練使用SeaJS,從此之後你的生活會變得更加輕鬆愉悅!
1、SeaJS是什麼?
2、下載並檢閱SeaJS
3、建立工程和各種目錄
4、引入SeaJS庫
5、編寫自己的代碼
6、引入自己的代碼
7、壓縮合並
8、總結展望


--------------------------------------------------


1、SeaJS是什麼?


你一定聽過前端模塊化開發吧?神馬,你沒聽過?我只能說你out了……
你應該知道Java的import吧?神馬,你又不知道?那你應該知道CSS中的import吧……
在這裏我不想展開說前端模塊化的含義和價值,因爲這裏有一篇好文(https://github.com/seajs/seajs/issues/547),詳細說明了前端模塊化。

我知道你看到那麼長的文章肯定會望而卻步,也許你是希望能夠快速開始敲代碼(程序員的通病……)。沒關係,如果實在讀不下去,只要記住模塊化要解決的問題即可:命名衝突、文件依賴關係。

這兩個鬧心的問題應該遇到過吧,如果沒遇到過……我只能說你太牛X了

好了,前端模塊化就扯到這裏,寫過前端的人應該基本都知道JavaScript自身是不支持模塊化開發的,所以就有了SeaJS這款神器,爲前端從業者提供了一個強大易用的模塊化開發工具。




2、下載並檢閱SeaJS


SeaJS現在已經是2.0版本啦,到這裏下載:https://github.com/seajs/seajs


解壓後會看到下列目錄:


其中:
dist —— 壓縮好的、用於瀏覽器端的SeaJS代碼
docs —— 文檔
src —— 源代碼
package.json + Gruntfile.js —— Grunt構建工具所需要的文件,這個在第七步壓縮合並會介紹到
其他目錄或文件可以暫且不管




3、建立工程和各種目錄


準備工作已經完成,我們終於可以開始進行開發啦!來,跟我走:
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目錄並行存放。


至此,工程和文件都已準備完成。




4、引入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





5、編寫自己的代碼


這裏作爲示範,只做了一個非常簡單的效果,點擊查看: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,我們都可以方便的應對。




6、引入自己的代碼


你看到這個小標題,你可能會極力的鄙視我,這等工作還需要你來示範?於是,你啪啪啪啪,在引入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的時候,到這裏也感到了無比的舒心




7、壓縮合並


正當我伸伸懶腰,打算上個廁所的時候,突然想到一件事情:如果模塊越來越多,那麼多文件都要分開加載?那豈不嚴重影響性能!?(啥,你不知道爲啥?)
要壓縮合並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>
大功告成!




8、總結展望


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


==================================

第一次發文章,難免會有各種不足,歡迎大家批評指正
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章