Plugins

Intro § 1

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.

Plugin Names § 2

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. However, when a plugin runs in the optimizer, it cannot have any dependencies (see the pluginBuilder approach to make that easier).

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) {
});

Note that before 0.24.0, the text!, i18n! and order! plugins were "special" in that you only need to use a name like "text" to refer to it, but the actual module name was "require/text". This was done to support old code that was authored before the Plugin API was fully fleshed out. That magic module name transformation is no longer done in 0.24.0 and greater.

API § 3

RequireJS will load the plugin module first, then pass it 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: function (name, parentRequire, load, config) § 3.1

load is a function, and it will be called with the following arguments:

An example plugin that does not do anything interesting, just does a normal require to load a JS module:


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

Some plugins may need to evaluate some JavaScript that was retrieved as text. To evaluate the JavaScript correctly, particularly if it contains require() or define() calls, there is a function off the load() argument, load.fromText(), that can be used to evaluate the JavaScript. eval() is used by RequireJS to evaluate that JavaScript.

Arguments for load.fromText():

An example plugin's load function that uses load.fromText():


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

        //Use a method to load the text (provide 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.
            load.fromText(name, text);

            //Now get a handle on the evaluated module,
            //to return that value for this plugin-loaded
            //resource
            req([name], function (value) {
                load(value);
            });
        });
    }
});

normalize: function (name, normalize) § 3.2

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, load, config) {
            req([parse(name).choice], function (value) {
                load(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: function (pluginName, moduleName, write) § 3.3

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");
    }
}

pluginBuilder § 3.4

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: