前言
Javascript模塊化規范有CommonJs規范,和主要適用於瀏覽器環境的AMD規范,以及國內的CMD規范,它是SeaJs遵循的模塊化規范。因為以前項目中用SeaJs做過前端的模塊管理工具,所以這里總結一下自己的使用心得。
在試用SeaJs和官方推薦的CMD包管理工具——Spm2.x的過程中,遇到了很多高低版本不兼容和配置參數沒弄明白的問題,后來在網上各處找資料才大概弄懂。這里我強調一下版本,是因為可能有的同學項目開始較早,用了以前版本的Seajs,再去看Seajs官網的API有些地方會不適用!下面各節對框架的描述中也都會帶上版本。
文中可能會有一些理解有誤或者沒講清楚的地方,有大神路過懇請指導...
一個簡單的DEMO
Seajs可以實現前端Js文件的按需加載和前端模塊管理的功能,不熟悉Seajs API (v2.3.0)的同學可以看看這里,非常簡單。
首先上一個使用Seajs的demo。目錄結構如下:
html:

1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 <script src="../js/lib/seajs1.2.0.js"></script> 7 </head> 8 <body> 9 <h1>spm前端構建測試</h1> 10 <script> 11 seajs.config({ 12 base: '../js' 13 // paths:{'pathtest':'src'}, //生效版本:seajs2.3.0 14 }); 15 seajs.use("src/eat.js",function(eat){ 16 if(eat) eat.start(); 17 }); 18 </script> 19 </body> 20 </html>
eat.js

1 "use strict" 2 define(function(require,exports,module){ 3 var rice=require("./rice"), 4 water=require("./water"), 5 i=require("./i"); 6 exports.start=function(){ 7 i.eat(rice.rice); 8 i.drink(water.water); 9 } 10 })
rice.js

1 "use strict" 2 define({rice:"i'm rice"})
i.js

1 "use strict" 2 define(function () { 3 return { 4 name:"tzyy", 5 eat:function(rice){ 6 alert(rice+",Delicious!"); 7 }, 8 drink: function (water) { 9 alert(water+",爽!"); 10 } 11 } 12 })
water.js

1 "use strict" 2 define({water:"i'm water"});
項目結構:
這樣就實現的前端代碼的模塊化,和模塊的按需加載、執行。
但是,這樣還不夠,我們打開Chrome控制台,network標簽,刷新網頁發現:
代碼確實是模塊化了,同時代碼也分散到了各個文件中。當項目中的文件非常多,由於http請求是無狀態的,每次都要建立、斷開連接,另外瀏覽器下載靜態文件的並發數量也是有上限的,會導致頁面加載的速度比所有代碼在同一個文件中要慢。
所以我們要對js文件進行壓縮、合並。那么問題來了:
1.define函數是seajs提供的,require函數也是seajs給注入的,代碼壓縮,函數名字發生變化后seajs還認得嗎?
2.現在的代碼中require接收的參數都是模塊文件的路徑,文件要是都合並了,這些代碼還上哪找去?
幸好有spm,它提供了構建CMD模塊代碼的功能,可以對模塊文件進行壓縮、合並的操作。
用spm 2.2.5進行模塊代碼的壓縮&合並
了解關於CMD模塊的壓縮和合並之前最好先了解一下模塊標識和構建過程相關的知識,以免掉入萬人坑。
這兩個點官方文檔講得很好,我就不再綴述了。
seajs模塊標識
https://github.com/seajs/seajs/issues/258
spm2.x構建過程詳解
http://docs.spmjs.org/doc/build-task
package.json配置
使用spm2構建cmd模塊需要在模塊根目錄創建一個package.json文件。 繼續以上面的項目為例:
{ "family":"tzyy", "name":"test", "version": "1.0.0", "spm": { "source": "src", "idleading":"pack/","output": ["eat.js"] } }
spm視圖像java項目里的maven那樣建立一個依賴管理的機制,family、name、version,這三個屬性用來定位一個項目,是必須填寫的。但是我們這里只是用spm來做構建工具,所以這幾個屬性隨便填寫就可以了。
spm屬性的值是一個json,其屬性
src——指定源碼目錄,spm將使用src配置地址中的文件來構建項目
idleading——指定模塊id前綴,spm在生成transport文件的過程中將這個屬性作為模塊id的前綴
output——一個數組,指定應該輸出哪些文件的構建結果
壓縮&合並
首先安裝好nodejs、npm、spm2.2.5,安裝的教程網上有很多。
進入/js目錄,shift+右鍵——在此處打開命令窗口,然后執行命令:
這個命令在/js下創建了一個mypack目錄來存放壓縮后的文件
兩個文件的內容為:
eat.js:

1 "use strict";define("pack/eat",["./rice","./water","./i"],function(a,b){var c=a("./rice"),d=a("./water"),e=a("./i");b.start=function(){e.eat(c.rice),e.drink(d.water)}}),define("pack/rice",[],{rice:"i'm rice"}),define("pack/water",[],{water:"i'm water"}),define("pack/i",[],function(){return{name:"tzyy",eat:function(a){alert(a+",Delicious!")},drink:function(a){alert(a+",爽!")}}});
eat-debug.js

1 "use strict"; 2 3 define("pack/eat-debug", [ "./rice-debug", "./water-debug", "./i-debug" ], function(require, exports, module) { 4 var rice = require("./rice-debug"), water = require("./water-debug"), i = require("./i-debug"); 5 exports.start = function() { 6 i.eat(rice.rice); 7 i.drink(water.water); 8 }; 9 }); 10 11 /** 12 * Created by zouchengzhuo on 2015/10/21. 13 */ 14 "use strict"; 15 16 define("pack/rice-debug", [], { 17 rice: "i'm rice" 18 }); 19 20 /** 21 * Created by zouchengzhuo on 2015/10/21. 22 */ 23 "use strict"; 24 25 define("pack/water-debug", [], { 26 water: "i'm water" 27 }); 28 29 /** 30 * Created by zouchengzhuo on 2015/10/21. 31 */ 32 "use strict"; 33 34 define("pack/i-debug", [], function() { 35 return { 36 name: "tzyy", 37 eat: function(rice) { 38 alert(rice + ",Delicious!"); 39 }, 40 drink: function(water) { 41 alert(water + ",爽!"); 42 } 43 }; 44 });
可以看到eat.js已經被壓縮,並且其依賴模塊也被壓縮后合並在了同一個文件中。
將html中啟動模塊的代碼修改一下:

1 seajs.use("mypack/eat.js",function(eat){ 2 if(eat) eat.start(); 3 });
運行正常,看看網絡請求:
剛剛那些文件只產生了一個網絡請求,3ms遠遠小於 3ms+4ms+12ms+14ms吧。
seajs config中使用了別名的壓縮&合並
但是,當我們的seajs項目中使用了別名的配置時,情況又不同了!修改項目結構如下:
為water.js配置別名如下:

1 seajs.config({ 2 base: '../js', 3 alias:{ 4 "water":"src/alias/water.js" 5 } 6 });
從src目錄啟動,可以正常運行。然后構建一下,發現:
得到的eat-debug.js:

1 /** 2 * Created by zouchengzhuo on 2015/10/21. 3 */ 4 "use strict"; 5 6 define("pack/eat-debug", [ "./rice-debug", "water-debug", "./i-debug" ], function(require, exports, module) { 7 var rice = require("./rice-debug"), water = require("water-debug"), i = require("./i-debug"); 8 exports.start = function() { 9 i.eat(rice.rice); 10 i.drink(water.water); 11 }; 12 }); 13 14 /** 15 * Created by zouchengzhuo on 2015/10/21. 16 */ 17 "use strict"; 18 19 define("pack/rice-debug", [], { 20 rice: "i'm rice" 21 }); 22 23 /** 24 * Created by zouchengzhuo on 2015/10/21. 25 */ 26 "use strict"; 27 28 define("pack/i-debug", [], function() { 29 return { 30 name: "tzyy", 31 eat: function(rice) { 32 alert(rice + ",Delicious!"); 33 }, 34 drink: function(water) { 35 alert(water + ",爽!"); 36 } 37 }; 38 });
報錯,找不到water模塊,最終得到的輸出文件中也沒有包含water模塊。
原因是如果seajs配置了別名,在package.json的spm屬性里邊應該也配置一下,這樣spm會首先將別名替換成全名,然后再構建。
修改package.json,把alias配置貼過去,如下:

{ "family":"tzyy", "name":"test", "version": "1.0.0", "spm": { "source": "src", "idleading":"pack/", "alias":{ "water":"src/alias/water.js" }, "output": ["eat.js"] } }
再次運行,結果依然不對!這種現象官網上並沒有解釋,經過試驗和網上查資料,
我的理解:
這里要注意一下,由於seajs的config中指定了base路徑 ../js,別名攜程 src/alias/water.js是可以的,但是spm構建的時候,不會這么想,它似乎將src/alias/water.js作為一個頂級標識來處理,而頂級標識一般被認為是其他的公用模塊,不會被transport,也不會被合並到輸出文件中。 這里不對的話請指正:)
在package.json中將alias里邊water的別名配置為 ./alias/water.js 這種相對路徑,輸出文件表現就正常了:

1 /** 2 * Created by zouchengzhuo on 2015/10/21. 3 */ 4 "use strict"; 5 6 define("pack/eat-debug", [ "./rice-debug", "./alias/water-debug.js", "./i-debug" ], function(require, exports, module) { 7 var rice = require("./rice-debug"), water = require("./alias/water-debug.js"), i = require("./i-debug"); 8 exports.start = function() { 9 i.eat(rice.rice); 10 i.drink(water.water); 11 }; 12 }); 13 14 /** 15 * Created by zouchengzhuo on 2015/10/21. 16 */ 17 "use strict"; 18 19 define("pack/rice-debug", [], { 20 rice: "i'm rice" 21 }); 22 23 /** 24 * Created by zouchengzhuo on 2015/10/21. 25 */ 26 "use strict"; 27 28 define("pack/alias/water-debug", [], { 29 water: "i'm water" 30 }); 31 32 /** 33 * Created by zouchengzhuo on 2015/10/21. 34 */ 35 "use strict"; 36 37 define("pack/i-debug", [], function() { 38 return { 39 name: "tzyy", 40 eat: function(rice) { 41 alert(rice + ",Delicious!"); 42 }, 43 drink: function(water) { 44 alert(water + ",爽!"); 45 } 46 }; 47 });
但是此時cmd中依然會報錯
不知道這是spm2.2.5的bug,還是我上面的理解有問題,官方文檔和網上也沒找到資料,求指導~
啟動模塊合並
從上一小節看來,壓縮之后不是會自動合並模塊嗎? 為什么還要合並呢?
下面我們來變一下項目內容。增加一個啟動模塊drink.js:
drink.js內容:

1 define(function(require,exports,module){ 2 var water=require("water"), 3 i=require("./i.js"); 4 return function(){ 5 i.drink(water.water); 6 } 7 });
在package中增加一個輸出模塊 drink.js

1 { 2 "family":"tzyy", 3 "name":"test", 4 "version": "1.0.0", 5 "spm": { 6 "source": "src", 7 "idleading":"pack/", 8 "alias":{ 9 "water":"./alias/water.js" 10 }, 11 "output": ["eat.js","drink.js"] 12 } 13 }
運行
可以看到生成了兩個模塊
html中增加一個啟動模塊

1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 <script src="../js/lib/seajs1.2.0.js"></script> 7 </head> 8 <body> 9 <h1>spm前端構建測試</h1> 10 <script> 11 seajs.config({ 12 base: '../js', 13 alias:{ 14 "water":"src/alias/water.js" 15 } 16 }); 17 seajs.use("mypack/eat.js",function(eat){ 18 if(eat) eat.start(); 19 }); 20 seajs.use("mypack/drink.js",function(drink){ 21 if(drink) drink(); 22 }); 23 </script> 24 </body> 25 </html>
在瀏覽器中運行,正常,打開控制台查看網絡請求
可以看到請求了兩個模塊啟動文件,eat.js 和 drink.js ,並沒有被合並為一個文件。
而且,打開drink-debug.js,可以看到

1 define("pack/drink-debug", [ "./alias/water-debug.js", "./i-debug" ], function(require, exports, module) { 2 var water = require("./alias/water-debug.js"), i = require("./i-debug"); 3 return function() { 4 i.drink(water.water); 5 }; 6 }); 7 8 /** 9 * Created by zouchengzhuo on 2015/10/21. 10 */ 11 "use strict"; 12 13 define("pack/alias/water-debug", [], { 14 water: "i'm water" 15 }); 16 17 /** 18 * Created by zouchengzhuo on 2015/10/21. 19 */ 20 "use strict"; 21 22 define("pack/i-debug", [], function() { 23 return { 24 name: "tzyy", 25 eat: function(rice) { 26 alert(rice + ",Delicious!"); 27 }, 28 drink: function(water) { 29 alert(water + ",爽!"); 30 } 31 }; 32 });
而drink.js中,water和i 兩個模塊在eat.js中也是存在的,這也是對資源的浪費。
此時我們可以再新建一個模塊,將上面那兩個模塊放入該模塊中啟動:
main.js

1 define(function (require,exports,module) { 2 var eat=require("./eat"), 3 drink=require("./drink"); 4 eat.start(); 5 drink(); 6 })
package.json輸出模塊改為main.js

1 { 2 "family":"tzyy", 3 "name":"test", 4 "version": "1.0.0", 5 "spm": { 6 "source": "src", 7 "idleading":"pack/", 8 "alias":{ 9 "water":"./alias/water.js" 10 }, 11 "output": ["./main.js"] 12 } 13 }
html中啟動代碼修改為:
seajs.use("mypack/main.js");
再次運行可以看到所有文件都被壓縮、合並了
當啟動模塊越來越多時,我們就可以這樣干。
css的壓縮、合並
spm2.x對css的壓縮合並是通過seajs的seajs.importStyle函數來完成的。
我們在src目錄下建一個css文件夾(之前把src目錄放到js目錄里邊了,不想改了,大家記得調換一下順序)
然后在main.js中require :

1 define(function (require,exports,module) { 2 var eat=require("./eat"), 3 drink=require("./drink"); 4 require("./css/style.css"), 5 require("./css/style2.css"); 6 eat.start(); 7 drink(); 8 })
運行spm build,最后得到的main-debug.js中可以看到如下代碼片段:
這樣css文件也被壓縮到了main.js文件中。

1 define("pack/main-debug", [ "./eat-debug", "./rice-debug", "./alias/water-debug.js", "./i-debug", "./drink-debug", "./css/style-debug.css", "./css/style2-debug.css" ], function(require, exports, module) { 2 var eat = require("./eat-debug"), drink = require("./drink-debug"); 3 require("./css/style-debug.css"), require("./css/style2-debug.css"); 4 eat.start(); 5 drink(); 6 }); 7 8 以及最下方的 9 define("pack/css/style-debug.css", [], function() { 10 seajs.importStyle("body{color:red}"); 11 }); 12 13 define("pack/css/style2-debug.css", [], function() { 14 seajs.importStyle("h1{font-size:100px}"); 15 });
這里有一些關於seajs版本的坑
1.seajs2.2.1中,可以直接require css文件,但是seajs2.3.0不行,需要引入一個seajs-css插件
2.seajs2.3.0中,通過seajs.use啟動一個transport文件中的模塊時(也就是從合並過的代碼中啟動模塊時),use中寫的模塊id必須和define函數中寫的模塊id相同,但是在seajs2.2.1中卻是必須和文件路徑相同。 seajs2.3.0如此做的原因是,可以在合並過的文件中可以通過模塊id准確獲取模塊exports的內容,好傳回use的回調函數中。
這些坑在seajs官網文檔里面都不太容易找到。
開發、生產環境自動切換
定義一個變量,來設置模塊啟動路徑,可以很方便的切換開發、生產環境。
將html中seajs配置、啟動模塊代碼做如下修改:

1 var environment=location.hostname=="localhost"?"src":"mypack"; 2 seajs.config({ 3 base: '../js', 4 alias:{ 5 "water":"src/alias/water.js" 6 } 7 }); 8 seajs.use(environment+"/main.js");
這樣通過localhost訪問時,自動啟用src目錄下的代碼,生產環境時,自動啟用打包目錄下的代碼。
最后
前端領域的技術正在飛速發展,日新月異,各種技術層出不窮! 這篇文章主要在講我使用spm2.2.5作為seajs的構建工具的使用經歷,spm3.x和spm2.x的差別都非常大。和CommonJs、AMD規范相關的還有很多優秀的模塊管理工具,希望能在學習使用這些工具的過程中能不斷深入理解模塊化的精髓!
這篇文章是學習過程中的產物,可能有很多沒講清楚或者理解有誤的地方,如果您路過發現,還望在評論里指出,我會在文中更正!