插件
- 简介§ 1
- 插件名称§ 2
- API§ 3
- load§ 3.1
- normalize§ 3.2
- write§ 3.3
- onLayerEnd§ 3.4
- writeFile§ 3.5
- pluginBuilder§ 3.6
简介§ 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" 函数,用于加载其他模块。这个函数将会相对于请求插件资源的模块的名称解析相对模块名。如果加载器插件想要
require()
相对于自身的 IDrequire
加载某些内容define
,它可以要求其 own- 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! 插件使用它来获取当前区域设置,如果 Web 应用程序想强制使用特定的区域设置的话。优化器会在 config 中设置一个isBuild属性为 true,表示该插件(或 pluginBuilder)正在被优化器调用。
一个并不做任何有趣操作的示例插件,仅使用常规 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 接受 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 中运行时选择同步完成资源加载。文本插件就是这么做的。如果您只是想在 Node 中运行 AMD 模块并使用amdefine加载插件依赖项,它们也需要同步完成以匹配 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仅由优化器使用,且只有当插件需要写入由插件处理的依赖项的替代版本时才需要实现。扫描项目中所有模块以查找所有插件依赖项是比较昂贵的操作,因此只有在optimizeAllPluginResources: true属于 RequireJS 优化器的构建配置文件。writeFile 会被以下参数调用:
- pluginName: 字符串。该归一化的插件名称。大多数插件不会带有名称编写(它们是匿名插件),所以了解插件模块的归一化名称在优化文件中使用时很有用。
- name: 字符串。该归一化的资源名称。
- parentRequire: 函数。一个局部 "require" 函数。writeFile 中的主要用途是调用 parentRequire.toUrl() 来生成位于构建目录内部的文件路径。
- write: 一个接受两个参数的函数:
- fileName: 字符串。要写入的文件的名称。你可以使用 parentRequire.toUrl() 加相对路径来生成一个将位于构建输出目录内的文件名。
- text: 字符串。文件内容。必须为 UTF-8 编码。
有关 writeFile 示例,请参见text! 插件。
pluginBuilder § 3.6
pluginBuilder可以是一个字符串,指向另一个模块,当该插件作为优化器构建的一部分使用时,代替当前插件使用。
一个插件可能具有非常特定的逻辑,依赖于某个特定环境,例如浏览器。然而,在优化器内部运行时,环境大不相同,并且插件可能拥有一种write插件 API 实现,而它不希望作为浏览器中加载的正常插件的一部分被分发。在这种情况下,指定 pluginBuilder 是有用的。
关于使用 pluginBuilder 的一些注意事项:
- 不要在插件或 pluginBuilder 中使用命名模块。pluginBuilder 的文本内容会替代插件文件的内容使用,但这只有在文件没有使用 define() 带名称定义模块的情况下才有效。
- 作为构建过程一部分运行的插件和 pluginBuilder 具有非常有限的运行环境。优化器会在几个不同的 JS 环境中运行。如果你希望插件能作为优化器的一部分运行,请谨慎对待关于运行环境的假设。