RequireJS in Node

优质
小牛编辑
131浏览
2023-12-01

Doesn't Node already have a module loader?

Yes Node does. That loader uses the CommonJS module format. The CommonJS module format is non-optimal for the browser, and I do not agree with some of the trade-offs made in the CommonJS module format. By using RequireJS on the server, you can use one format for all your modules, whether they are running server side or in the browser. That way you can preserve the speed benefits and easy debugging you get with RequireJS in the browser, and not have to worry about extra translation costs for moving between two formats.

If you want to use define() for your modules but still run them in Node without needing to run RequireJS on the server, see the section below about using amdefine.

Can I use Node modules already written in the CommonJS module format?

Yes! The Node adapter for RequireJS, called r.js, will use Node's implementation of require and Node's search paths if the module is not found with the configuration used by RequireJS, so you can continue to use your existing Node-based modules without having to do changes to them.

RequireJS will use its Configuration Options first to find modules. If RequireJS cannot find the module with its configuration, it is assumed to be a module that uses Node's type of modules and configuration. So, only configure module locations with RequireJS if they use the RequireJS API. For modules that expect Node's APIs and configuration/paths, just install them with a Node package manager, like npm, and do not configure their locations with RequireJS.

Best practice: Use npm to install Node-only packages/modules into the projects node_modules directory, but do not configure RequireJS to look inside the node_modules directory. Also avoid using relative module IDs to reference modules that are Node-only modules. So, do not do something like require("./node_modules/foo/foo").

Finally, RequireJS in Node can only load modules that are on the local disk -- fetching modules across http, for instance, is not supported at this time.

How do I use it?

There are two ways to get the Node adapter:

npm

Use npm to install it:

npm install requirejs

This option will install the latest release.

Download r.js

If you prefer to not use npm, you can get r.js directly:

  • Download r.js from the the download page and place it in your project.
  • Get the source from the r.js repo and either generate the r.js via "node dist.js", or grab a snapshot from the dist directory.

Usage

These instructions assume an npm installation of 'requirejs'. If you are using the r.js file directly, replace require('requirejs') with require('./path/to/r.js'). Basic usage is:

  • require('requirejs')
  • Pass the main js file's "require" function in the configuration to requirejs.

Example:

var requirejs = require('requirejs');
requirejs.config({
    //Pass the top-level main.js/index.js require
    //function to requirejs so that node modules
    //are loaded relative to the top-level JS file.
    nodeRequire: require
});
requirejs(['foo', 'bar'],
function   (foo,   bar) {
    //foo and bar are loaded according to requirejs
    //config, but if not found, then node's require
    //is used to load the module.
});

Be sure to read the notes in section 2 about configuring RequireJS so that it can load node-only modules installed via npm.

To see a more complete example that loads a module via RequireJS but uses Node-native modules for other things, see the embedded test in the r.js repo.

Note: requirejs([], function() {}) will call the function callback asynchronously in RequireJS 2.1+ (for earlier versions it was synchronously called). However, when running in Node, module loading will be loaded using sync IO calls, and loader plugins should resolve calls to their load method synchronously. This allows sync uses of the requirejs module in node to work via requirejs('stringValue') calls:

//Retrieves the module value for 'a' synchronously
var a = requirejs('a')

Building node modules with AMD or RequireJS

If you want to code a module so that it works with RequireJS and in Node, without requiring users of your library in Node to use RequireJS, then you can use the amdefine package to do this:

if (typeof define !== 'function') {
    var define = require('amdefine')(module);
}
define(function(require) {
    var dep = require('dependency');
    //The value returned from the function is
    //used as the module export visible to Node.
    return function () {};
});

The RequireJS optimizer, as of version 1.0.3, will strip out the use of 'amdefine' above, so it is safe to use this module for your web-based projects too. Just be sure to use the exact 'amdefine' if() test and contents as shown above. Differences in spaces/line breaks are allowed. See the amdefine project for more information.

If you want to use RequireJS directly to code your module, and then export a module value to node so that it can be used in other Node programs without requiring that app to use RequireJS, you can use the approach listed in the next example.

It is best to set the baseUrl specifically to the directory containing the module, so that it works properly when nested inside a node_modules heirarchy. Use the synchronous requirejs('moduleId') to fetch the module using the config and rules in requirejs, then use Node's module.exports to export your module value:

var requirejs = require('requirejs');
requirejs.config({
    //Use node's special variable __dirname to
    //get the directory containing this file.
    //Useful if building a library that will
    //be used in node but does not require the
    //use of node outside
    baseUrl: __dirname,
    //Pass the top-level main.js/index.js require
    //function to requirejs so that node modules
    //are loaded relative to the top-level JS file.
    nodeRequire: require
});
//foo and bar are loaded according to requirejs
//config, and if found, assumed to be an AMD module.
//If they are not found via the requirejs config,
//then node's require is used to load the module,
//and if found, the module is assumed to be a
//node-formatted module. Note: this synchronous
//style of loading a module only works in Node.
var foo = requirejs('foo');
var bar = requirejs('bar');
//Now export a value visible to Node.
module.exports = function () {};

Using the optimizer as a node module

The node module also exposes the RequireJS Optimizer as an optimize method for using the RequireJS optimizer via a function call instead of a command line tool:

var requirejs = require('requirejs');
var config = {
    baseUrl: '../appDir/scripts',
    name: 'main',
    out: '../build/main-built.js'
};
requirejs.optimize(config, function (buildResponse) {
    //buildResponse is just a text output of the modules
    //included. Load the built file for the contents.
    //Use config.out to get the optimized file contents.
    var contents = fs.readFileSync(config.out, 'utf8');
}, function(err) {
    //optimization err callback
});

This allows you to build other optimization workflows, like a web builder that can be used if you prefer to always develop with the "one script file included before the </body> tag" approach. The optimizer running in Node if fairly fast, but for larger projects that do not want to regenerate the build for every browser request, but just if you modify a script that is part of the build. You could use Node's fs.watchFile() to watch files and then trigger the build when a file changes.

Feedback

If you find you have a problem, and want to report it, use the r.js GitHub Issues page.

If you want to discuss the RequireJS-Node integration, you can use the RequireJS Group.