外掛程式

簡介 § 1

RequireJS 允許您撰寫載入器外掛程式,這些外掛程式可以載入不同類型的資源作為相依性,甚至將相依性包含在最佳化的組建中。

現有載入器外掛程式的範例為 text!i18n! 外掛程式。text! 外掛程式處理載入文字,而 i18n 外掛程式處理載入由幾個不同模組的物件組成的 JavaScript 物件。該物件包含在地化的字串。

RequireJS wiki 有較長的 外掛程式清單

外掛程式名稱 § 2

載入器外掛程式只是另一個模組,但它們實作特定的 API。載入器外掛程式也可以參與最佳化最佳化,讓它們載入的資源可以內嵌在最佳化的組建中。

注意:外掛程式及其相依性應該可以在非瀏覽器環境中執行,例如 Node 和 Nashorn。如果它們無法執行,您應該使用替代的 外掛程式組建器 模組,該模組可以在這些環境中執行,以便它們可以參與最佳化組建。

您可以透過在相依性中放置其模組名稱在 ! 之前來參照您的外掛程式。例如,如果您建立一個名稱為「foo.js」的外掛程式,您會像這樣使用它


require(['foo!something/for/foo'], function (something) {
    //something is a reference to the resource
    //'something/for/foo' that was loaded by foo.js.
});

因此,外掛程式的模組名稱在 ! 分隔符號之前。! 分隔符號之後的部分稱為資源名稱。資源名稱可能看起來像一般的模組名稱。外掛程式的模組名稱可以是任何有效的模組名稱,因此,例如,您可以使用相對指標


require(['./foo!something/for/foo'], function (something) {
});

或者,如果它在套件或目錄內,例如 bar/foo.js


require(['bar/foo!something/for/foo'], function (something) {
});

API § 3

RequireJS 會先載入外掛程式模組,然後將相依性名稱的其餘部分傳遞給外掛程式上的 load() 方法。還有一些方法可以協助模組名稱正規化,以及將外掛程式用作 最佳化器 的一部分。

完整的 Plugin API

  • load:呼叫來載入資源的函式。這是外掛程式需要實作的唯一強制性 API 方法,才能發揮作用。
  • normalize:正規化資源名稱的函式。這有助於提供最佳的快取和最佳化,但只有在資源名稱不是模組名稱時才需要。
  • write:最佳化器用來指示外掛程式應在最佳化檔案中寫出資源表示時使用。
  • pluginBuilder:模組名稱字串,用於最佳化程式中執行最佳化工作。最佳化程式執行時,會使用該模組,而非外掛模組。

load: function (name, parentRequire, onload, config) § 3.1

load 是函式,將呼叫下列引數

  • name:字串。要載入的資源名稱。這是名稱中驚嘆號分隔符號之後的部分。因此,如果模組要求「foo!something/for/foo」,foo 模組的 load 函式會收到「something/for/foo」作為名稱。
  • parentRequire:函式。用於載入其他模組的區域性「require」函式。此函式會根據要求此外掛資源的模組名稱,解析相對模組名稱。如果載入器外掛想要根據其自己的 ID 執行 require(),可以在其自己的 define 呼叫中要求 require。此 require 函式有一些公用程式
    • parentRequire.toUrl(moduleResource):其中 moduleResource 是模組名稱加上副檔名。例如「view/templates/main.html」。它會傳回資源的完整路徑,並遵循任何 RequireJS 組態。
    • parentRequire.defined(moduleName):如果模組已載入並定義,傳回 true。在 RequireJS 0.25.0 之前,稱為 require.isDefined。
    • parentRequire.specified(moduleName):如果模組已要求或正在載入中,且應在某個時間點可用,傳回 true。
  • onload:函式。使用名稱值呼叫函式。這會告知載入器外掛已載入資源。如果外掛偵測到錯誤狀況,表示資源無法正確載入,可以呼叫 onload.error(),並傳遞錯誤物件給它。
  • config:物件。組態物件。這是最佳化程式和 Web 應用程式將組態資訊傳遞給外掛的方法。i18n! 外掛使用此方法取得目前語言環境,如果 Web 應用程式想要強制使用特定語言環境。如果此外掛 (或 pluginBuilder) 作為最佳化程式組建的一部分呼叫,最佳化程式會在 config 中設定 isBuild 屬性為 true。

範例外掛,沒有執行任何有趣的事項,僅執行一般 require 來載入 JS 模組


define({
    load: function (name, req, onload, config) {
        //req has the same API as require().
        req([name], function (value) {
            onload(value);
        });
    }
});

有些外掛可能需要評估以文字形式擷取的 JavaScript,並將評估後的 JavaScript 用作資源的值。onload() 引數有一個函式,onload.fromText(),可用於評估 JavaScript。RequireJS 使用 eval() 評估該 JavaScript,而且 RequireJS 會針對評估後的文字中的任何匿名 define() 呼叫執行正確的工作,並使用該 define() 模組作為資源的值。

onload.fromText() 的參數(RequireJS 2.1.0 及更新版本)

  • text:字串。要評估的 JavaScript 字串。

使用 onload.fromText() 的範例外掛程式載入函式


define({
    load: function (name, req, onload, config) {
        var url = req.toUrl(name + '.customFileExtension'),
            text;

        //Use a method to load the text (provided elsewhere)
        //by the plugin
        fetchText(url, function (text) {
            //Transform the text as appropriate for
            //the plugin by using a transform()
            //method provided elsewhere in the plugin.
            text = transform(text);

            //Have RequireJS execute the JavaScript within
            //the correct environment/context, and trigger the load
            //call for this resource.
            onload.fromText(text);
        });
    }
});

在 RequireJS 2.1.0 之前,onload.fromText 會將模組名稱當作第一個參數:onload.fromText(moduleName, text),而載入器外掛程式必須在 onload.fromText() 呼叫後手動呼叫 require([moduleName], onload)

建置考量:最佳化器會同步追蹤相依性,以簡化最佳化邏輯。這與瀏覽器中 require.js 的運作方式不同,而且表示只有能夠同步滿足其相依性的外掛程式,才能參與允許內嵌載入器外掛程式值的最佳化步驟。否則,如果 config.isBuild 為 true,外掛程式應立即呼叫 load()


define({
    load: function (name, req, onload, config) {
        if (config.isBuild) {
            //Indicate that the optimizer should not wait
            //for this resource any more and complete optimization.
            //This resource will be resolved dynamically during
            //run time in the web browser.
            onload();
        } else {
            //Do something else that can be async.
        }
    }
});

有些外掛程式可能會在瀏覽器中執行非同步作業,但選擇在 Node/Nashorn 中執行時同步完成資源載入。這就是文字外掛程式所做的。如果你只想在 Node 中使用 amdefine 執行 AMD 模組和載入外掛程式相依性,它們也需要同步完成,以符合 Node 的同步模組系統。

normalize: function (name, normalize) § 3.2

normalize 會被呼叫來正規化用於識別資源的名稱。有些資源可能會使用相對路徑,而且需要正規化為完整路徑。normalize 會使用下列參數呼叫

  • name:字串。要正規化的資源名稱。
  • normalize:函式。可以呼叫來正規化一般模組名稱的函式。

範例:假設有一個index! 外掛程式,它會載入給定索引的模組名稱。這是一個虛構的範例,只是為了說明這個概念。模組可能會這樣參照 index! 資源


define(['index!2?./a:./b:./c'], function (indexResource) {
    //indexResource will be the module that corresponds to './c'.
});

在這種情況下,正規化名稱「./a」、「./b」和「./c」會根據要求這個資源的模組來決定。由於 RequireJS 不知道如何檢查「index!2?./a:./b:./c」來正規化「./a」、「./b」和「./c」的名稱,因此它需要詢問外掛程式。這就是 normalize 呼叫的目的。

透過適當地正規化資源名稱,它允許載入器有效地快取值,並在最佳化器中適當地建置最佳化的建置層。

index! 外掛程式可以這樣撰寫


(function () {

    //Helper function to parse the 'N?value:value:value'
    //format used in the resource name.
    function parse(name) {
        var parts = name.split('?'),
            index = parseInt(parts[0], 10),
            choices = parts[1].split(':'),
            choice = choices[index];

        return {
            index: index,
            choices: choices,
            choice: choice
        };
    }

    //Main module definition.
    define({
        normalize: function (name, normalize) {
            var parsed = parse(name),
                choices = parsed.choices;

            //Normalize each path choice.
            for (i = 0; i < choices.length; i++) {
                //Call the normalize() method passed in
                //to this function to normalize each
                //module name.
                choices[i] = normalize(choices[i]);
            }

            return parsed.index + '?' + choices.join(':');
        },

        load: function (name, req, onload, config) {
            req([parse(name).choice], function (value) {
                onload(value);
            });
        }
    });

}());

如果資源名稱只是一個正規的模組名稱,則不需要實作正規化。例如,text! 外掛程式不會實作正規化,因為相依名稱看起來像 'text!./some/path.html'。

如果外掛程式沒有實作正規化,則載入器會嘗試使用一般模組名稱規則來正規化資源名稱。

write: function (pluginName, moduleName, write) § 3.3

write 僅由最佳化器使用,而且只有當外掛程式可以輸出一些屬於最佳化層級的內容時,才需要實作。它會使用下列引數呼叫

  • pluginName: 字串。外掛程式的正規化名稱。大多數外掛程式不會以名稱撰寫(它們會是匿名的外掛程式),因此了解最佳化檔案中使用的外掛程式模組的正規化名稱是有用的。
  • moduleName: 字串。正規化資源名稱。
  • write: 函式。一個函式,會使用要寫入最佳化檔案的輸出字串呼叫。此函式也包含一個屬性函式,write.asModule(moduleName, text)。asModule 可用來寫出一個模組,其中可能有一個匿名的 define 呼叫,需要插入名稱或/和包含隱含的 require("") 相依性,需要將其拉出以供最佳化檔案使用。asModule 對文字轉換外掛程式很有用,例如 CoffeeScript 外掛程式。

text! 外掛程式實作 write,以寫出它載入的文字檔的字串值。該檔案的片段


write: function (pluginName, moduleName, write) {
    //The text plugin keeps a map of strings it fetched
    //during the build process, in a buildMap object.
    if (moduleName in buildMap) {
        //jsEscape is an internal method for the text plugin
        //that is used to make the string safe
        //for embedding in a JS string.
        var text = jsEscape(buildMap[moduleName]);
        write("define('" + pluginName + "!" + moduleName  +
              "', function () { return '" + text + "';});\n");
    }
}

onLayerEnd: function (write, data) § 3.4

onLayerEnd 僅由最佳化器使用,而且僅在最佳化器的 2.1.0 或更新版本中支援。它會在模組寫入層級後呼叫。如果您需要一些應該放在層級最後的程式碼,或者如果外掛程式需要重設一些內部狀態,則使用它會很有用。

一個範例:一個外掛程式需要在層級開頭寫出一些公用函式,作為第一個 write 呼叫的一部分,而且外掛程式需要知道何時重設內部狀態以了解何時為下一個層級寫出公用程式。如果外掛程式實作 onLayerEnd,它可以在需要重設其內部狀態時收到通知。

onLayerEnd 會使用下列引數呼叫

  • write: 函式。一個函式,會使用要寫入最佳化層級的輸出字串呼叫。不應該在此呼叫中寫出模組。它們不會正確正規化,以與檔案中已有的其他 define() 呼叫共存。它僅適用於寫出非 define() 程式碼。
  • 資料:物件。圖層資訊。只有兩個屬性
    • 名稱:圖層的模組名稱。可能是未定義的。
    • 路徑:圖層的檔案路徑。可能是未定義的,特別是如果輸出只是一個由其他指令碼使用的字串。

    writeFile:函式 (pluginName, name, parentRequire, write) § 3.5

    writeFile 僅由最佳化程式使用,而且只有當外掛程式需要寫出外掛程式處理的相依項的替代版本時,才需要實作。掃描專案中的所有模組以尋找所有外掛程式相依項的成本有點高,因此只有當 RequireJS 最佳化程式的建置設定檔中包含 optimizeAllPluginResources: true 時,才會呼叫此 writeFile 方法。writeFile 會以下列引數呼叫

    • pluginName: 字串。外掛程式的正規化名稱。大多數外掛程式不會以名稱撰寫(它們會是匿名的外掛程式),因此了解最佳化檔案中使用的外掛程式模組的正規化名稱是有用的。
    • 名稱:字串。正規化的資源名稱。
    • parentRequire:函式。一個本機「require」函式。在 writeFile 中主要使用它來呼叫 parentRequire.toUrl() 以產生建置目錄內的檔案路徑。
    • 寫入:函式。一個會以兩個引數呼叫的函式
      • 檔名:字串。要寫入的檔案名稱。您可以將 parentRequire.toUrl() 與相對路徑搭配使用,以產生建置輸出目錄內的檔案名稱。
      • 文字:字串。檔案內容。必須以 UTF-8 編碼。
      此函式還包含一個屬性函式 write.asModule(moduleName, fileName, text)。asModule 可用於寫出一個模組,其中可能有一個匿名定義呼叫,需要插入名稱或/和包含隱含的 require("") 相依項,需要將其取出以供最佳化檔案使用。

    請參閱 text! 外掛程式 以取得 writeFile 的範例。

    pluginBuilder § 3.6

    pluginBuilder 可以是一個字串,指向另一個模組,當外掛程式用於最佳化建置的一部分時,用於取代目前的 plugin。

    外掛程式可能有非常具體的邏輯,取決於特定環境,例如瀏覽器。但是,在最佳化程式內執行時,環境非常不同,而且外掛程式可能有一個 寫入 外掛程式 API 實作,它不希望將其作為在瀏覽器中載入的正常外掛程式的一部分提供。在這些情況下,指定 pluginBuilder 會很有用。

    關於使用 pluginBuilder 的一些注意事項

    • 不要對外掛程式或 pluginBuilder 使用命名模組。會使用 pluginBuilder 文字內容,而不是外掛程式檔案的內容,但這只有在檔案沒有使用名稱呼叫 define() 時才會有效。
    • 作為建置流程一部分執行的外掛程式和 pluginBuilder 有一個非常有限的環境。最佳化程式在幾個不同的 JS 環境中執行。如果您希望外掛程式作為最佳化程式的一部分執行,請小心環境假設。