編寫自己的模塊加載器

2020-04-26 10:29 更新

自己實(shí)現(xiàn)一個(gè)模塊加載器——bodule.js

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è)概念組成:

  • url,一個(gè)url地址對應(yīng)一個(gè)模塊;
  • meta module:如下形式為一個(gè)meta module:

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。

  • 一個(gè)模塊文件包含一個(gè)或多個(gè)meta module,但是,在該模塊文件中,必須包含一個(gè)該模塊文件url作為id的meta module,例如:

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)行配置。

bodule cloud

在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

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。

bodule.js的API

.use

.use(id)

在頁面中使用一個(gè)模塊,相當(dāng)于node id.js。

.use(dependancies, factory)

在頁面上定義一個(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>

define

define(id, dependencies, factory)

定義一個(gè)meta module;

define(id, anythingNotFunction)

定義一個(gè)meta module,該模塊的exports即為anythingNotFunction;

幾個(gè)例子:d.js,e.jsbackbone.js

.package(config)

配置模塊和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

coffeescript是一門非常有趣的語言,敲起代碼來很舒服,不會被JavaScript各種繁瑣的細(xì)節(jié)所煩擾。所以我打算使用它來實(shí)現(xiàn)bodule.js。訪問[coffeescript.org],上面有簡潔文檔,如果你熟悉JavaScript,我相信你能很快掌握CoffeeScript的。

commonjs運(yùn)行時(shí)

從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í)。

bodule API

我們改從何入手編寫一個(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


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號