RequireJS插件开发

介绍§ 1

RequireJS允许您编写加载程序插件,这些加载程序插件可以将不同类型的资源作为依赖项加载,甚至可以将依赖项包含在优化的构建中。

文本i18n!插件是现有加载程序插件的示例!文件插件处理加载文本,而i18n插件处理加载JavaScript对象,该JavaScript对象由来自几个不同模块的对象组成。该对象包含本地化的字符串。

插件名称§ 2

加载程序插件只是另一个模块,但是它们实现了特定的API。加载程序插件也可以参与优化程序优化,从而允许将它们加载的资源内联到优化的构建中。

注意: 该插件及其依赖项应能够在非浏览器环境(如Node和Nashorn)中运行。如果他们不能,则应使用可以在这些环境中运行的备用插件构建器模块,以便他们可以参与优化构建。

您可以通过将其模块名称放在!之前来引用您的插件。在依赖中。例如,如果您创建一个名为"foo.js"的插件,则可以这样使用它:


require(['foo!something/for/foo'], function (something) {
    //有些东西是对资源的引用
    //foo.js加载的“something/for/foo"。
});

因此,插件的模块名称位于!之前。分隔器。之后的部分!分隔符称为资源名称。资源名称可能看起来像普通的模块名称。插件的模块名称可以是任何有效的模块名称,因此,例如,您可以使用相对指示符:


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

或者,如果位于包或目录中,请说出bar/foo.js:


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

API<

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函数具有一些实用程序:
    • 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应用程序将配置信息传递给插件的一种方式。i18n!如果网络应用要强制使用特定的语言环境,则插件会使用它来获取当前的语言环境。如果此插件(或pluginBuilder)作为优化器构建的一部分被调用,则优化器会将配置中的isBuild属性设置为true。

一个示例插件,它没有做任何有趣的事情,只是正常需要加载JS模块:


define({
    load: function (name, req, onload, config) {
        //req与require()具有相同的API。
        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;

        //使用方法加载插件提供的文本
        fetchText(url, function (text) {
            //使用插件中其他地方提供的Transform()方法转换适合插件的文本。
            text = transform(text);

            //让RequireJS在正确的环境/上下文中执行JavaScript,
	    //并触发此资源的加载调用。
            onload.fromText(text);
        });
    }
});

在RequireJS 2.1.0之前,onload.fromText接受moduleName作为第一个参数:onload.fromText(moduleName, text),并且require([moduleName], onload) 在onload.fromText()调用之后,加载程序插件必须手动调用。

构建注意事项: 优化器同步跟踪依赖关系,以简化优化逻辑。这与浏览器中require.js的工作方式不同,这意味着只有可以同步满足其依赖性的插件才应参与允许内联加载程序插件值的优化步骤。否则,如果config.isBuild 为true ,则插件应立即调用load():


define({
    load: function (name, req, onload, config) {
        if (config.isBuild) {
            //指示优化器不应再等待此资源并完成优化。
            //此资源将在运行时在web浏览器中动态解析。
            onload();
        } else {
            //做一些其他可以异步的事情。
        }
    }
});

有些插件可能会在浏览器中执行异步操作,但是在Node/Nashorn中运行时选择同步完成资源加载。这就是文本插件的作用。如果您只想运行AMD模块并在Node中使用amdefine加载插件依赖性,则还需要同步完成以匹配Node的同步模块系统。

normalize: function (name, normalize) § 3.2

normalize 被称为正常化用于识别资源的名称。一些资源可以使用相对路径,并且需要规范化为完整路径。使用以下参数调用normalize:

  • name: 字符串。要规范化的资源名称。
  • normalize: 函数。可以调用以标准化常规模块名称的函数。

一个例子:假设有一个索引!插件将加载给定索引的模块名称。这是一个人为的示例,仅用于说明概念。模块可以引用索引!像这样的资源:


define(['index!2?./a:./b:./c'], function (indexResource) {
    //indexResource将是对应于“./c"的模块。
});

在这种情况下,将相对于请求此资源的模块来确定标准化名称"./a"、“./b"和"./c"。由于RequireJS不知道如何检查'index!2?./ a:./ b:./ c'来规范化'./a'、'./b'和'./c'的名称,它需要询问插件。这是规范化调用的目的。

通过正确规范化资源名称,它允许加载程序有效地缓存值,并在优化器中正确构建优化的构建层。

该指数!插件可以这样写:


(function () {

    //解析'N'的助手函数?value:value:value'资源名称中使用的格式。
    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
        };
    }

    //主模块定义。
    define({
        normalize: function (name, normalize) {
            var parsed = parse(name),
                choices = parsed.choices;

            //规范化每个路径选择。
            for (i = 0; i < choices.length; i++) {
                //调用传入此函数的normalize()方法来规范化每个模块名。
                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!./ some/path.html"。

如果插件未实现规范化,则加载程序将尝试使用常规模块名称规则来规范资源名称。

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

write 仅由优化器使用,并且仅在插件可以输出属于优化层的内容时才需要实现。使用以下参数调用它:

  • pluginName: 字符串。插件的标准化名称。大多数插件都不会使用名称进行创作(它们将是匿名插件),因此了解用于优化文件中的插件模块的标准化名称很有用。
  • moduleName: 字符串。该归一化的资源名称。
  • write: 函数。带有输出字符串的调用函数,以写入优化文件。此函数还包含属性函数write.asModule(moduleName,text)。asModule可以用于写出一个模块,该模块中可能需要名称插入或/和包含隐式require(“")依赖关系的模块可能在其中进行匿名定义调用,而这些依赖关系需要针对优化文件进行提取。asModule对于文本转换插件(例如CoffeeScript插件)很有用。

文本!插件实现write,以为其加载的文本文件写出一个字符串值。该文件的摘录:


write: function (pluginName, moduleName, write) {
    //文本插件在一个build map对象中保存它在构建过程中获取的字符串的映射。
    if (moduleName in buildMap) {
        //jsEscape是文本插件的内部方法,用于使字符串安全地嵌入JS字符串。
        var text = jsEscape(buildMap[moduleName]);
        write("define('" + pluginName + "!" + moduleName  +
              "', function () { return '" + text + "';});\n");
    }
}

onLayerEnd: function (write, data)§ 3.4

onLayerEnd 仅由优化器使用,并且仅在2.1.0或更高版本的优化器中受支持。在将用于该层的模块写入该层之后,将调用该方法。如果您需要一些应放在层末尾的代码,或者插件需要重置某些内部状态,则使用此命令很有用。

一个示例:一个插件需要在层的开头写出一些实用程序函数,作为第一个写调用的一部分,并且该插件需要知道何时重置内部状态,才能知道何时为该应用程序写出实用程序。下一层。如果插件实现onLayerEnd,则可以在何时重置其内部状态时得到通知。

使用以下参数调用onLayerEnd:

  • write: 函数。带有输出字符串的调用函数,以写入优化层。不应在此调用中写出模块。为了与文件中已有的其他define()调用共存,将无法正确规范化它们。仅在写出非define()代码时有用。
  • data: 对象。有关图层的信息。只有两个属性:
    • name: 图层的模块名称。可能是不确定的。
    • path: 图层的文件路径。可能是不确定的,特别是如果输出仅是另一个脚本使用的字符串时。

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

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

    • pluginName: 字符串。插件的标准化名称。大多数插件都不会使用名称进行创作(它们将是匿名插件),因此了解用于优化文件中的插件模块的标准化名称很有用。
    • name: 字符串。该归一化的资源名称。
    • parentRequire: 函数。本地“要求"函数。在writeFile中,此方法的主要用途是调用parentRequire.toUrl()以生成构建目录中的文件路径。
    • write: 函数。一个带有两个参数的函数:
      • fileName: 字符串。要写入的文件名。您可以将parentRequire.toUrl()与相对路径一起使用,以生成将在构建输出目录内的文件名。
      • text: 字符串。文件的内容。必须为UTF-8编码。
      此函数还包含属性函数write.asModule(moduleName,fileName,text)。asModule可以用于写出一个模块,该模块中可能需要名称插入或/和包含隐式require(“")依赖关系的模块可能在其中进行匿名定义调用,而这些依赖关系需要针对优化文件进行提取。

    pluginBuilder§ 3.6

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

    插件可能具有非常特定的逻辑,该逻辑取决于特定的环境,例如浏览器。但是,当在优化器中运行时,环境非常不同,并且该插件可能具有写插件API实现,因此它不希望作为浏览器中加载的普通插件的一部分提供。在这些情况下,指定pluginBuilder很有用。

    有关使用pluginBuilder的一些注意事项:

    • 不要为插件或pluginBuilder使用命名模块。使用pluginBuilder文本内容代替了插件文件的内容,但是仅在文件不使用名称调用define()时才有效。
    • 作为构建过程的一部分运行的Plugins和pluginBuilders具有非常有限的环境。优化器在几种不同的JS环境中运行。如果要让插件作为优化程序的一部分运行,请注意环境假设。