剛才的示例很簡單?實(shí)際上Sea.js本身小巧而不失靈活,讓我們再來深入地了解下如何使用Sea.js!
Sea.js是CMD這個(gè)模塊系統(tǒng)的一個(gè)運(yùn)行時(shí),Sea.js可以加載的模塊,就是CMD規(guī)范里所指明的。那我們該如何編寫一個(gè)CMD模塊呢?
Sea.js提供了一個(gè)全局方法——define
,用來定義一個(gè)CMD模塊。
define(factory)
define(function(require, exports, module) {
// 模塊代碼
// 使用require獲取依賴模塊的接口
// 使用exports或者module來暴露該模塊的對外接口
})
factory
是這樣一個(gè)函數(shù)function (require?, exports?, module?) {}
,如果模塊本身既不依賴其他模塊,也不提供接口,require
、exports
和module
都可以省略。但通常會是以下兩種形式:
define(function(require, exports) {
var Vango = require('vango')
exports.drawCircle = function () {
var vango = new Vango(document.body, 100, 100)
vango.circle(50, 50, 50, {
fill: true,
styles:{
fillStyle:"red"
}
})
}
})
或者:
define(function(require, exports, module) {
var Vango = require('vango');
module.exports = {
drawCircle: function () {
var vango = new Vango(document.body, 100, 100);
vango.circle(50, 50, 50, {
fill: true,
styles:{
fillStyle:"red"
}
});
}
};
});
注意:必須保證參數(shù)的順序,即需要用到require, exports不能省略;在模塊中exports對象不可覆蓋,如果需要覆蓋請使用
module.exports
的形式(這與node的用法一致,在后面的原理介紹會有相關(guān)的解釋)。你可以使用module.exports
來export任意的對象(包括字符串、數(shù)字等等)。
define(id?, dependencies?, factory)
id:String 模塊標(biāo)識
dependencies:Array 模塊依賴的模塊標(biāo)識
這種寫法屬于Modules/Transport/D規(guī)范。
define('drawCircle', ['vango'], function(require, exports) {
var Vango = require('vango');
exports.drawCircle = function () {
var vango = new Vango(document.body, 100, 100);
vango.circle(50, 50, 50, {
fill: true,
styles:{
fillStyle:"red"
}
});
};
})
與CMD的define
沒有本質(zhì)區(qū)別,我更情愿把它稱作“具名模塊”。Sea.js從用于生產(chǎn)的角度來說,必須支持具名模塊,因?yàn)殚_發(fā)時(shí)模塊拆得太小,生產(chǎn)環(huán)境必須把這些模塊文件打包為一個(gè)文件,如果模塊都是匿名的,那就傻了。
所以Sea.js支持具名模塊也是無奈之舉。
define(anythingelse)
除去以上兩種形式,在CMD標(biāo)準(zhǔn)中,可以給define傳入任意的字符串或者對象,表示接口就是對象或者字符串。不過這只是包含在標(biāo)準(zhǔn)中,在Sea.js并沒有相關(guān)的實(shí)現(xiàn)。
Sea.js為了能夠使用起來更靈活,提供了配置的接口??膳渲玫膬?nèi)容包括靜態(tài)服務(wù)的位置,簡化模塊標(biāo)識或路徑。接下來我們來詳細(xì)地了解下這些內(nèi)容。
config:Object,配置鍵值對。
Sea.js通過.config
API來進(jìn)行配置。你甚至可以在多個(gè)地方調(diào)用seajs.config來配置。Sea.js會mix傳入的多個(gè)config對象。
seajs.config({
alias: {
'jquery': 'path/to/jquery.js',
'a': 'path/to/a.js'
},
preload: ['seajs-text']
})
seajs.config({
alias: {
'underscore': 'path/to/underscore.js',
'a': 'path/to/biz/a.js'
},
preload: ['seajs-combo']
})
上面兩個(gè)配置會合并為:
{
alias: {
'jquery': 'path/to/jquery.js',
'underscore': 'path/to/underscore.js',
'a': 'path/to/biz/a.js'
},
preload: ['seajs-text', 'seajs-combo']
}
config
可以配置的鍵入下:
base:String,在解析絕對路徑標(biāo)識的模塊時(shí)所使用的base路徑。
默認(rèn)地,在不配置base的情況下,base與sea.js的引用路徑。如果引用路徑為http://example.com/assets/sea.js
,則base為http://example.com/assets/
。
在閱讀Sea.js這份文檔時(shí)看到:
當(dāng) sea.js 的訪問路徑中含有版本號時(shí),base 不會包含 seajs/x.y.z 字串。 當(dāng) sea.js 有多個(gè)版本時(shí),這樣會很方便。
即如果sea.js的引用路徑為http://example.com/assets/1.0.0/sea.js,則base仍為http://example.com/assets/。這種方便性,我覺得過了點(diǎn)。
使用base配置,根本上可以分離靜態(tài)文件的位置,比如使用CDN等等。
seajs.config({
base: 'http://g.tbcdn.cn/tcc/'
})
如果我們有三個(gè)CDN域名,如何將靜態(tài)資源散列到這三個(gè)域名上呢?
paths:Object,如果目錄太深,可以使用paths這個(gè)配置項(xiàng)來縮寫,可以在require時(shí)少寫些代碼。
如果:
seajs.config({
base: 'http://g.tbcdn.cn/tcc/',
paths: {
'index': 's/js/index'
}
})
則:
define(function(require, exports, module) {
// http://g.tbcdn.cn/tcc/s/js/index/switch.js
var Switch = require('index/switch')
});
alias:Object,本質(zhì)上看不出和paths有什么區(qū)別,區(qū)別就在使用的概念上。
seajs.config({
alias: {
'jquery': 'jquery/jquery/1.10.1/jquery'
}
})
然后:
define(function(require, exports, module) {
// jquery/jquery/1.10.1/jquery
var $ = require('jquery');
});
看出使用概念的區(qū)別了么?
preload
配置項(xiàng)可以讓你在加載普通模塊之前提前加載一些模塊。既然所有模塊都是在use之后才加載的,preload有何意義?然,看下面這段:
seajs.config({
preload: [
Function.prototype.bind ? '' : 'es5-safe',
this.JSON ? '' : 'json'
]
});
preload比較適合用來加載一些核心模塊,或者是shim模塊。這是一個(gè)全局的配置,使用者無需關(guān)系核心模塊或者是shim模塊的加載,把注意力放在核心功能即可。
還有一些別的配置,比如vars
、map
等,可以參考配置。
seajs.use(id)
Sea.js通過use方法來啟動一個(gè)模塊。
seajs.use('./main')
在這里,./main
是main模塊的id,Sea.js在main模塊LOADED之后,執(zhí)行這個(gè)模塊。
Sea.js還有另外一種啟動模塊的方式:
seajs.use('./main', function(main) {
main.init()
})
Sea.js執(zhí)行ids中的所有模塊,然后傳遞給callback使用。
Sea.js官方提供了7個(gè)插件,對Sea.js的功能進(jìn)行了補(bǔ)充。
importStyle
,動態(tài)地向頁面中插入css;由此可見,Sea.js的插件主要是解決一些附加問題,或者是給Sea.js添加一些額外的功能。私覺得有些功能并不合適讓Sea.js來處理。
總結(jié)一下,插件機(jī)制大概就是兩種:
私還是覺得Sea.js應(yīng)該保持純潔;為了實(shí)現(xiàn)插件,在Sea.js中加入的代碼,感覺有點(diǎn)不值;combo這種事情,更希望采取別的方式來實(shí)現(xiàn)。Sea.js應(yīng)該做好運(yùn)行時(shí)。
很多時(shí)候,某個(gè)工具或者類庫,玩玩可以,但是一用到生產(chǎn)環(huán)境,就感覺力不從心了。就拿Sea.js來說,開發(fā)的時(shí)候根據(jù)業(yè)務(wù)將邏輯拆分到很多小模塊,邏輯清晰,開發(fā)方便。但是上線后,模塊太多,HTTP請求太多,就會拖慢頁面速度。
所以我們必須對模塊進(jìn)行打包壓縮。這也是SPM的初衷。
SPM是什么?
使用者認(rèn)為SPM是Sea.js Package Manager,但是實(shí)際上代表的是Static Package Manager,及靜態(tài)包管理工具。如果大家有用過npm,你可以認(rèn)為SPM是一個(gè)針對前端模塊的包管理工具。當(dāng)然它不僅僅如此。
SPM包括:
SPM心很大,SPM囊括yo、bower和grunt這三個(gè)工具。
spm is a package manager, it is not build tools.
這句話來自github上spm2的README文件。spm是一個(gè)包管理工具,不是構(gòu)建工具!
,它與npm非常相似。
一個(gè)spm的模塊至少包含:
-- dist
-- overlay.js
-- overlay.min.js
-- package.json
在模塊中必須提供一個(gè)package.json,該文件遵循Common Module Definition模塊標(biāo)準(zhǔn)。與node的package.json
兼容。在此基礎(chǔ)上添加了兩個(gè)key。
一個(gè)典型的package.json
文件:
{
"family": "arale",
"name": "base",
"version": "1.0.0",
"description": "base is ....",
"homepage": "http://aralejs.org/base/",
"repository": {
"type": "git",
"url": "https://github.com/aralejs/base.git"
},
"keywords": ["class"],
"spm": {
"source": "src",
"output": ["base.js", "i18n/*"],
"alias": {
"class": "arale/class/1.0.0/class",
"events": "arale/events/1.0.0/events"
}
}
}
dist
目錄包含了模塊必要的模塊代碼;可能是使用spm-build打包的,當(dāng)然只要滿足兩個(gè)條件,就是一個(gè)spm的包。
$ npm install spm -g
安裝好了spm,那該如何使用spm呢?讓我們從help命令開始:
我們可以運(yùn)行spm help
查看spm
所包含的功能:
$ spm help
Static Package Manager
Usage: spm <command> [options]
Options:
-h, --help output usage information
-V, --version output the version number
System Commands:
plugin plugin system for spm
config configuration for spm
help show help information
Package Commands:
tree show dependencies tree
info information of a module
login login your account
search search modules
install install a module
publish publish a module
unpublish unpublish a module
Plugin Commands:
init init a template
build Build a standar cmd module.
spm
包含三種命令,系統(tǒng)命令,即與spm
本身相關(guān)(配置、插件和幫助),包命令,與包管理相關(guān),插件命令,插件并不屬于spm
的核心內(nèi)容,目前有兩個(gè)插件init
和build
。
也可以使用help
來查看單個(gè)命令的用法:
$ spm help install
Usage: spm-install [options] family/name[@version]
Options:
-h, --help output usage information
-s, --source [name] the source repo name
-d, --destination [dir] the destination, default: sea-modules
-g, --global install the package to ~/.spm/sea-modules
-f, --force force to download a unstable module
-v, --verbose show more logs
-q, --quiet show less logs
--parallel [number] parallel installation
--no-color disable colorful print
Examples:
$ spm install jquery
$ spm install jquery/jquery arale/class
$ spm install jquery/jquery@1.8.2
我們可以使用config
來配置用戶信息、安裝方式以及源。
; Config username
$ spm config user.name island205
; Or, config default source
$ spm config source.default.url http://spmjs.org
spm
是一個(gè)包管理工具,與npm
類似,有自己的源服務(wù)器。我們可以使用search
命令來查看源提供的包。
由于
spm
在包規(guī)范中加入了family
的概念,常常想運(yùn)行spm install backbone
,發(fā)現(xiàn)并沒有backbone這個(gè)包。原因就是backbone
是放在gallery
這族下的。
$ spm search backbone
1 result
gallery/backbone
keys: model view controller router server client browser
desc: Give your JS App some Backbone with Models, Views, Collections, and Events.
然后我們就可以使用install
來安裝了,注意我們必須使用包的全名,即族名/包名
。
$ spm install gallery/backbone
install: gallery/backbone@stable
fetch: gallery/backbone@stable
download: repository/gallery/backbone/1.0.0/backbone-1.0.0.tar.gz
save: c:\Users\zhi.cun\.spm\cache\gallery\backbone\1.0.0\backbone-1.0.0.tar.gz
extract: c:\Users\zhi.cun\.spm\cache\gallery\backbone\1.0.0\backbone-1.0.0.tar.gz
found: dist in the package
installed: sea-modules\gallery\backbone\1.0.0
depends: gallery/underscore@1.4.4
install: gallery/underscore@1.4.4
fetch: gallery/underscore@1.4.4
download: repository/gallery/underscore/1.4.4/underscore-1.4.4.tar.gz
save: c:\Users\zhi.cun\.spm\cache\gallery\underscore\1.4.4\underscore-1.4.4.tar.gz
extract: c:\Users\zhi.cun\.spm\cache\gallery\underscore\1.4.4\underscore-1.4.4.tar.gz
found: dist in the package
installed: sea-modules\gallery\underscore\1.4.4
spm
將模塊安裝在了sea_modules
中,并且在~/.spm/cache
中做了緩存。
`~sea-modules/
`~gallery/
|~backbone/
| `~1.0.0/
| |-backbone-debug.js
| |-backbone.js
| `-package.json
`~underscore/
`~1.4.4/
|-package.json
|-underscore-debug.js
`-underscore.js
spm
還加載了backbone
的依賴underscore
。
當(dāng)然,Sea.js也是一個(gè)模塊,你可以通過下面的命令來安裝:
$ spm install seajs/seajs
seajs
的安裝路徑為sea_modules/seajs/seajs/2.1.1/sea.js
,看到這里,結(jié)合seajs頂級模塊定位的方式,對于seajs在計(jì)算base路徑的時(shí),去掉了seajs/seajs/2.1.1/
的原因。
spm
并不是以構(gòu)建工具為目標(biāo),它本身是一個(gè)包管理器。所以spm
將構(gòu)建的功能以插件的功能提供出來。我們可以通過plugin命令來安裝build
:
$ spm plugin install build
安裝好之后,如果你使用的是標(biāo)準(zhǔn)的spm
包模式,就可以直接運(yùn)行spm build
來進(jìn)行標(biāo)準(zhǔn)的打包。
SPM2的功能和命令就介紹到這里,更多的命令在之后的實(shí)踐中介紹。
其實(shí)之前介紹的spm是其第二個(gè)版本spm2。spm的第一個(gè)版本可以在這里找到。
spm與spm2同樣都是包管理工具,那它們之間有什么不同呢?
為什么作者對spm進(jìn)行了大量的重構(gòu)?
之所以進(jìn)行大量的重構(gòu),就是為了保持spm作為包管理工具的特征。如npm一般,只指定最少的規(guī)范(package.json),提供包管理的命令,但是這個(gè)包如何構(gòu)建,代碼如何壓縮并不是spm關(guān)心的事情。
只有規(guī)則簡單合理,只定義接口,不關(guān)心具體實(shí)現(xiàn),才有更廣的實(shí)用性。
spm本身是從業(yè)務(wù)需求成長起來的一個(gè)包管理工具,spm1更多的是一些需求功能的堆砌,而spm2就是對這些功能的提煉,形成一套適用于業(yè)界的工具。
apm的全稱是:
Alipay package manager
即支付寶的包管理工具。
apm是基于spm的一套專門為支付寶開發(fā)的工具集。我們可以這么看,spm2和apm是spm升級后的兩個(gè)產(chǎn)物,spm2更加專注于包管理和普適性,而apm更加專注于支付寶業(yè)務(wù)。由于業(yè)務(wù)細(xì)節(jié)和規(guī)模的不同,apm可能并不適合其他公司所用,所以需要spm2,而又因?yàn)橹Ц秾殬I(yè)務(wù)的特殊性和基因,必須apm。
謝謝 @lepture 的指正:
不一定要用 apm,只是 apm 把所有要用到的插件都打包好了,同時(shí)相應(yīng)的默認(rèn)配置也為支付寶做了處理,方便內(nèi)部員工使用,不用再配置了。
更多建議: