天天看點

Dojo loader 文檔 <3>

Dojo 最強大的一個庫,從企業級應用到Mobile上的使用,庫的大小也可以根據需求來定制(比Jquery小)。因為它的全面,很适合于我們了解他的源代碼而提升自己的能力。

下載下傳的Dojo的源代碼,本來想着直接學習Dojo.js 這個加載器源代碼,但發現很難看懂,還是先從Dojo要實作的功能及其機制,在帶着如果讓自己去實作這些功能,去看源代碼,可能會更好的掌握。

看本文檔的時候還是遇到很多不懂的地方,是以才去寫了 dojo loader的入門使用及進階使用

源英文文檔http://dojotoolkit.org/reference-guide/1.9/loader/amd.html#id2

介紹

Dojo loader 包括兩部分的API:

  1. 遵循AMD規範的API(“AMD API”), 如require([‘dojo/dom’],function(dom){})
  2. 相容dojo 1.7以前的加載方法的API, 如dojo.require(), dojo.provide();

AMD API 是在1.7版本才出現的, API的實作跟requirejs, curl及bdload是一樣的。是以dojo可以不用使用自己的加載器,而直接用requirejs來加載它的核心。 Commonjs 是javascript代碼子產品化的标準,可以提升代碼的移值性及互操作性。Commonjs主要應用于伺服器端,比如nodejs. 如 var A=require(‘A’); A.hello(); 因為用戶端因為網絡加載的原因,不知道js何時加載完成,是以在commonjs的基礎上提出了AMD規範, require([‘A’],function(A){ A.hello()}); 當 A.js檔案加載完了之後,在調用回調函數。 AMD是一個異步加載的規範,它比同步加載有兩點好處:

  1. 子產品可以同時通過異步來加載,而同步加載是一個個的加載進來,是以速度是可以提升10倍。
  2. 調試上的改進,不在使用debugAtAllCosts來調試程式

遵循 AMD的規範的loader都會提供一系列擴充功能,但他們的核心功能是相容的,而且教程大部是通用的。

除了loader的核心API外, 新的Dojo loader還提供了以下特點:

  • 多平台支援: 預設的,Dojo loader包含浏覽器端,node.js 及 rhino的配置
  • Has.js API: Dojo loader實作了has.js的API, 使用這個api 可以排除一些不需要的功能, 結合 dojo build工具,可以将整個loader的代碼縮小到3kb(壓縮及gzip過後)
  • 配置API: 加載器包括一個可以由用戶端應用程式配置API.

Dojo.js

在 1.7版本前, dojo.js 是加載器,也是整個架構應用的啟動程式,現在的loader得到改進,隻具有加載功能。實院上,現在的loader也可以加載非dojo庫的檔案。為了向前相容,loader 預設是以同步的方式運作, 它會自動加載 dojo的核心庫檔案

<script src="path/to/dojo/dojo.js"></script>
<script>
  // the dojo base API is available here
</script>
           

為了執行AMD模式, 可以設定配置項 async 為ture.

<script data-dojo-config="async:1" src="path/to/dojo/dojo.js"></script>
<script>
  // ATTENTION: nothing but the AMD API is available here
</script>
           

請注意您在dojo被加載完之前設定async,才會進入AMD mode, 這樣dojo或其它的庫才不會自動加載進來,在AMD模式下,要加載的js檔案完全取決于應用程式需要哪個子產品或庫.

配置及特征檢測

配置

這裡有三個方法将配置的參數傳遞給loader.

  1. 在loader被定義之前,通過全部對象dojoConfig。
  2. 在<script src=”path/loader”> 裡面,對過data-dojo-config參數
  3. 在加載完loader後,通過全局的require方法。

配置資料通常是javascript 标準對象,例{ a:’a’}. 當對象被作為data-dojo-config的屬性傳遞時,{}方括号将被省略。 以下是data-dojo-config的使用示例:

<script
  data-dojo-config="async:true, cacheBust:new Date(), waitSeconds:5"
  src="path/to/dojo/dojo.js">
</script>
           

使用全局配置對象dataConfig:

<script>
  var dojoConfig = {
    async:true,
    cacheBust:new Date(),
    waitSeconds:5
  };
</script>
<script src="path/to/dojo/dojo.js"></script>
           

通過require函數,如下:

require({
  cacheBust:new Date(),
  waitSeconds:5
});
           

注意:async以及has.js測試都必須在loader加載前定義, 不能在require函數裡面設定。即隻能通過dataConfig 或者data-dojo-config對象。

為了向之前的版本相容,被棄用的變量djConfig 可以被用來替換dojoConfig. 在這有一點需要注意,當dojoConfig及djConfig被同時定義, djConfig會被忽略。同樣也要注意的時,當dojoConfig或者djConfig被定義但同時存在data-dojo-config屬性,data-dojo-config的優先級高于前者。

配置對象可以用來設定任意的,應用程式指定的資料。 所有的配置對象的屬性會被拷貝到require.rawConfig;  當它們的值可以被loader承認,則被添加到require.config. 因為整個過程是淺拷貝, 如果你需要混合屬性對象的子對象而不是完全取代,loader 會包含一個”config” 事件,當配置資料被接受會通過内部的微事件API觸發。

“config” 事情會将以下兩個參數傳遞給監聽器:

  1. config: 配置對象傳遞給加載器時,加載器觸發” config” event.
  2. rawConfig: require.rawConfig的值

特征檢測

Has.js 有一套标準的 API, 允許特征檢測從相關的代碼中分離開來(如下的,隻需要修改has.add裡的測試,而不用在調用這個測試的地方修改, 這就是分離),并且可以使得build system 建立一套合适的dojo版本以适合你的應用程式。

以下是一個測試的例子,通過 has.add()添加測試方法,然後在使用它。

has.add("dom-addeventlistener", !!document.addEventListener);

if(has("dom-addeventlistener")){
  node.addEventListener("click", handler);
}else{
  node.attachEvent("onclick", handler);
}
           

在build的時候,通過一個适當的描述檔案, 這段代碼翻譯成

0 && has.add("dom-addeventlistener", !!document.addEventListener);

if(1){
  node.addEventListener("click", handler);
}else{
  node.attachEvent("onclick", handler);
}
           

當翻譯完的代碼在傳遞給 js壓縮工具,删除沒用的代碼,最後的輸出為

node.addEventListener("click", handler);
           

當這些技術被應用到大量的這類選擇分支代碼,會省下很多代碼空間。省下的空間對于帶寬和緩存有限的移動開發環境來說非常重要。

以下是Dojo實作的has.js的has, 這在标準的has.js 規範裡面是沒有的。

  • 測試的結果會緩存在 has.cache
  • 函數has.add 包含第四個可選參數, force.  将會被用來重寫已經存在的測試(正常的, 第一次定義的值一直使用,借一個常量)

Dojo loader 在初始化幾個測試并緩存。 dojoConfig 及 data-dojo-config 可以重寫這些緩存結果。同時任何配置對象可以通過has配置屬性,來添加更多的測試。例如:

<script>
  var dojoConfig = {
    has: {
      "config-tlmSiblingOfDojo":0,
      "myApp-someFeature":1
    }
  };
</script>
           

一個測試也可以被定義為一個函數, 當這個特征第一次被has請求時,函數會被執行,并傳回值。

<script>
  var dojoConfig = {
    has: {
      "myApp-someFeature":function(){
        return !!document.addEventListener;
      }
    }
  };
</script>
           

由于has 測試的使用類似于配置對象的變量,是以 loader 的配置API會将has.add應用在它接受的配置變量上。并在這些變量上加上字首 “config-“,例如:

<script data-dojo-config="tlmSiblingOfDojo:0" src="path/to/dojo/dojo.js"></script>
           

上面這段代碼的結果為has測試的名稱為config-tlmSiblingOfDojo, 值為0;

選項及特點

下表中的例出的可選項會在loader内部使用。第一列是在loader内定義的選項, 第二列辨別這個特征是否被偵測到(通過has.add)或 這僅僅是一個選項及這個選項的預設值。 在這個沒有經過build的源檔案裡面,所有的這些特征及選項都是有效的, 如果loader 被build後,會設定成為staticHasFeatures, 而且不能在被配置。

Feature Default Value Description
dojo-trace-api True
dojo-sync-loader True
dojo-config-api True
dojo-cdn False
dojo-requirejs-api False
dojo-test-sniff True
dojo-combo-api False
dojo-undef-api False
config-tlmSiblingOfDojo True
config-dojo-loader-catches True
dojo-inject-api True
config-stripStrict False
dojo-timeout-api True
dojo-log-api True
dojo-amd-factory-scan True
dojo-publish-privates True
dojo-built False
dojo-loader True
host-node Detected Environment is running on the the NodeJS platform
host-rhino Detected Environment is running on the Rhino platform
dojo-xhr-factory Detected
dojo-force-activex-xhr Detected Force XHR provider to use ActiveX API (MSXMLHTTP).
native-xhr Detected Browser has native XHR API, XMLHttpRequest.
dojo-gettext-api Detected Dojo provides API for retrieving text resource contents from a URL.
dojo-loader-eval-hint-url Detected Module location should be used as source hint during eval rather than module identifier.
ie-event-behavior Detected Browser supports legacy IE event behaviour API (attachEvent versus attachEventListener).

AMD API

AMD API 是 dojo的首選API(dojo.require應該為次要的),通過兩個全局函數require和defiine暴露, 這兩個函數都為異步的。 當運作的是之前的loader API模式, AMD會以同步的方式運作。

Require 可以用來配置loader 及加載AMD 子產品。以下是它的方法簽名

require(
  configuration, // (optional; object) configuration object
  dependencies,  // (optional; array of strings) list of module identifiers to load before calling callback
  callback       // (optional; function) function to call when dependencies are loaded
) -> undefined
           

如果提供了configuration 對象,它會被傳遞給configuration API. 接下來,如果傳遞了依賴關系,依賴的子產品會被加載。 最後,如果傳遞了依賴關系,加載的依賴子產品會被傳遞給callback.

require([ "my/app", "dojo" ], function(app, dojo){
  // do something with app and dojo...
});
           

這裡也有别種使用方法, 它有以下方法簽名

require(
  moduleId // (string) a module identifier
) -> any
           

當指定的moduleId确定被加載的情況下,才使用這個方法。 它将傳回被請求的Module. 如果被請求的module沒有被加載完成,會抛出一個錯誤。 這種方法不被推薦使用,因為它将導緻 程式的依賴混亂。

Define函數類似于require. 主要用于定義一個AMD 子產品。它的方法簽名如下

define(
  moduleId,      // (optional; string) an explicit module identifier naming the module being defined
  dependencies,  // (optional; array of strings) list of module identifiers to load before calling factory
  factory        // (function or value) the value of the module, or a function that returns the value of the module
)
           

如果factory 是一個函數, 被定義的子產品的值為這個函數的傳回值。 否則, 這個子產品的值為 factory. 不管factory是不是一個函數, 依賴(dependencies)都會在子產品定義前解析。

如果僅僅有factory,并且factory 是一個函數,那麼它是一個沒有依賴的子產品.  **

moduleId 可以不用被提供,這個參數是早期手動給AMD loader添加的。隻要這裡有一個子產品恰好被定義,loader将會從給定的依賴清單中,自動産生一個正确的子產品辨別,并且促使子產品被加載。例如, 當使用require([‘matthLIb/arithmetic’]), loader 知道被加載的子產品的辨別為 “mathLib/arithmetic”.

也有可能加載的依賴角本根本就沒有通過define來定義, 這将導緻解析後的值将會是undefined. 

Define 擁有兩個額外重要的特性,可能不會立即顯示

  • 子產品的建立是懶惰和異步的,不會在調用define時立即運作。這個意思就是factory 不會被執行。并且依賴的子產品也不會下載下傳并解析。直到有運作的代碼需要這個子產品。
  • 一旦一個子產品的值進入子產品空間,它将隻會計算一次,而不會每次請求而進行重新計算。在實踐層面上, 這個意思是factory隻會被調用一次,運算完後傳回的值會被緩存并共享給所有請求隻子產品的代碼使用。(注意:dojo loader 有一個非标準的函數require.undef, 表明沒有傳回值);

Require函數的Dependencies和callback參數恰恰如同define中的dependencies和factory參數。 例如

require(
  ["dijit/layout/TabContainer", "bd/widgets/stateButton"],
  function(TabContainer, stateButton){
    // do something with TabContainer and stateButton...
  }
);
           

Define中的如下

define(
  ["dijit/layout/TabContainer", "bd/widgets/stateButton"],
  function(TabContainer, stateButton){
    // do something with TabContainer and stateButton...
    return definedValue;
  }
);
           

兩者都可以獲得dijit/layout/TabContainer和bd/widgets/stateButton兩個子產品的值。兩者的不同在于, 後者會不會立即調用依賴的包,而且會傳回一個值給它的調用者。前者就是簡單的調用并運作。

子產品辨別符

子產品辨別符看起來很像系統路徑(例如:”dijit/form/Button”). 這些辨別符會被規範化為決對路徑(浏覽器)或者檔案路徑(伺服器端), 然後loader才能查找并加載這些子產品的源檔案。

下面的配置變量可以控制加載器如何将子產品辨別符映射為URL:

  • baseUrl:(string) 預設指定的根目錄為dojo.js所在的目錄,如”lib/dojo/dojo.js” 則baseUrl為 lib/dojo/. 在require([‘dojo/dom’]) 下, baseUrl應該指定為lib/dojo. 如果被定義, has的config-tlmSiblingOfDojo為false.否則為true.
  • Paths: (object). 自定義路徑。會将子產品辨別符的一部分映射到路徑的一部分。 指定的越詳細,優先級越高,如,’a/b/c” 的優先級高于a及’a/b’.
dojoConfig={
	paths:{
	‘a/b’:”./custom”
}
}
Require([‘a/b/dom’]) 将會加載custom下的dom.js檔案。
           
  • Aliases: (object). 将子產品辨別符映射為另一個子產品辨別符。如:
require({
  aliases:[
    ["text", "dojo/text"]
  ]
});
require(["text"], function(text){ //...
require(["dojo/text"], function(text){ //...
define(["text"], function(text){ //...
define(["dojo/text"], function(text){ //...
           
  • Packages: (package 對象資料) 明确定義的包清單。Dojo及dijit是兩個packages的列子。 一個包對象包含四個屬性:
Name: (string) 包名字。(例:’myapp’);
	Location: (string) 包存在的路徑;
	Main: (可選,string) 包的入口檔案,給定的值為一個子產品辨別符。
{  
name: 'dijit',  
location: 'dojo/dijit',  
main:'lib/main'                     
}
	
           
  • packageMap(可選,object) 一個映射關系,允許在這個包内,指定的包可以映射到别外一個包;(這個隻在dojo的loader裡面存在), 如下例,在utill1的包中,dojox包,指向dojox1. 已被棄用,用map代替
{name: "util1",
 location: "packages/util1",
  packageMap: {dojox:"dojox1"}
           

子產品辨別符的相對關系

在一個子產品定義時的 依賴數組裡面的子產品辨別符,可以給出全局路徑,可以是相對路徑。例如:

// this is "myPackage/myModule/mySubmodule"
define(
  ["myPackage/utils", "myPackage/myModule/mySubmodule2"],
  function(utils, submodule){
    // do something spectacular
  }
);
           

可以寫成以下方式:

// this is "myPackage/myModule/mySubmodule"
define(
  ["../utils", "./mySubmodule2"],
  function(utils, submodule){
  // do something spectacular
  }
);
           

“.” 可以被認為是目前的子產品的檔案夾, “..” 目前子產品的父檔案夾。

注意: 這種相對的子產品辨別隻能用于單個包内。 也就是說,”../” 不能用于頂級的子產品辨別。是以上面的例子, “../../someOtherPackage/otherModule” 将是無效的。

在一個包的内部子產品之間,這種方式是重點推薦。因為使用全稱的時候可能會具有相同名字的兩個不同的包(或者兩個不同版本的同一個同)。 這将會在rolocating module namespaces有更具體的解釋

上下文相關的 require

讓我們子產品引用的最後一個細節。假設我們需要一個功能,這個功能依賴于程式的流程,需要一個條件才會去require或執行特定的代碼。 例如

// this is "myApp/topLevelHandlers"
define(["dojo"], function(dojo){
  dojo.connect(dojo.byId("debugButton"), "click", function(){
    require(["myApp/perspectives/debug"], function(perspective){
      perspective.open();
    });
  });
});
           

這段代碼是合法的。 但可以優化的更好。由于這段代碼在”myApp/topLevelHandlers”子產品内。我們可以重寫”./perspectives/debug” 來代替”myApp/perspectives/debug”.  可惜, 全局的require 函數是不知道如何去引用這個參照子產品(參照子產品的解釋是require(“dojo/dom”) 而dom裡面有需要依賴”./sniff”, 當在loader 在加載sniff裡,referencesModule指向的就是dom 子產品)。是以如果我們嘗試去改變辨別符,會導緻失敗。我們需要一種方式能夠記住這個子產品供以後使用。我們需要指定子產品辨別符require在 依賴資料裡.

// this is "myApp/topLevelHandlers"
define(["dojo", "require"], function(dojo, require){
  dojo.connect(dojo.byId("debugButton"), "click", function(){
    require(["./perspectives/debug"], function(perspective){
      perspective.open();
    });
  });
});
           

現在的調用require方法是在本地的方法,而不是全局的. Loader 安排本地的require去解析子產品辨別符。 這種本地的require函數被稱為” 上下文 require, context-sensitive require”.

一般的javascript角本注入

一個明确的路徑或者URL代表的javascript可以作為子產品辨別符傳遞 給require函數。假若這樣,角本會被簡單的執行,并且返加一個undefined的值。 例如:

require(["http://acmecorp.com/stuff.js"], function(){
  // etc.
});
           

Loader的解釋器會當下列情況作為普通的角本辨別:

  • 以協定開送的string(如: http 或 https)
  • 以 斜線/ 開頭的字元串(如”/acmecorp.com/stuff”)
  • 以  .js 結尾的字元。

子產品别名

有可能為一個子產品創造一個别名,有一個例子,當通過load加載了一個文本資源, 整個應用程式需要做用到這個公用的text插件。RequireJS最先定義子產品别名,而其它的庫都是依賴于RequireJS定義的子產品。 Dojo的實作這個功能, 跟 requires 的實作100%相容, 并且更小,更多的功能。下面是dojo text module 的别名:

require({
  aliases:[
    ["text", "dojo/text"]
  ]
});
           

現在,當指定一個子產品辨別符text.  Loader将會 解析”dojo/text”. 換句話說, 在給定了上面的配置之後, 下面語句的傳回的結果是一樣的。

require(["text"], function(text){ //...
require(["dojo/text"], function(text){ //...
define(["text"], function(text){ //...
define(["dojo/text"], function(text){ //...
           

有一種情況需要優先考慮别名。 兩個不同絕對子產品辨別符( 在子產品辨別符處理的第6步之後)将會導緻兩個不同的子產品被執行個體化,即使解析到的是相同的路徑。 這個意思是你不能使用paths 來解決這個問題。 例如 , baseUrl 指向dojo檔案例, 你不能通過以下方式,給 "dojo/text" 取别名為 "text", 如下:

require({
  paths:{
    "text":"./text"
  }
});
           

在這種情況下, 假設沒有相關的子產品引用(隻是簡單的考慮這個問題). "text" 被解析為 "path/to/dojo/text.js". ("text", "path/to/dojo/text.js")(經過paths定義了"text"),  而正常的"dojo/text" 會解析為("dojo/text", "path/to/dojo/text.js"),兩者的mid不同,但是它們指向相同的路徑 。 是以loader 還是會建立兩個獨立的子產品執行個體, 這可能是你不想要的。 為給兩個不同的子產品辨別符被解析到為同一個子產品值,唯一的方法是在define時指定明确的ID, 或者提供一個别名配置。

解析子產品辨別符(源代碼為getModuleInfo_函數)

下面的步驟主要概述loader内部處理辨別符的過程, 處理過程會涉及到子產品辨別ID, 特定情況的上下文require, reference module(引用子產品:如在dom.js裡面, define(["./sniff","./_base/window"], 當要加載dom子產品時,需要先加載sniff及 _base/window, 那麼在這時相對于sniff時,引用子產品就是dojo/dom, 而dom的引用子產品是全局函數require,那麼引用子產品為0), 以及産生一個結果路徑或URL(傳回一個結果)

  1. 如果moduleId 協定或者斜線開台,又或者以”.js”結尾。假設該請求的是任意數量的javascript代碼,而不是一個子產品。在這種情況下,剩下的步驟會直接跳過。 
  2. 如果moduleId是一個相對的(例如,第一個位元組為”.”),但沒有給定reference module。會抛出一個錯誤:moduleId is not resolvable. 子產品無法解析。(注意:源代碼裡把這種情況歸類到第一步,當以”.”開頭,而沒有referenceModule, 會直接相對于網頁的路徑去加載moduleId. 如果沒有moduleId這個路徑,會直接找不到相應檔案錯誤。), 是以在全局require中是不能使用相對子產品,相對子產品主要用于包内的子產品定義或者上下文。
  3. 如果moduleId是相對的,而且給定了參考子產品, 設定moduleId為 referenceModule + “/../”+ moduleId, 并且通過compactPath函數删除所有相對的路徑,如 “..”, “.” ; 以過compactPath處理後,moduleId不會在有相對路徑的片段。當經過compactPath處理後,還有 “.” 辨別,會抛出一個 moduleId不能解析的錯誤, 源代碼是  makeError("irrationalPath", mid) .
  4. 如果給定了referenceModule, 而且referenceModule是packages配置變量下其中的一個包内, 并且那個包也是packageMap 配置變量的一項,那麼用在packageMap中的包名替換最左名的部分(第一個”/”前面那一段,即包名), 主要用于多版本的相容, 可以檢視一下 loader 進階使用教程下中的map選項。
  5. 在配置變量aliases 中查找 第3步計算出來來的moduleId, 如果這個moduleId存在别名, 從aliases中獲得這個moduleId得到的别名,重新計算(調用getModuleInfo_),  很重要一點是, aliases是在packageMap生效之後在引用的。
  6. 如果moduleId 僅有一部會(沒有 "/"), 并且這部分與packages内的包名稱相同。 在moduleId末尾添加一個"/" 和 包配置中的 "main" 配置變量的值(例如:moduleId 為 "dojo" 那麼而這個真正的子產品為 "dojo/main")。

目前為此,loader 已經将moduleId 完成統一為了一個絕對子產品辨別符(也就是說, reference module 不在影響絕對辨別符) 7.  從moduleId(a/b/dom)的開頭部分開始比對paths{"a/b":"./custom","a":"./custom"}中最長的部分(即比對"a/b",而不是"a").  如果比對成功, 将paths中正确的值替換moduleId中正确的值('a/b'會 被替換為 8. 如果在第7 步沒有比對到paths 并且moduleId  引用的是一個包中的子產品。 相關包的location屬性會替換 moduleId的第一部分(包名), 如"dojo/dom", dojo包位于 ./lib/js/dojo/,那麼結果 為./lib/js/dojo/dom. 9.  如果第7與第8步都沒有比對成功, 并且有(”config-tmlSiblingOfDojo") 為 true. 配置屬性tmlSiblingOfDojo 預設為true. 這時結果為 "../" +moduleId。 意思就是不指定packages及path時,路徑 為 loader檔案 dojo.js的父路徑(如果dojo.js 存在于, js/lib/dojo, 那麼 ../ 為 js/lib), 更多可以檢視 loader的進階使用教程中的tmlSiblingOfDojo. 10.  如果result不是一個絕對路徑, 那麼在它之前添加配置變量baseUrl的值  11. 給result 添加.js 字尾

解析子產品辨別符例子

在所有這些例子中, 假設的使用的都是預設配置. 

dojo

dojo ⇒ dojo/main (Step 6)
dojo/main ⇒ ./main (Step 8)
./main ⇒ path/to/dojo/ + ./main ⇒ path/to/dojo/main (Step 10)
path/to/dojo/main.js (Step 11)
           

dojo/store/api/Store

dojo/store/api/Store ⇒ ./store/api/Store (Step 8)
./store/api/Store ⇒ path/to/dojo/ + ./store/api/Store ⇒ path/to/dojo/store/api/Store (Step 10)
path/to/dojo/store/api/Store.js (Step 11)
           

../../_base/Deferred with reference module dojo/store/util/QueryResults

../../_base/Deferred ⇒ dojo/store/util/QueryResults + /../ + ../../_base/Deferred ⇒
dojo/store/util/QueryResults/../../../_base/Deferred ⇒ dojo/_base/Deferred (Step 3)
dojo/_base/Deferred ⇒ ./_base/Deferred (Step 8)
./_base/Deferred ⇒ path/to/dojo/ + ./_base/Deferred ⇒ path/to/dojo/_base/Deferred (Step 10)
path/to/dojo/_base/Deferred.js (Step 11)
           

myApp

myApp ⇒ ../myApp (Step 9)
../myApp ⇒ path/to/dtk + ../myApp ⇒ path/to/myApp (Step 10)
path/to/myApp.js (Step 11)
           

myApp/someSubmodule

myApp/someSubmodule ⇒ ../myApp/someSubmodule (Step 9)
../myApp/someSubmodule ⇒ path/to + ../myApp/someSubmodule ⇒ path/to/myApp/someSubmodule (Step 10)
path/to/myApp/someSubmodule.js (Step 11)
           

請注意, 假設的baseUrl預設值為dojo的樹路徑(js/lib/dojo/js, 庫路徑為js/lib/dojo), 那麼頂級子產品辨別符"myApp" 就跟dojo 樹路徑在一個檔案夾下。就是“ tmlSiblingOfDojo” 這個文字暗示的一個。 

如果myApp的根目錄(整個應用程式的檔案夾)位于"other/path/to/myApp", 這時候需要提供一個paths的配置:

var dojoConfig = {
  paths:{
    "myApp":"/other/path/to/myApp"
  }
};
           

由于 "other/path/to/myApp" 是一個絕對路徑(絕對也是相對于index.html), 第10步不會發生。

myApp

myApp ⇒ /other/path/to/myApp (Step 7)
/other/path/to/myApp.js (Step 11)
           

myApp/someSubmodule

myApp/someSubmodule ⇒ /other/path/to/myApp/someSubmodule (Step 7)
/other/path/to/myApp/someSubmodule.js (Step 11)
           

paths也是可以映射到路徑的相對部分上, 例如, 你有以下的檔案結構:

scripts/
  dtk/
    dojo/
    dijit/
    dojox/
  myApp/
  experimental/
           

在這個情況下, myApp 不在是dojo的姐妹目錄, 但是它依然可以自動獲得到baseUrl,指向script/dtk/dojo。 如果給myApp指定一個相對于baseUrl的路徑,它依然是有效的:

var dojoConfig = {
  paths:{
    "myApp":"../../myApp"
  }
};
           

作為結果如下:

myApp

myApp ⇒ ../../myApp (Step 7)
../../myApp ⇒ path/to/dtk/dojo/ + ../../myApp ⇒ path/to/myApp (Step 10)
path/to/myApp ⇒ path/to/myApp.js (Step 11)
           

myApp/someSubmodule

myApp ⇒ ../../myApp/someSubmodule (Step 7)
../../myApp/someSubmodule ⇒ path/to/dtk/dojo/ + ../../myApp ⇒ path/to/myApp/someSubmodule (Step 10)
path/to/myApp/someSubmodule ⇒ path/to/myApp/someSubmodule.js (Step 11)
           

這是一個覆寫tlmSiblingOfDojo的行為。 另一個方法是設定tlmSiblingOfDojo 為false 或者明确指定baseUrl.  假設我們依然采用以上的檔案結構, 考慮 這個配置:

var dojoConfig = {
  baseUrl:"scripts",
  packages:[{
    name:'dojo',
    location:'dtk/dojo'
  },{
    name:'dijit',
    location:'dtk/dijit'
  }]
}
           

注意我們不在需要路徑映射了,因為我們設定的baseUrl時, tlmSiblingOfDojo被設定為了false. 辨別符現在都是直接相對于baseUrl. 

myApp

myApp ⇒ scripts/ + myApp ⇒ script/myApp (Step 10)
scripts/myApp ⇒ scripts/myApp.js (Step 11)
           

myAPp/someSubModule

myApp ⇒ scripts/ + myApp/someSubmodule ⇒ script/myApp/someSubmodule (Step 10)
scripts/myApp/someSubmodule ⇒ scripts/myApp/someSubmodule.js (Step 11)
           

dojo

dojo ⇒ dojo/main (Step 4)
dojo/main ⇒ dtk/dojo/main (Step 8)
dtk/dojo/main ⇒ scripts/dtk/dojo/ + ./main ⇒ scripts/dtk/dojo/main (Step 10)
scripts/dtk/dojo/main.js (Step 11)
           

dojo/behavior

dojo/behavior ⇒ dtk/dojo/behavior (Step 8)
dtk/dojo/behavior ⇒ scripts/dtk/dojo/ + ./behavior ⇒ scripts/dtk/dojo/behavior (Step 10)
scripts/dojo/behavior.js (Step 11)
           

如果我們指定myApp為了下包,"myApp"的解析過程将會改變:

var dojoConfig = {
  baseUrl:"scripts"
  packages:[{
    name:'myApp',
    location:'myApp'
  },{
    name:'dijit',
    location:'dtk/dijit'
  },{
    name:'dijit',
    location:'dtk/dijit'
  }]
};
           

"myApp/someSubModule" 不什麼改變,但 'myApp' 的解析如下 myApp

myApp ⇒ myApp/main (Step 4)
myApp/main ⇒ myApp/main (Step 8)
myApp/main ⇒ scripts/ + myApp/main ⇒ scripts/myApp/main (Step 10)
scripts/myApp/main.js (Step 11)
           

配置packages通常要比将直接将檔案夾與一推頂級子產品混合在一起好。

通常,你可以在任何地方映射一個子產品辨別符。 例如,你可能體驗下将新子產品來代替  dojo/coookie.  這種情況下,你希望所有的dojo子產品都正常使用, 隻是想dojo/cookie 映射為scripts/experimental/dojo/cookie。 為了實作這個目的,需要在配置選項中 paths添加一條目錄:

var dojoConfig = {
  paths:{
    "dojo/cookie":"../../experimental/dojo/cookie"
  }
}
           

現在,第7步中 dojo/cookie會被特别對待,将會映射到 scripts/experimental/dojo/cookie.

最後,考慮下當你想映射的子產品辨別符正好是一些子產品的父路徑部分, 考慮以下這個結構

scripts/
  myApp/
    myApi.js
    myApi/
      helper1.js
      helper2.js
           

一方面, "myApp/myApi是一個子產品, 但它同樣的子產品myApp/myApi/helper1與 myApp/myApi/helper2的父路徑部分。 這個意思是paths中的條目"myApp/myApi":"path/to/another/myApi" 也同樣會被映射到兩個helper子產品。 很多時候, 這是你想要的,但如果不是,你可以直接把helpers子產品添加到paths中。

var dojoConfig = {
  paths:{
    "myApp/myApi":"path/to/another/myApi",
    "myApp/myApi/helper1":"path/to/original/myApi/helper1",
    "myApp/myApi/helper2":"path/to/original/myApi/helper2"
  }
}
           

看起來很繁瑣,但你很少會使用到這種配置。

重定位子產品命名空間

如果你想在同一時間内用同一個名字來使用兩個包, 隻要包的作者遵循最佳規範 并且在調用define 函數時沒有明确指定moduleId, 你可需要簡單的将兩個包放到不同的檔案夾下, 然後在packages中給每個檔案夾指定一個包的名稱。 例如:

var dojoConfig = {
  baseUrl: "./",
  packages: [{
      name: "util1",
      location: "packages/util1"
    }, {
      name: "util2",
      location: "packages/util2"
    }]
};
           

現在你可以通過require 或 define正常通路這兩個包:

define(["util1", "util2"], function(util1, util2){
  // well that was easy.
});
           

另外有的包需要使用到其它包,那麼就需要對其它包進行重映射。 例名:

var dojoConfig = {
  packages: [{
    name: "util1",
    location: "packages/util1",
    packageMap: {dojox:"dojox1"}
  }, {
    name: "util2",
    location: "packages/util2",
    packageMap: {dojox:"dojox2"}
  }, {
    name: "dojox1",
    location: "packages/dojox-version-1-6"
  }, {
    name: "dojox2",
    location: "packages/dojox-version-1-4"
  }]
};
           

上面的代碼會確定所有在"util1"中顯示引用"dojox"的包會被轉向到 "dojox", 而所有在 "util2"中引用"dojox"的包會被轉向的“dojox" (注, packageMap已棄用,請用map 代替)

var dojoConfig = {
  packages: [{
    name: "util1",
    location: "packages/util1",
    
  }, {
    name: "util2",
    location: "packages/util2",
   
  }, {
    name: "dojox1",
    location: "packages/dojox-version-1-6"
  }, {
    name: "dojox2",
    location: "packages/dojox-version-1-4"
  }],
  map:{
	util1:{
		dojox:"dojox1"
	},
	util2:{
		dojox:"dojox2"
	}
	}
};
           

這種設計取代了 在 dojo v.16中所謂的的多版本設計 同時也消除了在RequireJs 實作需要的上下文。 記住,不同于 多版本設計(multi-version).  Build不能部署一個遷移包。 隻是通過簡單的配置來應對所有的問題。 這個功能雖然強大, 但隻有dojo實作了這個功能。

實作功能

dojo 的AMD API 包括了一些實用功能:

require.toUrl  它像解析一個子產品辨別符為一個路徑一樣,來獲得一個資源的路徑。

require.toUrl(
  id // (string) a resource identifier that is prefixed by a module identifier
) -> string
           

舉個例子, 比如,我們已經定義了一個配置,這個配置将會使子產品模識符"myApp/widgets/button" 指向到資源 "http://acmeCopy.com/myApp/widgets/button.js", 在此情況下, require.toUrl("myApp/widgets/templates/button.html") 将會傳回 "http://acmeCopy.com/myApp/widgets/templates/button.html". 

當 require是一個 content-sensitive require時,ids可以是相對的辨別符。 如:

define(["require", ...], function(require, ...){
     ... require.toUrl("./images/foo.jpg") ...
}
           

請注意URL是以"./"開頭。

require.toAbsMid  将給定的子產品ID轉化為一個絕對的子產品Id, 這個函數隻有結合context-sensitive require使用時,才能有用。

require.toAbsMid(
  moduleId // (string) a module identifier
) -> string
           

require.undef  從子產品命名空間上删除一個子產品。 require.undef 主要用于測試架構,測試時不需要重新加載整個架構,而隻加載和解除安裝個别子產品。

require.undef(
  moduleId // (string) a module identifier
) -> undefined      

require.log 等介于目前環境下的console.log,  每一個傳遞的參數,會單獨輸出到一行。

require.log(
  // (...rest) one or more messages to log
) -> undefined
           

require.toAbsMid 及 require.undef  都是 dojo特有的功能,擴充了AMD 規範。

Commonjs 的 require, exports, module

AMD 規範中定義了三個特别的 子產品辨別符: require(用于上下文中的require), exports(循環依賴時使用) 以及module。 

require 子產品的使用可參考 Context-sensitive require

module 子產品傳回一個包含以下屬性的一個對象:

  • id:  一個唯一的子產品辨別符字元串, 當它被傳遞給require時,會傳回這個子產品的值。
  • uri: 子產品資源被加載完後的一個URI(有時沒用)
  • exports: 詳情如下

exports 子產品以及module.exports 提供别外一種方法來定義一個子產品的值。 代替了子產品工廠函數的傳回值。 exports 提供了一個對象,可以給這個對象添加任意屬性。 例如, 以下兩個子產品的定義完成相同:

define([], function(){
  return {
    someProperty:"hello",
    someOtherProperty:"world"
  };
});


define(["exports"], function(exports){
  exports.someProperty = "hello";
  exports.someOtherProperty = "world";
});
           

當在循環依賴時, 為了確定子產品被正确定義,唯一的方法就是使用exports對象,并将需要對外爆露的方法或資料添加到export對象上。

如果需要,module.exports 可以完成取代export子產品的使用:

define(["module"], function(module){
  module.exports = dojo.declare(/*...*/);
});
           

最後,AMD 規範定義, 當define隻提供一個工廠函數時, loader 必須像有["require", "exports","module"]這樣的依賴數組,換句話說,以下兩個定義是相等的。

define(["require", "exports", "module"], function(require, exports, module){
  // define a module
});

define(function(require, exports, module){
  // define a module
});
           

後者,在require("foo")形式的函數内部将使用 "foo"作為依賴來掃描,并解析。

所有的這些功能主要是相容其它CommonJS的子產品。 你不應該使用這些功能,除非你需要寫一些特定的子產品(node.js). 而又不想要求使用者去加載一個相容AMD模範的加載器。 當然如果你需要解決循環依賴時,exports是非常有用的。

插件

插件可以被用來擴充 加載器(loader) 以 支援加載除了AMD子產品的其它資源(如, templates  或者 i18n 資源包.  Dojo v.17 包含了以下幾個插件:

  • dojo/domReady:  延遲子產品工廠函數的執行, 直到整個文檔被解析(相當于jQuery 中的 $(document).ready(function(){}) )
  • dojo/text: 加載文本資源, 它是RequireJS文本插件的一個超集, 并将加載的結果存入 dojo.cache. 
  • dojo/i18n: 加載國際化資源包, 包括老版本的格式(v1.6 - i18n API) 及AMD的格式, 是RequireJS 國際資源包的超集。
  • dojo/has: 允許使用 has.js的表達式,有條件的加載子產品。
  • dojo/load:  在運作時加載經過計算得到的子產品依賴
  • dojo/require:  下載下傳一個老版本的子產品而不加載它,就是之前版本中的dojo.require。
  • dojo/loadInit:  調用dojo.loadinit函數,這樣其它的老版本的 API函數可以被執行,特别是dojo.require來下載下傳的子產品。

當一個子產品辨別符被傳遞給 require 或者 define時,有一個 "!", loader 将辨別符以"!"為分隔符,劃分為兩部分。  "!"的左邊被當成一個子產品ID來對待, 作為插件的辨別符。 右邊部分會被傳遞給插件進行處理。

像很多其它的AMD子產品, 插件子產品也隻是加載一次。 不同于正常的子產品, 它必需傳回一個含有" load" 函數簽名的對象。

load(
  id,        // the string to the right of the !
  require,   // AMD require; usually a context-sensitive require bound to the module making the plugin request
  callback   // the function the plugin should call with the return value once it is done
) -> undefined
           

這裡是一個"text" 插件加載文檔的例子:

// this is "myApp/myModule"
define(["text!./templates/myModule.html"], function(template){
  // template is a string loaded from the resource implied by myApp/templates/myModule.html
});
           

一個簡間的 ”text" 插件的實作:

define(["dojo/_base/xhr"], function(xhr){
  return {
    load: function(id, require, callback){
      xhr.get({
        url: require.toUrl(id),
        load: function(text){
          callback(text);
        }
      });
    }
  };
});
           

不像正常子產品傳回的值, loader不會将緩存的值傳遞給插件的callback. 若有必要時, 插件可以自己維護一個内部的緩存。 

define(["dojo"], function(dojo){
  var cache = {};
  return {
    load: function(id, require, callback){
      var url = require.toUrl(id);
      if(url in cache){
        callback(cache[url]);
      }else{
        dojo.xhrGet({
          url: url,
          load: function(text){
            callback(cache[url] = text);
          }
        });
      }
    }
  };
});
           

Window 的 Load事件檢測

Dojo loader 可以連接配接 window.onload事件, 如果document.readyState 尚未設定, 則設定document.readyState為"complete" 。 它使得正常的AMD子產品可以依賴document.readyState,其至是不支援document.readyState屬性的浏覽器。

微事件(loader内部事件)API

loader 有定義一個micro event API, 用來報告錯誤, 配置發生改變,追蹤 以及 空閑狀态。這個API包含兩個方法:

require.on = function(
  eventName, // (string) the event name to connect to
  listener   // (function) called upon event
)

require.signal = function(
  eventName, // (string) the event name to signal
  args       // (array) the arguments to apply to each listener
)
           

loader本身使用require.signal 觸發它自己的事件, 用戶端可以通過傳遞一個監聽函數require.on來監聽loader事件。 例如, 一個用戶端可以連接配接"config"事件來監聽 改變的配置,如下:

var handle = require.on("config", function(config, rawConfig){
      if(config.myApp.myConfigVar){
        // do something
  }
});
           

注意 "config"  事件提供了兩個參數 config 和 rawConfig. 更多的描述可以參考 Configuration 章節。

require.on 傳回一個不透明的 handle對象, handle對像可以對過調用 handle.remove()來停止監聽。

loader 保留的事件名稱有 "error", "config" "idle" 以及"trace". 用戶端應用程式用其它的名稱來自定義的事件。

錯誤報告

當有錯誤發誤時, loader 會通過微事件API(micro event api)來抛出一個"error" 事件。 你可以通過 require.on 來監聽loader 的錯誤。

function handleError(error){
  console.log(error.src, error.id);
}

require.on("error", handleError);
           

傳遞給監聽器的第一個參數是一個loader error對象, 它包含src 和 id  屬性, src 通常為 "dojoLoader", id 為一個特定錯誤的辨別符。 loader 定義了以下錯誤辨別符:

factoryThrew

一個子產品的工廠函數抛出了的錯誤

xhrFailed

異步請求一個子產品資源失敗。 一般是一個 HTTP 404錯誤, 通常是由于 paths, aliases, packages 或 baseUrl 等配置錯誤引發。

multipleDefine

一個子產品已經被建立了,在次調用 define 來建立這個子產品時,會抛出這個錯誤,如 require(['my/app']) ,會請求js/my/app.js檔案,并執行app.js裡的define函數。如果你通過在loader有效時,直接通過<script src="js/my/app.js"></script> 來加載這個檔案,那麼define函數會被在次調用, 建立了兩次my/app子產品, 是以會發現multipleDefine.   通常這個錯誤都是因為直接在 html 文檔中用script來加載子產品。 是以加載子產品時請使用loader, 而不要使用<script>标簽。 第二個主要原因是在define中明确指定了子產品辨別符,即define的方法簽名中的第一個形參。 請勿在任何情況下指定一個子產品辨別符。

timeout

由于最後一個子產品被請求時已以常過了 waitSeconds 指定的時間, 而還是沒有獲得所有子產品。 一般是HTTP 404錯誤, 主要由于 paths, aliases, packages或者baseUrl 配置錯誤。

definele

主要發生在IE環境下匿名調用define函數, 而它又不能确定隐式的子產品辨別符。 definele錯誤原因跟 multipleDefine 錯誤是一樣的。

Loader 錯誤是不能恢複的。如果你的應用程式請求的子產品不存在,loader 不能修複這個錯誤。 可是, 事件API 使用在伺服器端或者 給使用者提供一個錯誤資訊,改進使用者體驗。

調試

異步調試本來就很難,如果是一個高度異步處理就更加棘手。 如加載一個子產品樹(頂點子產品A, A依賴B, B依賴C ....)。 這裡有一些點使得調試易于管理:

  • 老版本的loader API最常見的程式錯誤是在子產品辨別符中使用"."而不是"/"。
  • 最常見的文法錯誤是在依賴清單裡缺少一個逗号,而有的浏覽器不會報告這個錯誤。
  • 常見的程式錯誤是,依賴清單的順序跟回調函數或者工廠方法的順依不一樣。 通常會出現" object is not a constructor" 或者 "method dose not exist" 或 相類似的。
  • 在一些浏覽器的某些情況下,插入一個斷點的過程中将會改變異步請求的順序, 斷點被插入時導緻應用程式失敗。 這表明子產品在被定義時已經确定了依賴的順序。精心設計的AMD應用程式将不會有這些規定。

loader 也可以通過在require object,暴露調試期間的檢查的内部狀态, 如下:

async

Boolean, 辨別目前使用的loader 是否為 aysnchronous.

legacyMode

String, 表示 loader運作在legacyMode( 如果async為false)

baseUrl

配置中的baseUrl變量

paths

配置中的paths變量

packs

package 配置, 是所有被傳遞的package的集合

waiting

傳回一個loader已經請求但還沒成功的子產品清單, 如果loader 看起來已經停止, 第一先去檢視你浏覽器的deugger的網絡面闆中的404錯誤,第二在看這裡。

execQ  (exec queue)

已安排執行的子產品對列。 如果這個隊列停止不前,肯定存在有問題, 可能是 404 錯誤, 文法錯誤,或者 命名錯誤等。

modules

子產品的命名空間,loader 通過這個modules中的條目來獲得每一個子產品的所有資訊

  • result  子產品的值
  • injected 加載的狀态( 0 表示 "requested", 1 表示 "arrived")
  • executed  工廠函數執行的狀态 ( 0 表示 "executing", 1 表示 "executed")
  • pid  子產品包的名稱(如果有的話)
  • url 子產品中定義的資源的位址
  • def 工廠函數

警告:這些被爆露的内部定義隻在調試時有用。 不能将它們用于你的代碼。 因為它們的結構可能會被改變。

追蹤

由于 loader的異步特性,有時最好的技術是可以解決加載中的問題,讓loader正常運作,而不需要使用任何的斷點及分析loader事件發生的順序,如一個子產品的injecting, defining, executing。 loader 的源版本(從官方網站下載下傳的源檔案的版本)包含一個有利于調試技術的 tracing API.  如果有需要, tracing API也可以用于你自己的代碼。 

tracing API 含有以下的方法簽名:

require.trace = function(
  groupId, // (string) the tracing group identifier to which this trace message belongs  
  args     // (array of any) additional data to send with trace 
) -> undefined

require.trace.set(
  groupId, // (string) a tracing group identifier  
  enable   // (boolean) enable or disable tracing of messages from groupId 
) -> undefined

require.trace.set(
  groupMap  // (object:groupId --> boolean) a map from trace group identifier to on/off value 
) -> undefined

require.trace.on // (boolean) enable/disable all tracing 

require.trace.group // (object) a map from trace group id to boolean  
           

為了觸發追蹤的消息, 可以傳遞groupId 以及資訊數組傳定給require.trace .

當 require.trace(groupId, args) 被調用時, 以下是它的處理過程:

  1. 如果 trace.on 為 false, 則直接傳回,不做任何事情。
  2. 如果trace.group[groupId] 為false, 直接傳回
  3. 通過 micro event API( loader的内部事件處理 API) 發現一個trace event 信号,信号的參數為 [groupId, args]
  4. 連接配接groupId 以及 args中的每一個用逗号隔開的字元串, 并調用require.log輸出結果字元串。
  5. 調用require.log 輸出args中的值。

可以通過配置變量trace 來關閉一個或者多個跟蹤群組。 例如:

require({
  trace:{
    "loader-inject":1, // turn the loader-inject group on
    "loader-define":0 // turn the loader-define group off
  }
});
           

另外, require.trace.set 可以被直接調用。 有以下兩種方式:

require.trace.set({
  "loader-inject":1, // turn the loader-inject group on
  "loader-define":0 // turn the loader-define group off
});
           

或者

require.trace.set("loader-inject", 1);
require.trace.set("loader-define", 0);      

所有的跟蹤暫停可以通過設定require.trace.on 為 false.  設定require.trace.on 為 true 隻對groups 單獨設定為true的有效。

loader 定義了以下 追蹤數組:

loader-inject

當一個子產品被注入到應用程式時觸發。 如果子產品已經被緩存,那麼 args[0] 為 "cache", 如果子產品是通過 XHR(ajax)請求的方式注入,那麼args[0]為 "xhr",如果子產品是通過script方式,那麼args[0]為 "script". Args[1] 為子產品辨別符; args[2]是 URL/filename; 如果 args[0] 為"xhr" , args[3] 将會為 true;

loader-define (完成子產品定義)

當define調用完時觸發, args[0] 為子產品辨別符, args[1] 為依賴數組。 注意:args給出的是這些參數的解碼值(如A 依賴 B, 解碼值為運作完 B子產品 的define而傳回的值),而不是arguments[0]以及arguments[1] (不是 "B" 這樣的字元串) 的實際的值。通常在所有的define 調用處理完成之前, loader 不會實際的處理 define函數的調用。 define調用的處理過程可以檢視以下的"loader-define-module".

loader-exec-module (等待進入工廠方法,是一個持續進行的一個過程)

當loader  在第一次追蹤子產品的依賴樹并運作所有依賴子產品的工廠方法過程進行時,嘗試(能)或者不能運作子產品的工廠方法時觸發(如A依賴 B, 目前正在運作 B的define方法, 那麼A的狀态就是等待嘗試運作 A的子產品,觸發loader-exec-module)。能否成功運作工廠方法是沒有保證的。 如果一個依賴的子產品不能被解析(可能還沒有抵達), 則 attempt 會被中止(abort, 代表失敗),并在之後重新嘗試, args[0] 如果是'exec' 則表示嘗試, 'abort' 為失敗。 args[1] 則為子產品辨別符。

loader-run-factory (準備進入工廠方法)

當loader 在所有的依賴已經就緒後,即将調用子產品的工廠方法時觸發。 args[0] 為子產品辨別符。

loader-finish-exec (完成工廠方法調用)

當loader 已經成功運作完子產品的工廠函數之後執行最後的清理時觸發。 這包括正在給新建立的插件子產品執行個體傳遞所有的傳遞插件請求隊例, 以及更新老版本子產品的值。 args[0] 為子產品辨別符。

loader-define-module (完成依賴的子產品定義,準備傳回到上一級子產品)

當loader即将處理上一個define(A依賴B, 執行完B後,即将執行A的define) 時觸發, 可以檢視之前的loader-define. args[0] 為子產品辨別符

loader-circular-dependency

當loader 發現一個循環依賴時觸發, 有可能會有一個程式錯誤。

非浏覽器環境

在 v1.7 版本中,可以在Rhino 和 node.js 中直接使用 dojo loader。 如下,在node.js 的指令行加載dojo loader :

#!/bin/bash
node dojo/dojo.js load=config load=main
           

Rhino:

#!/bin/bash
java -jar util/shrinksafe/js.jar dojo/dojo.js baseUrl=file:///full/path/to/dojo/dojo load=config load=main
           

老版本API

為了相容版本v.16及之前的版本, v1.7的loader 也包括了同步加載API( dojo.provide, dojo.require, dojo.requireLocalization, dojo.requireIf, dojo.requireAfterIf, dojo.platfromRequire, 以及dojo.loadInit), 跟 之前的loader沒有什麼差別, 隻有一個例外:

由于在v1.6及之前的版本中定義的dojo.eval 與浏覽器的eval函數一樣, 有時一個子產品的代碼會在全局作用域内執行,而有時會在一個函數作用域内執行。 考慮下這個子產品:

dojo.provide("module.that.defines.a.global");
var someVariable = anAwesomeCalculation();
           

如果上面的代碼是在全局作用域内執行,someVariable将會進行到全局空間。 如果是在一個函數作用哉内, someVariable 是一個本地變量 而且會在函數傳回時消失。

在 v1.7以上版本, 所有的代碼會被當成文本來下載下傳,并且在函數作用域内 通過eval來拆行。 如果你獲得的代碼如上,并且期望someVariable被定義在全局空間裡, 它将在v1.7版本中無效。 為了建立一個全局的變量,可以将屬性添加到 dojo.global 上。

dojo.provide("module.that.defines.a.global");
dojo.global.someVariable = anAwesomeCalculation();
           

工作模式

v1.7 loader 可以在同一個應用裡加載 加載老版本的子產品和AMD的子產品。 它允許老版本API的客戶應用程式使用dojo, digit, 及被AMD重寫的其它庫。 這種情況下, loader 必須是同步模式, 因為用老版本 API寫的子產品不能通過異步加載。

v1.7 loader 的legacy  mode 下有兩種模式。 synchronous (同步) 和 跨域(異步).

Legacy Synchronous 模式

在這種模式下, 唯一的不同是v1.7 的loader 和 先前的loader 如何處理子產品的值。 不同于正常的AMD API 操作, legacy synchronous 模式将導緻依賴會被立即解析及工廠函數直接運作。 即使相關的子產品還沒有被使用(即使這個子產品沒有被dojo.require請求)。

loader 也可以将dojo.require請求的AMD子產品的傳回值指派給 在dojo.require給定的對象, 隻要該對象在dojo.require被調用用的時候為undefined. 這種行可以通過has配置config-publishRequireResult為false來限制。

Legacy Cross Domain 模式

一旦loader進入到cross-domain 模式,legacy modules 開始異步執行。 如果loader恰巧是在追蹤由幾個互相依賴的老版本子產品組成的依賴樹的中間,之後任何dojo.require的調用會立即傳回,而不執行子產品。v1.6也有這種特性。

配置參考

配置變量

async (true, false/"sync", "legacyAsync")   如果為true, loader 為 AMD模式, 如果為false 或者 "sync", loader  為同步模式, "legacyAsync" loader為遺棄的跨域模式, 預設值為 false.

baseUrl (string)

當子產品辨別符轉化為路徑時 baseUrl會添加到路徑的最前面。 在浏覽器環境下,預設值為 dojo.js的路徑。非浏覽器下為目前的工作目錄。

packages (array of package configuration objects)

包的定義可以檢視 子產品辨別符的章節,預設值的定義可以檢視 Default Configuration.   

packagePaths (object) 

一個簡化的符号,可以用來給相同根路徑的多個包指定配置。 特别的是包的 location 的計算是 将 map 鍵(指定的根路徑)+ 每個包的name。 一個包的配置對象也可以 string.  字元串就代表包的名字, main 和 packageMap的預設值。 例

packagePaths:{
  "path/to/some/place":[
    "myPackage",
    {
      name:"yourPackage",
      main:"base"
    }
  ]
}
           

相當于

packages:[{
  name:"myPackage",
  location:"path/to/some/place/myPackage"
},{
  name:"yourPackage",
  location:"path/to/some/place/youPackage"
}]
           

别名 (鍵值對數組(兩個一組))

可以檢視模 塊辨別符中的定義 , 第一個元素可以是一個正規表達式, 表明給多個子產品取一個别名。或者為字元串, 表名隻是給單個子產品取的别名。 在鍵值對的第二個元素通常為字元串(絕對的子產品辨別符),即為真實的子產品名稱。

hasCache : (map: has feature name --> ( 布爾值或者函數) has 特性檢測或者值)

提供has 特征的值的一個集合, 預設值可檢視 Default Configuration 

waitSeconds (number)

loader 請求子產品的等待時間(秒),如果在指定的時間内,子產品沒有抵達, 抛出 timeout error.  任意子產品被請求成功後,計時器重新計算。 預設值為 0 (一直等待)

cacheBust (boolean)

隻适用于浏覽器環境。 如果為true, 這個值将會被添加到每個子產品的URL末尾,做為查詢字元串參數以打斷浏覽器緩存。 預設為 false.

deps ( array of module identifier strings) /callback(function)

這些個配置變量隻在loader被加載前有效。 一旦loader 被加載, 将導緻 laoder去執行 require(deps, callback)。

stripStrict(boolean)

輸出的子產品可以不使用嚴格模式 "use strict", 它可以使你通過arguments.callee.caller來通路調用棧, 預設為disable.  它僅在同步模式下有用。

 預設的配置

到現在為此整個文檔已經寫完, 這裡是dojo loader的預設配置。 最新的配置可以檢視dojo.js檔案。

{
    // the default configuration for a browser; this will be modified by other environments
    hasCache:{
        "host-browser":1,
        "dom":1,
        "dojo-amd-factory-scan":1,
        "dojo-loader":1,
        "dojo-has-api":1,
        "dojo-inject-api":1,
        "dojo-timeout-api":1,
        "dojo-trace-api":1,
        "dojo-log-api":1,
        "dojo-dom-ready-api":1,
        "dojo-publish-privates":1,
        "dojo-config-api":1,
        "dojo-sniff":1,
        "dojo-sync-loader":1,
        "dojo-test-sniff":1,
        "config-tlmSiblingOfDojo":1
    },
    packages:[{
        // note: like v1.6-, this bootstrap computes baseUrl to be the dojo directory
        name:'dojo',
        location:'.'
    },{
        name:'tests',
        location:'./tests'
    },{
        name:'dijit',
        location:'../dijit'
    },{
        name:'build',
        location:'../util/build'
    },{
        name:'doh',
        location:'../util/doh'
    },{
        name:'dojox',
        location:'../dojox'
    },{
        name:'demos',
        location:'../demos'
    }],
    trace:{
        // these are listed so it's simple to turn them on/off while debugging loading
        "loader-inject":0,
        "loader-define":0,
        "loader-exec-module":0,
        "loader-run-factory":0,
        "loader-finish-exec":0,
        "loader-define-module":0,
        "loader-circular-dependency":0
    },
    async:0
}
           

繼續閱讀