平臺(tái):Windows 7
版本:1.7.7
簡(jiǎn)介
Sea.js實(shí)現(xiàn)了對(duì)JS代碼的模塊化組織,大大提高了前端開(kāi)發(fā)效率。然而在實(shí)際項(xiàng)目中,大量的細(xì)分模塊卻導(dǎo)致大量的腳本請(qǐng)求,拖慢了頁(yè)面加載速度,也給服務(wù)器造成不小的壓力。針對(duì)這一情況,spm(static package manager)因運(yùn)而生,專門(mén)用于打包、壓縮Sea.js模塊以及CSS文件。
Sea.js遵循CMD(Common Module Definition)模塊定義規(guī)范,一個(gè)文件一個(gè)模塊,清清爽爽。CMD模塊定義形如:
define(function(require, exports, module) {
? ? //使用require表達(dá)對(duì)其他CMD模塊的依賴
? ? //使用exports或module.exports或return向外拋出接口
});
spm的打包原理很簡(jiǎn)單,就是將這一形式(Modules/Wrappings)的CMD模塊轉(zhuǎn)換為Modules/Transport形式:
define("id", ["dep-1", "dep-2"], function(require, exports, module) {
? ? //源碼
});
這一過(guò)程僅僅是給模塊換了個(gè)框框,spm需要完成的工作當(dāng)然不止于此,主要可以概括如下:
1、如上述那樣按照Modules/Transport格式在模塊定義中加入標(biāo)識(shí)和依賴,并把多個(gè)模塊合并到一個(gè)文件中(包括CSS);
2、將源碼中的變量名、函數(shù)名等標(biāo)識(shí)符簡(jiǎn)化為a、b、c這樣的單字母,并刪除空格、縮進(jìn)、換行等空白符(所以最終你看到的是一長(zhǎng)串擁擠的、可讀性極低的代碼);
3、識(shí)別無(wú)用代碼后將其刪除,還有其他一些優(yōu)化措施。
這些步驟在本地完成,都是靜態(tài)的文本處理,過(guò)程并不復(fù)雜,如果用起來(lái)不滿意,完全可以自己實(shí)現(xiàn)一個(gè),甚至可以用C語(yǔ)言寫(xiě)。
安裝
首先我們需要Node.js,到官網(wǎng)下載安裝即可,完后記得把安裝路徑(如C:\Program Files\nodejs)加到環(huán)境變量Path里,在cmd中運(yùn)行node -v查看版本以確定是否安裝成功:

接著使用Node.js的包管理工具npm(node package manager)安裝spm。spm版本已更新到3.9,但是從第3版開(kāi)始轉(zhuǎn)向了CommonJS規(guī)范,在打包前需要扒掉所有模塊的define封裝,項(xiàng)目中幾十個(gè)乃至幾百個(gè)文件都需要手動(dòng)處理,實(shí)在犯不著。于是我們轉(zhuǎn)投低版本——spm@2.x或spm@1.x,經(jīng)過(guò)多次測(cè)試,spm@2.x的各個(gè)版本在安裝或使用過(guò)程中總出現(xiàn)莫名其妙的報(bào)錯(cuò),也許是與Windows兼容不佳(具體未能考證),只好拿spm@1.x將就將就——當(dāng)然,spm@1.x已經(jīng)可以滿足所需。
安裝spm前,如果Node.js裝在了C盤(pán),最好通過(guò)以下兩行命令先修改一下npm的全局路徑,否則spm的相關(guān)文件可能會(huì)無(wú)法正常寫(xiě)入磁盤(pán):
npm config set prefix "D:\nodejs\node_global"
npm config set cache "D:\nodejs\node_cache"
(這里以D盤(pán)的一個(gè)路徑為例,當(dāng)然你得先新建好這兩個(gè)文件夾。)
將spm@1.x安裝到全局路徑:
npm install spm@1.x -g

完成后,在剛才設(shè)置的node_global文件夾下可以看到spm的相關(guān)文件,其中spm.cmd就是主程序,將D:\nodejs\node_global添加到環(huán)境變量Path,執(zhí)行spm查看版本和可用指令:

如未能順利安裝或安裝后使用有問(wèn)題可以執(zhí)行如下命令卸載spm,而后嘗試重裝(其他版本):
npm uninstall spm -g
使用示例
編寫(xiě)一個(gè)簡(jiǎn)單的示例,功能模塊demo(位于modules目錄下)包含a.js、b.js、c.js三個(gè)CMD模塊,b和c放在subModules文件夾下,a作為入口調(diào)用b,b調(diào)用c:



通過(guò)seajs.use()在頁(yè)面中調(diào)用a模塊:
seajs.use(['modules/demo/a'], function(a) {
? ? a();
});

下面我們使用spm將a、b、c進(jìn)行打包。首先需要一個(gè)工作目錄,比如D:\spmworkspace,將整個(gè)功能模塊(本例中就是demo文件夾)放在這里,而后需要一個(gè)package.json文件,用于編輯spm build的配置信息:

"root":功能模塊的根目錄名,這取決于它在整個(gè)項(xiàng)目中處于哪個(gè)位置。
"name":功能模塊的名稱。默認(rèn)情況下,打包后的Modules/Transport模塊標(biāo)識(shí)是對(duì)"root"、"name"和CMD模塊文件名的拼接,如在本例中模塊a的id就是modules/name/a。
"src":源文件夾(要求與package.json在同級(jí)目錄下),存放需要打包的JS文件們。
"to":目標(biāo)文件夾,用于存放打包后生成的文件。該文件夾會(huì)自動(dòng)生成,也與package.json在同級(jí)目錄下。
"output":配置文件合并規(guī)則,這里配置的"a.js"要求在demo文件夾下必須存在相應(yīng)的a.js文件。
配置好后,在cmd中來(lái)到工作目錄D:\spmworkspace下,執(zhí)行spm build:

此時(shí)在D:\spmworkspace下就出現(xiàn)了一個(gè)dist文件夾,里頭包含a.js和a-debug.js兩個(gè)文件,其中a.js是最終打包結(jié)果:

這是在項(xiàng)目上線時(shí)用的,在開(kāi)發(fā)調(diào)試時(shí),我們可以使用a-debug.js:

部署時(shí)直接將a.js替換原本的a.js即可,subModules下的b.js和c.js不再需要,完美運(yùn)行:

可以看到,只加載了壓縮后的a.js,文件總體大小和加載時(shí)間都大大縮減,在實(shí)際項(xiàng)目中打包的CMD模塊越多,表現(xiàn)就越好,這就是spm的價(jià)值所在。
幾點(diǎn)補(bǔ)充
require的路徑問(wèn)題
通常在項(xiàng)目開(kāi)發(fā)中,require主要使用兩種形式的路徑載入其他CMD模塊:
一種是相對(duì)路徑,以./或../開(kāi)頭,這種形式用于同一功能模塊之內(nèi)JS文件之間的相互引用;
一種是全局base尋址形式,直接以文件或文件夾名打頭,形如xxx/xxx,Sea.js會(huì)根據(jù)SeaConfig.js中配置的base路徑來(lái)拼接出絕對(duì)路徑,這種形式用于功能模塊之間JS文件的相互引用,比如有個(gè)實(shí)現(xiàn)通用功能的util模塊,其他功能模塊中的JS通常會(huì)以require(util/utilA)的形式引用。
spm打包是針對(duì)單個(gè)功能模塊的,無(wú)法找到以上述第二種路徑形式require的文件,因此不會(huì)將它們打包進(jìn)來(lái)。確實(shí)對(duì)于其他功能模塊里的東西,也沒(méi)必要都?jí)嚎s進(jìn)來(lái)。
如果一定要將它們一齊打包,就需要用到spm提供的C/S模式的源服務(wù)。使用spm server指令可以在本地構(gòu)建一個(gè)源服務(wù)器,局域網(wǎng)內(nèi)的開(kāi)發(fā)者可以將打包好的各個(gè)功能模塊部署上去,通過(guò)在C:\Users\Administrator\.spm\config.json中增加源服務(wù)器、在package.json中增加"dependencies"配置、"output"中的"."改為"*",在打包時(shí)就可以加入在源服務(wù)中的其他功能模塊。如此倒是提供了協(xié)同開(kāi)發(fā)的可能性,但操作起來(lái)挺不方便,我也沒(méi)有進(jìn)行驗(yàn)證,如有不對(duì)之處,還望路過(guò)的大神不吝賜教。
多個(gè)入口文件的打包
在本文的demo示例中,整個(gè)功能模塊只有a.js一個(gè)入口,它把其他CMD模塊都牽扯到了,如果入口文件不止一個(gè),或者說(shuō)一些文件與另一些文件相互獨(dú)立怎么辦呢?比如在util下,有負(fù)責(zé)加法的add.js和負(fù)責(zé)減法的sub.js,它們之間互不引用,package.json可以這樣配置:
"output": {
? ? "add.js": ".",
? ? "sub.js": "."
}
最終生成兩個(gè)壓縮后的JS,或者將它們強(qiáng)行合并:
"output": {
? ? "util.js": ["add.js", "sub.js"]
}
圖片的相對(duì)路徑問(wèn)題
比如在CSS中使用相對(duì)路徑引用圖片,在打包前表現(xiàn)正常,但打包后CSS內(nèi)容并入JS中,被載入JSP或HTML頁(yè)面,相對(duì)路徑將以頁(yè)面文件位置為基準(zhǔn),此時(shí)就需要調(diào)整圖片文件的位置,在開(kāi)發(fā)時(shí)使用絕對(duì)路徑可避免這一情況。
CSS載入問(wèn)題
實(shí)測(cè)表明,只有在入口JS中載入CSS才能正常打包,如果其他CMD模塊require了CSS,spm將提示無(wú)法找到該CSS文件,這一現(xiàn)象很奇特,目前尚未弄明原因。
開(kāi)發(fā)約束/建議(僅針對(duì)1.7.7版)
1、確保所有JS文件符合CMD規(guī)范;
2、入口JS文件(可以有多個(gè))應(yīng)放在功能模塊根目錄下;
3、避免使用相對(duì)路徑require其他功能模塊里的JS;
4、CSS的require放在入口JS文件中;
5、使用絕對(duì)路徑引用圖片。
學(xué)習(xí)資料
關(guān)于Sea.js
關(guān)于spm
2016年2月22日 無(wú)錫