Require.js让我感到头疼。关于它加载脚本/模块的方式有一些基本问题。

46

假设这是我的config.js或main.js文件:

require.config({
    // paths are analogous to old-school <script> tags, in order to reference js scripts
    paths: {
        jquery: "libs/jquery-1.7.2.min",
        underscore: "libs/underscore-min",
        backbone: "libs/backbone-min",
        jquerymobile: "libs/jquery.mobile-1.1.0.min",
        jquerymobilerouter: "libs/jquery.mobile.router.min"
    },
    // configure dependencies and export value aliases for old-school js scripts
    shim: {
        jquery: ["require"],
        underscore: {
            deps: ["jquery"],
            exports: "_"
        },
        backbone: {
            deps: ["underscore", "jquery"],
            exports: "Backbone"
        },
        jquerymobilerouter: ["jquery", "backbone", "underscore"],
        jquerymobile: ["jquery", "jquerymobilerouter", "backbone", "underscore"]
    }
});
require(["jquery", "backbone", "underscore", "app/app.min", "jquerymobilerouter", "jquerymobile"], function ($, Backbone, _, App) {
    console.log($);
    console.log(Backbone);
    console.log(_);
    $("body").fadeIn(function () {
        App.init();
    });
});
  1. 如果我理解正确,paths 配置选项允许您引用脚本,就像 HTML 中的 <script> 标签一样。假设这是正确的,那么在我的实际 require 语句下,我是否仍然需要使用 $_ 别名来别名化像 jQuery 这样的脚本?如果您使用标准的 <script> 标签引用 jQuery,则可以自动在整个脚本中使用 $。那么使用 paths 不应该也是一样吗?

  2. 我对 shim 配置选项比较陌生,我了解到它已经替换了不再使用的 order! 插件。但是,exports 属性实际上是用来做什么的呢?它似乎并没有为脚本创建一个别名;例如,如果我将 underscore 的 exports 设置为 "whatever",然后尝试 console.log(whatever),它的值为 undefined。那么它的作用是什么呢?

  3. 像 jQuery 这样的脚本应该如何正确地在“全局”中使用?也就是说,使用 $ 别名在我的 App.js 模块或任何其他模块中能否使用?我是否必须在每个单独的模块中 require jQuery 并且每次都需要别名化 $?还是我在这里的做法是正确的?

我非常感谢对这个特定脚本的任何其他批评;我认为 Require.js 的文档还有很大的提升空间;一些我想更多了解的事情似乎被忽略了,让我很困惑。


12
我希望我能将这个问题的截图发送给每一个新用户,作为如何正确提问的示例。 - Mike Robinson
2
@MikeRobinson 是的,那是真的。最近我没有时间回答问题,但对于这样一个好问题,我会尽力回答它。 - Nicola Peluchetti
2个回答

23
为了澄清有关 exports 的任何疑惑,假定任何 shim 库都会将属性附加到全局上下文(windowroot),或修改已存在的全局属性(例如 jQuery 插件)。当 requireJS 获取命令以加载经过 shim 处理的依赖项时,它会检查全局上下文是否存在与该 shim 配置的 exports 值匹配的属性,如果找到它,则将其作为该模块的值返回。如果没有找到它,那么它就会加载相关的脚本,等待其执行,然后查找全局符号并返回它。
需要记住的一个重要事实是,除非 shim 配置包含 exports 值,否则该配置上的任何 init 方法都不会被执行。在该模块可以初始化之前,依赖项加载器必须找到该模块的值(即 exports 指定的内容),这就是为什么如果存在用于该模块的 shim init,则需要该属性的原因。
更新:我还需要指出,如果涉及的模块在任何地方调用了define,那么您为该模块设置的任何shim配置都将被忽略。这实际上给我带来了一些麻烦,因为我想使用shim配置来调用jQuery的jQuery.noConflict(true)方法来取消全局作用域并将其限制在仅需要它的模块中,但我无法使其正常工作。(有关如何使用map配置轻松完成此操作而不是使用shim配置,请参见底部的更新信息。)
更新2:最近在requireJS谷歌组中提出的一个问题让我意识到我的解释可能有点误导,所以我想澄清一下。只有在至少加载了一次shimmed依赖项后,RequireJS才会重新使用它。也就是说,如果您只是在托管页面上(例如underscore)上有一个<script>标记,像这样:
<script src='lib/underscore.js'></script>
<script src='lib/require.js' data-main='main.js'></script>

......并且你的requireJS配置中有类似于这样的内容:

paths: {
    'underscore': 'lib/underscore'
},
shim: {
    'underscore': {
        exports: '_'
    }
}

当你第一次执行define(['underscore'], function (_) {});var _ = require('underscore');时,RequireJS 会重新加载 underscore 库,而不是重用之前定义的 window._,因为在 RequireJS 看来,你之前从未加载过 underscore。它可以检查根作用域上是否已经定义了 _,但它无法验证已经存在的 _ 是否与你在 paths 配置中定义的那个相同。例如,prototypejquery 默认都将自己分配给 window.$,如果 requireJS 假定 'window.$' 是 jQuery,而实际上是 prototype,你就会陷入困境。
所有这些意味着,如果你混合使用脚本加载样式,你的页面最终会变成这样:
 <script src='lib/underscore.js'></script>
 <script src='lib/require.js' data-main='main.js'></script>
 <script src='lib/underscore.js'></script>

第二个下划线实例是由requireJS加载的。

基本上,要使requireJS知道库的存在,必须通过requireJS加载该库。然而,下一次需要underscore时,requireJS会说“嘿,我已经加载了它,所以只需返回exports值,不要担心再加载另一个脚本。”

这意味着您有两个真正的选择。其中一个是我认为是反模式:简单地不使用requireJS来表达全局脚本的依赖关系。也就是说,只要库将全局附加到根上下文中,即使未明确要求该依赖项,您也可以访问它。您可以看到这为什么是反模式-您基本上消除了使用AMD加载器(显式依赖项列表和可移植性)的大部分优势。

另一个更好的选择是使用requireJS加载所有内容,只需创建一个初始加载requireJS的实际脚本标签。您可以使用shims,但95%的情况下,将AMD包装器添加到脚本中实际上并不那么困难。将所有非AMD库转换为AMD兼容可能需要更多的工作,但一旦完成了一两个,就会变得更加容易-我可以在不到一分钟的时间内将任何通用的jQuery插件转换为AMD模块。这通常只涉及添加。
define(['jquery'], function (jQuery) {

在顶部,以及

    return jQuery;
});

在底部。我将“jquery”映射到jQuery而不是$的原因是,我注意到大多数插件现在都被包装在这样的闭包中:
(function ($) {
    // plugin code here
})(jQuery);

建议注意插件的预期范围。假设插件不是期望找到jQuery而是$,你可以将'jquery'直接映射到$。这只是基本的AMD包装器-更复杂的包装器通常会尝试检测使用了哪种加载程序(commonJS vs AMD vs普通的全局),并根据结果使用不同的加载方法。你可以在谷歌上花几秒钟就能找到这方面的例子。

更新:我用来支持在RequireJS中使用jQuery.noConflict(true)的解决方法虽然有效,但需要对jQuery源代码进行非常小的修改。后来我找到了一种更好的方法来完成相同的任务,而且幸运的是,RequireJS的作者James Burke也找到了这种方法,并将其添加到了RequireJS文档中:http://requirejs.org/docs/jquery.html#noconflictmap


2
这可能是关于RequireJS shim用法的最有帮助的解释之一。'exports:'属性经常被误解为RequireJS应该将脚本附加到哪里的指示,而实际上它是RequireJS查找脚本的地方,表明它已准备就绪。谢谢! - Micros
1
很高兴能帮到你!我花了很长时间来研究requireJS,所以我被迫学习它的特点,并且我想分享我所学到的内容可能会帮助其他人避免我经历过的相同的头痛。 - Isochronous

22
  1. Paths tell require.js where to look when you require that dependency.

    For example i have things configured like this:

    "paths": { 
        "jquery": "require_jquery"
    },
    "shim": {
        "jquery-cookie"  : ["jquery"],
        "bootstrap-tab"  : ["jquery"],
        "bootstrap-modal": ["jquery"],
        "bootstrap-alert": ["jquery"]
    },
    

    this means that every time in a module I do

    define( ['jquery']
    

    requirejs loads the file require_jquery from the main path instead of trying to load jquery.js. In your case it would load the jQuery source file, which would then be globally available. I personally don't like that approach and for that reason in the require_jquery.js file I do:

    define( ["jquery_1.7.2"], function() {
        // Raw jQuery does not return anything, so return it explicitly here.
        return jQuery.noConflict( true );
    } );
    

    which means that jQuery will be defined only inside my modules. (This is because i write Wordpress plugins and so I can include my own version of jQuery without touching the outside version)

  2. Exports (reading from the docs simply should be the name of the module you are using so that it can be detected if loading went correctly. Here is explained. So if you want to set an export for underscore it should be _

  3. jQuery should be global as I explained, if you simply import it the file is executed and jQuery is global

编辑 - 回答评论。

  1. yes i mean that, you must export $ or jQuery for jQuery and _ for backbone. From what i got from the docs this is needed only in some edge cases and would not be necessary for libraries that declare themselves in the global namespace as jQuery.

    I think that requirejs needs them when it has to fallback from loading jQuery from a CDN. i think that requirejs first tries to load jQuery from the CDN, then makes a check to verify that it was loaded correctly by checking that the "exported" variable exists, and if it doesn't it loads it form the local filesystem (if you had configured fallbacks, of course). This is something that it's needed when requirejs can't see a 404 coming back.

  2. jQuery is globally available because it's declared global. If you simply load and execute the jQuery script, you will end up with two globals, $ and jQuery (or you can do as i did and avoid that). Inside the define() function you can alias jQuery to be whatever you want.

    define( [ 'jquery' ], function( jq ) {
        // jq is jquery inside this function. if you declared it 
        // globally it will be also available as $ and jQuery
    } );
    

1
这为我解决了问题,谢谢。不过还有两个问题:1.) 当你说“exports”应该是模块的名称时,你是指库的实际别名吗?比如像jQuery的“$”一样?这是否意味着你不能随意分配导出值?我已经阅读了Requirejs文档,但并没有什么帮助。2.) 我不确定我是否理解了你关于jQuery全局可用性的例子。它之所以全局可用,是因为我使用了路径别名,还是因为它在定义块中被别名了? - J. Ky Marsh
@J.KyMarsh,我修改了我的答案。 - Nicola Peluchetti
4
我同意你的看法。虽然RequireJS文档非常庞大,但对我帮助不大。 - flu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接