amidala
v1.4.0
Published
Use both AMD/CommonJS modules in the browser/Node, with dynamic loading for browser
Downloads
8
Maintainers
Readme
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()
orrequire()
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()
andrequire()
) 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.