Modules/LoaderPlugin

From CommonJS Spec Wiki
Jump to: navigation, search

STATUS: PROPOSAL

Implementations
[[Implementations/PINF (partial)|]], [[Implementations/curl.js|]]

Loader plugins are modules that can be referenced in a module id for specialized loading of a resource or module. This provides a means for depending on resources besides CommonJS modules. The Modules/AsynchronousDefinition API specifies a mechanism for defining modules such that the module can be executed in a <script> and dependencies can be declared for asynchronous loading for prior to execution of a factory, and loader plugins are particularly important with AMD since non-module required resources can also be loaded asynchronously prior factory execution, to ensure they are available for synchronous execution.

Specification

A module id that uses a loader plugin shall use a '!' to delineate between the loader plugin module to use and the target resource to load:

 <loader-plugin-module-id>!<target-resource>

When a module loader needs to resolve a module id in the loader plugin form, it shall load the loader plugin module referenced by the string before the '!' character. It shall then call the function exported as the "load" property from loader plugin module exports. The load() function shall be called with the following signature:

 loaderModulePlugin.load(targetResourceId, parentRequire, loaded)

The first argument is the remaining string after the '!' character in the module id that is being resolved. The second argument is the require() (or equivalent) function that was or would be provided to the calling module, that is the module that is requesting the resource or module using a module id with the loader plugin form. The third argument is a callback function that shall be called when the resource is resolved and it shall be called with a single argument, the resolved exported value of the referenced module id.

For example, if a module was defined:

 define("bar", ["foo!resource", "require"], function(fr, require){
   barsRequire = require;
 });

The "foo" module's load() should be called with "resource" as the first argument, a function equivalent to the value barRequire as the second parameter, and the callback for the completion of loading "foo!resource" as the third argument.

require.toUrl

In order to provide sufficient tools for plugins to determine target resources, the require() function should provide a function as the "toUrl" property that takes a resource name and returns a URL:

 require.toUrl(<resource-name>)

The toUrl function shall use the same resolution mechanism as the require() function it is attached to, except that no file extension should be added by toUrl(). For example, require.toUrl("./module.js") should return the URL that returns the module source for the module whose exports would be returned by require("./module").

Build Integration

Tools can build a concatenated and minified script that contains multiple modules and resources. Loader plugin may interface with such tools to ensure that referenced resources can be included in the build file. Loaded plugins may provide build interaction with a loader plugin builder module. The loader plugin builder module may be referenced in a loader plugin with the following syntax:

 // plugin-builder: <builder-module-id>

The loader plugin builder module should export a function in the "build" property of the exports with the following signature:

 pluginBuilder.build(writeToBuiltFile)

The single argument, writeToBuiltFile, is a function that may be called with a string argument. When writeToBuiltFile is called, the string is outputted in the built script file. The build function shall return a function with the following signature:

 load(targetResourceId, parentRequire);

This function will be called each time a module id is encountered that uses the associated plugin. The first argument is the string after the '!'. The second argument may be included to indicate the dependency on another module. Typically a build would then include that module in the built script file.

Examples

We could define a plugin for loading resources in raw text format like:

 text.js:
 // plugin-builder: ./text-builder
 define([], function(){
   return {
     load: function(targetResourceId, parentRequire, loaded){
       httpRequest(parentRequire.toUrl(targetResourceId)).then(loaded);
     };
   };
 });

And we could use the text plugin:

 define(["text!./template.html"], function(template){
   // template will be the contents of template.html
 });

Usage notes

If the module plugin supports synchronous loading of a resource, it may, by convention, allow for the third argument (loaded) to be optional. If the plugin supports an optional third argument, the absence of the third argument indicates that the resolve resource exported value should be synchronously returned.

Module loaders generally can not safely cache plugin-based resources, as the resolution algorithm is determined by the plugin, and can not be assumed by the module loader. Plugins can implement their own caching, if they want.