插件

简介 § 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() 方法。还有一些方法可以帮助进行模块名称规范化,并使用插件作为 优化器 的一部分。

完整的插件 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**:函数。使用 name 的值调用的函数。这告诉加载器插件已完成资源加载。如果插件检测到表示资源将无法正确加载的错误情况,则可以调用 **onload.error()**,并向其传递一个错误对象。
  • **config**:对象。配置对象。这是优化器和 Web 应用程序将配置信息传递给插件的一种方式。如果 Web 应用程序想要强制使用特定的语言环境,则 i18n! 插件使用它来获取当前语言环境。如果此插件(或 pluginBuilder)作为优化器构建的一部分被调用,则优化器将在配置中将 **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() 的示例插件的 load 函数


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 接受 moduleName 作为第一个参数: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 中运行时同步完成资源加载。这就是 text 插件的作用。如果您只想运行 AMD 模块并使用 amdefine 在 Node 中加载插件依赖项,那么这些模块也需要同步完成以匹配 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);
            });
        }
    });

}());

如果资源名称只是一个常规模块名称,则不需要实现 normalize。例如,text! 插件没有实现 normalize,因为依赖项名称看起来像“text!./some/path.html”。

如果插件没有实现 normalize,则加载器将尝试使用常规模块名称规则来规范化资源名称。

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() 代码有用。
  • data:对象。关于层的信息。只有两个属性
    • name:层的模块名称。可能未定义。
    • path:层的路径。可能未定义,特别是如果输出只是一个被另一个脚本使用的字符串。

    writeFile: function (pluginName, name, parentRequire, write) § 3.5

    writeFile 仅供优化器使用,并且只有在插件需要写出由插件处理的依赖项的备用版本时才需要实现。扫描项目中的所有模块以查找所有插件依赖项的成本有点高,因此只有在 RequireJS 优化器的构建配置文件中存在 optimizeAllPluginResources: true 时才会调用此 writeFile 方法。writeFile 使用以下参数调用

    • **pluginName**:字符串。插件的**规范化**名称。大多数插件不会使用名称编写(它们将是匿名插件),因此了解插件模块的规范化名称以在优化文件中使用非常有用。
    • name:字符串。规范化 的资源名称。
    • parentRequire:函数。一个本地的 "require" 函数。在 writeFile 中,它的主要用途是调用 parentRequire.toUrl() 来生成构建目录内的文件路径。
    • write:函数。一个使用两个参数调用的函数
      • fileName:字符串。要写入的文件的名称。您可以将 parentRequire.toUrl() 与相对路径一起使用,以生成将在构建输出目录内的文件名。
      • text:字符串。文件的内容。必须是 UTF-8 编码。
      此函数还包含一个属性函数,write.asModule(moduleName, fileName, text)。asModule 可用于写出一个模块,该模块可能包含需要插入名称的匿名 define 调用,并且/或者包含需要为优化文件提取的隐式 require("") 依赖项。

    有关 writeFile 的示例,请参阅 text! 插件

    pluginBuilder § 3.6

    pluginBuilder 可以是一个字符串,指向在插件用作优化器构建的一部分时要使用的另一个模块,而不是当前插件。

    插件可能具有非常具体的逻辑,这些逻辑依赖于特定的环境,例如浏览器。但是,在优化器内部运行时,环境非常不同,并且插件可能具有一个它不想作为在浏览器中加载的普通插件的一部分提供的 write 插件 API 实现。在这些情况下,指定 pluginBuilder 很有用。

    关于使用 pluginBuilder 的一些注意事项

    • 不要对插件或 pluginBuilder 使用命名模块。pluginBuilder 文本内容用于代替插件文件的内容,但这只有在文件没有使用名称调用 define() 时才有效。
    • 作为构建过程的一部分运行的插件和 pluginBuilder 的环境非常有限。优化器在几个不同的 JS 环境中运行。如果您希望插件作为优化器的一部分运行,请注意环境假设。