shut up, show me the code!
要想真正地了解一個(gè)加載器是如何工作的,就是自己實(shí)現(xiàn)一個(gè)!讓我們來一步一步地實(shí)現(xiàn)一個(gè)名為bodule.js的模塊加載器。
一個(gè)模塊系統(tǒng),必然有一些約定,下面是bodule.js的規(guī)范。
bodule.js的模塊由以下幾個(gè)概念組成:
define(id, dependancies?, factory)
id必須為完整的url,dependancies如果沒有依賴,則可以省略,factory包含兩種形式:
Function:function(require, [exports,] [module]):
非Function:直接作為該meta模塊的exports。
define('http://bodule.org/island205/venus/1.0.0/venus', ['./vango'], function (require, exports, module) {
//CommonJS
})
// or
define('http://bodule.org/island205/venus/1.0.0/conststring', 'bodule.js')
// even or
define('http://bodule.org/island205/venus/1.0.0/undefined', undefined)
dependancies中的字符串以及CommonJS中的require的參數(shù),必須為url、相對路徑或頂級路徑的解析依賴于前面的id。
http://bodule.org/island205/venus/1.0.0/venus.js
對應(yīng)的模塊文件內(nèi)容為:
define('http://bodule.org/island205/venus/1.0.0/venus', ['./vango'], function (require, exports, module) {
//CommonJS for venus
})
define('http://bodule.org/venus/1.0.0/vango', [], function (require, exports, module) {
//CommonJS for vango
})
該模塊文件包含兩個(gè)meta module,而第一個(gè)是必須的。但這兩個(gè)meta模塊的順序不做要求。
為了簡化代碼,針對
define('http://bodule.org/island205/venus/1.0.0/venus', ['./vango'], function (require, exports, module) {
//CommonJS for venus
})
這樣的代碼我們可以將其簡化為:
define('./venus/1.0.0/venus', ['./vango'], function (require, exports, module) {
//CommonJS for venus
})
或者:
define('/venus/1.0.0/venus', ['./vango'], function (require, exports, module) {
//CommonJS for venus
})
這樣的形式,然相對路徑或者頂級路徑必須要由一個(gè)絕對路徑可參照,在bodule.js中,這個(gè)絕對路徑來自于當(dāng)前頁面的url地址,或者使用bodule.package進(jìn)行配置。
在node中,可以使用require('underscore')來引用node_modules中的模塊,作為bodule.js的目標(biāo),將commonjs橋接到瀏覽器端來使用,所以允許使用類似的寫法,這種模塊我們把它稱作bodule模塊,resovle后映射到http://bodule.org/underscore/stable
,bodule.js會在bodule.org上提供一個(gè)云服務(wù),來支持你從這里加載這些bodule模塊。
如果你想使用自己的bodule服務(wù)器,可以使用bodule.package來配置boduleServer。
npm非常流行,bodule.js將其作為模塊的源。我們采取與npm包一致的策略。典型的npm的package.json為(以underscore為例):
{
"name" : "underscore",
"description" : "JavaScript's functional programming helper library.",
"homepage" : "http://underscorejs.org",
"keywords" : ["util", "functional", "server", "client", "browser"],
"author" : "Jeremy Ashkenas <jeremy@documentcloud.org>",
"repository" : {"type": "git", "url": "git://github.com/jashkenas/underscore.git"},
"main" : "underscore.js",
"version" : "1.5.1",
"devDependencies": {
"phantomjs": "1.9.0-1"
},
"scripts": {
"test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true"
},
"licenses": [
{
"type": "MIT",
"url": "https://raw.github.com/jashkenas/underscore/master/LICENSE"
}
],
"files" : ["underscore.js", "LICENSE"]
}
bodule.js將會使用工具將其轉(zhuǎn)化為bodule模塊,最終會以http://bodule.org/underscore/1.5.1
這樣的地址地提供出來。注意:該地址會根據(jù)package.json中的main,變?yōu)?code>http://bodule.org/underscore/1.5.1/underscore。
在頁面中使用一個(gè)模塊,相當(dāng)于node id.js
。
在頁面上定義一個(gè)即時(shí)的模塊,該模塊依賴于dependancies,并use該模塊。等價(jià)于:
define('a-random-id', dependencies, factory)
Bodule.use('a-random-id')
.use比較簡單的例子,simplest.html:
<script type="text/javascript">
Bodule.use('./a.js')
Bodule.use('/b.js')
Bodule.use(['./c.js', './d'], function (require, exports, module) {
var c = require('./c.js')
var d = require('./d')
console.log(c + d)
})
Bodule.use(['./e'], function (require) {
var e = require('./e')
console.log(e)
})
</script>
定義一個(gè)meta module;
定義一個(gè)meta module,該模塊的exports即為anythingNotFunction;
幾個(gè)例子:d.js,e.js,backbone.js
配置模塊和bodule模塊的位置,還可以配置依賴的bodule模塊的版本號。
Bodule.package({
cwd: 'http://bodule.org:8080/',
path: '/bodule.org/',
bodule_modules:{
cwd: 'http://bodule.org:3000/',
path: '/bower_components/',
dependencies: {
'backbone': '1.0.0'
}
}
})
完整的例子可以參考bodule.org.html。
讓我們開始吧!
coffeescript是一門非常有趣的語言,敲起代碼來很舒服,不會被JavaScript各種繁瑣的細(xì)節(jié)所煩擾。所以我打算使用它來實(shí)現(xiàn)bodule.js。訪問[coffeescript.org],上面有簡潔文檔,如果你熟悉JavaScript,我相信你能很快掌握CoffeeScript的。
從bodule的規(guī)范中,可以看出,它其實(shí)commonjs,或者說是commonjs wrapping的一個(gè)實(shí)現(xiàn)。因此,我們將直接使用commonjs的方式來組織我們的代碼,你會發(fā)現(xiàn),這樣的代碼非常清晰易讀。
# This is a **private** CommonJS runtime for `bodule.js`.
# `__modules` for store private module like `util`,`path`, and so on.
modules = {}
# `__require` is used for getting module's API: `exports` property.
require = (id)->
module = modules[id]
module.exports or module.exports = use [], module.factory
# Define a module, save module in `__modules`. use `id` to refer them.
define = (id, deps, factory)->
modules[id] =
id: id
deps: deps
factory:factory
# `__use` to start a CommonJS runtime, or get a module's exports.
use = (deps, factory)->
module = {}
exports = module.exports = {}
# In factory `call`, `this` is global
factory require, exports, module
module.exports
上面這段代碼是commonjs規(guī)范一種精簡的表達(dá),出自node項(xiàng)目中的module.js。module.js比這復(fù)雜多了,包含了多native module、讀取、執(zhí)行module文件、以及支持多種格式的module的事情。而我們上面這段代碼就是commonjs最精簡的表達(dá),有了它,我們就可以使用common.js的方式來組織代碼了。
注意,代碼中的deps變量完全就是無用的,只是我覺得這樣寫的話,似乎更清晰一點(diǎn)。
define 'add', [], (require, exports, module)->
module.exports = (a, b)->
a + b
define 'addTwice', ['add'], (require, exports, module)->
add = require 'add'
exports.addTwice = (a, b)->
add add(a, b), b
use ['addTwice'], (require, exports, module)->
addTwice = require 'addTwice'
cosnole.log "#{2} + #{3} + #{3} = #{addTwice 2, 3}"
上面的代碼展示了如何使用這個(gè)commonjs運(yùn)行時(shí),很簡單,有木有?
很簡陋?確實(shí),我們只是用用它來組織代碼,最終實(shí)現(xiàn)bodule.js這個(gè)復(fù)雜的commonjs運(yùn)行時(shí)。
我們改從何入手編寫一個(gè)加載器呢,既然已經(jīng)有了規(guī)范和接口,那我們從接口寫起吧。
define 'bodule', [], (require, exports, module)->
Bodule =
use: (deps, factory)->
define: (id, deps, factory)->
package: (conf)->
module.exports = Bodule
use ['bodule'], (require, exports, module)->
Bodule = require 'bodule'
window.Bodule = Bodule
window.define = ->
Bodule.define.apply Bodule, arguments
更多建議: