Node 本身不是已经有模块加载器了吗? § 1

是的。Node它确实有。该加载器使用的是 CommonJS 模块格式。CommonJS 模块格式在浏览器环境中效果不佳,并且我也不认同CommonJS 模块格式中一些取舍的做法。通过在服务器端使用 RequireJS,你可以对所有模块使用一种格式,无论它们是在服务器端还是浏览器中运行。这样可以保留 RequireJS 在浏览器中提供的快速加载和易于调试的优势,并且无需担心在两种格式之间转换所带来的额外成本。

如果你希望为你的模块使用 define() 方法,但又希望在不使用 RequireJS 的情况下在 Node 中运行它们,请参阅下面的章节关于amdefine.

我能否使用已经以 CommonJS 模块格式编写好的 Node 模块? § 2

当然可以!RequireJS 的 Node 适配器 r.js 将会使用 Node 的 require 实现以及 Node 的搜索路径,如果该模块无法通过 RequireJS 的配置找到的话。因此你可以继续使用现有的 Node 模块而无需对其做出任何修改。

RequireJS 将首先使用其配置选项来查找模块。如果 RequireJS 无法使用其配置找到模块,则假定该模块是使用 Node 类型的模块和配置的模块。因此,只有当模块使用的是 RequireJS API 时才需要使用 RequireJS 配置模块位置。对于期望使用 Node 的 API 和配置/路径的模块,只需使用 Node 包管理器如npm来安装这些模块,不要使用 RequireJS 对这些模块的位置进行配置。

最佳实践:将仅用于 Node 的包/模块通过 npm 安装到项目中的node_modules目录下,但不要配置 RequireJS 去查找 node_modules 目录下的内容。同时避免使用相对模块 ID 引用那些仅用于 Node 的模块。所以,不要做类似这样的事情:require("./node_modules/foo/foo").

其他注意事项:

  • Node 版本的 RequireJS 只能加载本地磁盘上的模块——目前并不支持通过 HTTP 加载远程模块。
  • RequireJS 的配置选项如 map、packages、paths 只有在 RequireJS 自己加载模块时才生效。如果 RequireJS 需要调用 Node 的模块系统,原始的 ID 会被传递给 Node。如果你需要让某个 Node 模块配合 map 配置一起工作,内联的 define() 调用有效,请参考此邮件列表讨论。

我该如何使用? § 3

有两种方式获取 Node 适配器:

npm

使用npm进行安装:

npm install requirejs

此方法将安装最新版本。

下载 r.js

r.js 仓库获取源代码

  • 下载页面下载 r.js
  • 并将其放置在你的项目中。然后通过 "node dist.js" 生成 r.js,或从dist目录下的文件来运行优化器。

使用说明

这些说明假设你已通过 npm 安装了 'requirejs'。如果你直接使用 r.js 文件,则应将 require('requirejs') 替换为 require('./path/to/r.js')。基本用法如下:

  • require('requirejs')
  • 在配置中传入主 js 文件的 "require" 函数给 requirejs。

示例:

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

务必阅读第2节中的说明关于如何配置 RequireJS 以便它可以加载通过 npm 安装的仅用于 Node 的模块。

想查看一个更完整的示例,它通过 RequireJS 加载模块但使用 Node 原生模块处理其他任务,请参阅r.js 仓库中的嵌入测试

注意: requirejs([], function() {})在 RequireJS 2.1+ 中 will 调用函数回调是异步的(早期版本则是同步的)。然而,在 Node 中运行时,模块加载将使用同步 IO 调用完成,加载插件应该同步解析它们的 load 方法调用。这使得在 Node 中可以通过 requirejs('stringValue') 这样的方式同步使用 requirejs 模块:

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

使用 AMD 或 RequireJS 构建 Node 模块

如果你想编写一个模块,使其可以在 RequireJS 和 Node 中都能正常工作,而又不需要你的库在 Node 中的用户必须使用 RequireJS,那么你可以使用amdefineamdefine

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

包来实现这一点:只需确保按照上面所示的方式精确使用 'amdefine' 的 if 测试及其内容即可。空格和换行的不同是可以接受的。请参阅amdefine 项目了解更多详情。

如果你希望直接使用 RequireJS 编写模块,然后向 Node 导出模块值,使得其他 Node 程序无需 RequireJS 即可使用,你可以使用下一个示例中列出的方法。

最好显式设置 baseUrl 为你模块所在目录,这样它在嵌套在 node_modules 层次结构内部时才能正确工作。使用同步requirejs('moduleId')方法通过 requirejs 的配置和规则来获取模块,然后使用 Node 的 module.exports 向外导出你的模块值:

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

将优化器作为 Node 模块使用

Node 模块也以optimize通过函数调用而非命令行工具的方式揭露了 RequireJS 优化器的功能

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

这允许你构建其它的优化工作流,比如一个网页构建工具如果你更倾向于始终使用“在</body>标签之前包含一个脚本文件”的方式开发,就可以使用它。在Node中运行的优化器速度相当快,但对于那些不想每次浏览器请求都重新生成构建的大项目来说,仅当你修改了属于构建一部分的脚本时才重新生成构建即可。你可以使用Node的fs.watchFile()来监听文件,并在文件更改时触发构建。

反馈

如果你遇到问题并希望报告,请使用r.js GitHub Issues页面.