amidala

1.4.0 • Public • Published

Amidala: AMD for Node and the browser

Use AMD and CommonJS modules in both Node and the browser.

This package adapts CommonJS modules to AMD, providing a dynamic module loader (for browser use), but also makes AMD modules usable from Node.

Usage

var amidala = require('amidala');
var amd = amidala();
 
// Use a mixture of AMD and CommonJS modules
amd.require(['foo', 'bar'], function (foo, bar) {...});
 
var app = require('express')();
// Dynamic loading from web - location is arbitrary
app.use('/amidala/', amd.dynamic);
<script src="/amidala/"></script>
<script>
    // a CommonJS module installed via NPM
    require('uri-templates', function (UriTemplate) {
        ...
    });
</script> 

By default, the AMD loader script figures out where to load modules from by inspecting its own URL. This only works if you're loading it from a <script> tag in the page, otherwise you need to provide the .urls option.

What does it do?

On the server:

  • provides loading of AMD modules in Node, using the .require() method
  • provides a web-request handler function (.dynamic) which returns modules (including wrapper for NPM/CommonJS)

In the browser:

  • supplies define() or require() functions (following the AMD spec) which dynamically load dependencies from the server.

Security

Absolute and relative paths to modules are always disallowed.

This means that (by default) the only files this module loader will use/return will be from dependencies from node_modules/.

However, using the .path option it is possible to specify locations for modules (including relative modules). If you write an entry in .path that redirects to a folder, this gives users access to all suitable files in that folder, so make sure nothing sensitive is in there (even in hidden files).

Methods

First, construct an AMD loader using the options described below:

var amidala = require('amidala');
var amd = amidala({...});

The loader now has the following methods:

.require()

This method lets you use AMD modules on Node.js:

amd.require(['foo', 'bar'], function (Foo, Bar) {
    ...
});

The search locations for these modules can be specified using the .path option.

.dynamic

This provides a request listener (compatible with Express or http.createServer()). This listener:

  • provides a module loader (define() and require()) with dynamic loading
  • dynamically resolves and returns AMD/CommonJS modules

Options

.loader

This is the URL at which the main loader is accessible in the browser.

If the module is loaded in a subdirectory by Express, this URL is relative to that.

// loader URL is "/amidala/loader.js"
app.use('/amidala/', amidala({
    loader: '/loader.js'
}).dynamic);

If this is not specified, then it is taken to be the root:

// loader URL is "/amidala.js"
app.use('/amidala.js', amidala().dynamic);

.urls

This defines the URLs from which modules are requested/served This is passed to the client-side library, and it is not relative to any mounted roots.

By default, the loader will inspect its own URL (only works if loaded via a <script> element in the HTML source) and construct a template based on that:

// modules are loaded from "/amidala.js/..."
app.use(amidala({
    loader: '/amidala.js'
}).dynamic);

If this is a string, it's interpreted as a URI Template, using the variable name:

// modules are loaded from "/amd-modules/..."
app.use(amidala({
    loader: '/amidala.js',
    urls: '/amd-modules/{name}'
}).dynamic);

If it is an object or an array, it is taken to define a URI map between module names and URIs.

// modules are loaded from either "/amd-modules/..." or "/amd-local/..."
app.use(amidala({
    loader: '/amidala.js',
    urls: [
        // Separate rule for local modules, to prevent leading "." from disappearing
        {'./{+module}': '/amd-local/{+module}'},
        {'{+module}': '/amd-modules/{+module}'}
    ]
}).dynamic);

.path

With this option, you can specify locations (as files or folders) for some or all modules:

amd.dynamic({
    path: {
        "ui-components": __dirname + "/ui"
    }
})

These path definitions include submodules - so with the above, a call to require(['ui-components/xyz'], ...) will resolve to ..../ui/xyz instead.

You can provide an array for each entry, and it will attempt to resolve them in order:

amd.dynamic({
    path: {
        "./": [__dirname + '/static/amd/', __dirname + '/shared']
    }
})

The longest matches are checked first. You can use the empty string ("") to catch all modules not otherwise matched:

amd.dynamic({
    path: {
        "": __dirname + '/public/bower_components'
        "fs": "level-fs-browser"
    }
})

If you specify a string (or array) for .path, this is equivalent to using the empty string key (matches all modules):

amd.dynamic({
    path: __dirname + '/public/bower_components'
})

.extensions

This lets you provide a map from (lower-case) extensions to functions that assemble code from their contents. The result must contain a define() call for that module, but the module name can be implicit.

The functions take four arguments: sourceText, moduleName, moduleRelativeName, and callback. Use of callback is optional - if the function returns a string, then it will take that to be the result:

amd.dynamic({
    extensions: {
        "txt": function (sourceText) {
            return 'define([],' + JSON.stringify(sourceText) + ');';
        },
        "async-file": function (sourceText, name, relativeName, callback) {
            someAsyncOperation(function (error, code) {
                callback(error, code);
            });
        }
    }
});

If you need to resolve any relative module names, do that relative to moduleRelativeName instead.

For example, if the main entry point for a module some-module is /lib/main.js instead of /index.js, then moduleRelativeName will point to some-module/lib/main.js. This allows ./other.js to correctly resolve to /lib/other.js.

.transform

This lets you provide a function that transforms the module code before it is sent (for example, minification). This occurs after the any conversion in .extensions.

You can either return a transformed code string, or use an asynchronous callback:

var amd = amidala({
    transform: function (jsCode) {
        return '/*AMD*/\n' + jsCode;
    }
});
 
var amd = amidala({
    transform: function (jsCode, callback) {
        someAsyncApi(jsCode, {...}, callback);
    }
});

Enabling relative modules

Relative modules are not allowed - however, if they match an entry in .path, they can be rewritten to absolute locations:

amd.dynamic({
    path: {
        "./shared": __dirname + "/shared"
    }
});

This can be useful if you don't want to mess with the global namespace (e.g. occupying the global shared module).

However (as the security note above mentions), if the target is a directory then this gives users access to all files in that directory.

Package Sidebar

Install

npm i amidala

Weekly Downloads

1

Version

1.4.0

License

ISC

Last publish

Collaborators

  • geraintluff