RequireJS allows you to write loader plugins that can load different types of resources as dependencies, and even include the dependencies in optimized builds.
Examples of existing loader plugins are the text! and i18n! plugins. The text! plugin handles loading text, and the i18n plugin handles loading a JavaScript object that is made up from objects from a few different modules. The object contains localized strings.
The RequireJS wiki has a longer list of plugins.
Loader plugins are just another module, but they implement a specific API. Loader plugins can also participate in the optimizer optimizations, allowing the resources they load to be inlined in an optimized build.
Note: the plugin and its dependencies should be able to run in non-browser environments like Node and Nashorn. If they cannot, you should use an alternate plugin builder module that can run in those environments so that they can participate in optimization builds.
You can reference your plugin by putting its module name before a ! in the dependency. For instance, if you create a plugin with the name "foo.js", you would use it like so:
require(['foo!something/for/foo'], function (something) { //something is a reference to the resource //'something/for/foo' that was loaded by foo.js. });
So, the plugin's module name comes before the ! separator. The part after the ! separator is called the resource name. The resource name may look like a normal module name. The plugin's module name can be any valid module name, so for instance, you could use a relative indicator:
require(['./foo!something/for/foo'], function (something) { });
Or, if it was inside a package or directory, say bar/foo.js:
require(['bar/foo!something/for/foo'], function (something) { });
RequireJS will load the plugin module first, then pass the rest of the dependency name to a load() method on the plugin. There are also some methods to help with module name normalization and for making use of the plugin as part of the optimizer.
The complete Plugin API:
load is a function, and it will be called with the following arguments:
require()
something relative to its own ID, it can ask for a require
in its own define
call. This require function has some utilities on it: An example plugin that does not do anything interesting, just does a normal require to load a JS module:
define({ load: function (name, req, onload, config) { //req has the same API as require(). req([name], function (value) { onload(value); }); } });
Some plugins may need to evaluate some JavaScript that was retrieved as text, and use that evaluated JavaScript as the value for the resource. There is a function off the onload() argument, onload.fromText(), that can be used to evaluate the JavaScript. eval() is used by RequireJS to evaluate that JavaScript, and RequireJS will do the right work for any anonymous define() call in the evaluated text, and use that define() module as the value for the resource.
Arguments for onload.fromText() (RequireJS 2.1.0 and later):
An example plugin's load function that uses 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); }); } });
Before RequireJS 2.1.0, onload.fromText accepted a moduleName as the first argument: onload.fromText(moduleName, text)
, and the loader plugin had to manually call require([moduleName], onload)
after the onload.fromText() call.
Build considerations: The optimizer traces dependencies synchronously to simplify the optimization logic. This is different from how require.js in the browser works, and it means that only plugins that can satisfy their dependencies synchronously should participate in the optimization steps that allow inlining of loader plugin values. Otherwise, the plugin should just call load() immediately if config.isBuild
is true:
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. } } });
Some plugins may do an async operation in the browser, but opt to complete the resource load synchronously when run in Node/Nashorn. This is what the text plugin does. If you just want to run AMD modules and load plugin dependencies using amdefine in Node, those also need to complete synchronously to match Node's synchronous module system.
normalize is called to normalize the name used to identify a resource. Some resources could use relative paths, and need to be normalized to the full path. normalize is called with the following arguments:
An example: suppose there is an index! plugin that will load a module name given an index. This is a contrived example, just to illustrate the concept. A module may reference an index! resource like so:
define(['index!2?./a:./b:./c'], function (indexResource) { //indexResource will be the module that corresponds to './c'. });
In this case, the normalized names the './a', './b', and './c' will be determined relative to the module asking for this resource. Since RequireJS does not know how to inspect the 'index!2?./a:./b:./c' to normalize the names for './a', './b', and './c', it needs to ask the plugin. This is the purpose of the normalize call.
By properly normalizing the resource name, it allows the loader to cache the value effectively, and to properly build an optimized build layer in the optimizer.
The index! plugin could be written like so:
(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); }); } }); }());
You do not need to implement normalize if the resource name is just a regular module name. For instance, the text! plugin does not implement normalize because the dependency names look like 'text!./some/path.html'.
If a plugin does not implement normalize, then the loader will try to normalize the resource name using the normal module name rules.
write is only used by the optimizer, and it only needs to be implemented if the plugin can output something that would belong in an optimized layer. It is called with the following arguments:
The text! plugin implements write, to write out a string value for the text file that it loaded. A snippet from that file:
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 is only used by the optimizer, and is only supported in 2.1.0 or later of the optimizer. It is called after the modules for the layer have been written to the layer. It is useful to use if you need some code that should go at the end of the layer, or if the plugin needs to reset some internal state.
One example: a plugin that needs to write out some utility functions at the beginning of a layer, as part of the first write call, and the plugin needs to know when to reset the internal state to know when to write out the utilities for the next layer. If the plugin implements onLayerEnd, it can get notified when to reset its internal state.
onLayerEnd is called with the following arguments:
writeFile is only used by the optimizer, and it only needs to be implemented if the plugin needs to write out an alternate version of a dependency that is handled by the plugin. It is a bit expensive to scan all modules in a project to look for all plugin dependencies, so this writeFile method will only be called if optimizeAllPluginResources: true is in the build profile for the RequireJS optimizer. writeFile is called with the following arguments:
See the text! plugin for an example of writeFile.
pluginBuilder can be a string that points to another module to use instead of the current plugin when the plugin is used as part of an optimizer build.
A plugin could have very specific logic that depends on a certain environment, like the browser. However, when run inside the optimizer, the environment is very different, and the plugin may have a write plugin API implementation that it does not want to deliver as part of the normal plugin that is loaded in the browser. In those cases, specifying a pluginBuilder is useful.
Some notes about using a pluginBuilder:
© jQuery Foundation and other contributors
Licensed under the MIT License.
http://requirejs.org/docs/plugins.html