RequireJS进阶:配置文件的学习

555 查看

概述

Requires强大灵活的运用是通过配置文件决定的。通过配置文件我们可以给模块取别名、给模块加上版本标识、设置模块依赖、包装非模块等强大功能。同时RequireJS的优化器也大量使用了配置选项,如果你使用grunt、gulp等构建工具的话,也有必要深入的学习配置文件的使用。

下面通过示例来进行深度的探讨配置文件的使用。

配置文件的位置

配置文件的位置和声明用法是相对于Requires这个脚本文件来决定的。假如配置文件在Requires脚本的下方,则可以这样使用:

<script src="scripts/require.js"></script>
<script>
  require.config({
    baseUrl: "/another/path",
    paths: {
        "some": "some/v1.0"
    },
    waitSeconds: 15
  });

require( ["some/module", "my/module", "a.js", "b.js"],
    function(someModule,    myModule) {
        //This function will be called when all the dependencies
        //listed above are loaded. Note that this function could
        //be called before the page is loaded.
        //This callback is optional.
    }
  );
</script>

这种情况一般都是把配置参数写到require.config里面。如果配置文件在Requires脚本的上面,则是另外一种用法了,示例代码如下所示:

<script>
    var require = {
        baseUrl: "/another/path",
        paths: {
            "some": "some/v1.0"
        },
        waitSeconds: 15
    };
</script>
<script src="scripts/require.js"></script>

这种情况下由于require.js后加载,那么直接使用require.config就会报错,这时候就只能通过声明一个全局的变量来注入配置参数来实现了。

这时,你可能会发问了,改用那种情况呢?我只能说根据你具体的业务来选择某种场景。配置是活的,人也是活得。业务也是活的,所以这个没有统一的答案,再有甚者,你可以两个结合使用。

除了要注意配置文件的位置要灵活使用之外,以下有几点还是要注意的,这算是最佳实践吧。

  • 配置文件单独成一个文件,不要跟具体的业务模块耦合。
  • 最好使用 var require = {} 的形式而不是 window.require = {}的形式。后者在IE中运行不正常。

在决定配置位置后,下面就是配置文件的参数了,RequireJS包含了大量的参数,这也是配置文件的核心,具体明细如下。

配置文件参数的介绍

baseUrl

所有模块的查找根路径。所以上面的示例中,"my/module"的标签src值是"/another/path/my/module.js"。当加载纯.js文件(依赖字串以/开头,或者以.js结尾,或者含有协议),不会使用baseUrl。因此a.js及b.js都在包含上述代码段的HTML页面的同目录下加载。

如未显式设置baseUrl,则默认值是加载require.js的HTML所处的位置。如果用了data-main属性,则该路径就变成baseUrl。(所以最佳实践还是推重使用baseUrl

baseUrl可跟require.js页面处于不同的域下,RequireJS脚本的加载是跨域的。唯一的限制是使用text! plugins加载文本内容时,这些路径应跟页面同域,至少在开发时应这样。优化工具会将text! plugin资源内联,因此在使用优化工具之后你可以使用跨域引用text! plugin资源的那些资源。

paths

path映射那些不直接放置于baseUrl下的模块名(这相当于跟冗长的模块名取个简介的名字)。设置path时起始位置是相对于baseUrl的,除非该path设置以"/"开头或含有URL协议(如http:)。在上述的配置下,"some/module"的script标签src值是"/another/path/some/v1.0/module.js"。

用于模块名的path不应含有.js后缀,因为一个path有可能映射到一个目录。路径解析机制会自动在映射模块名到path时添加上.js后缀。在文本模版之类的场景中使用require.toUrl()时它也会添加合适的后缀。

在浏览器中运行时,可指定路径的备选(fallbacks),以实现诸如首先指定了从CDN中加载,一旦CDN加载失败则从本地位置中加载这类的机制。(回调函数,这个可以跟踪脚本加载是否成功。)

ps:paths映射的模块不一定是标准的AMD模块,但是最佳实践推荐是有意义的模块。

shim

为那些没有使用define()来声明依赖关系、设置模块的"浏览器全局变量注入"型脚本做依赖和导出配置。

下面有个示例,它需要 RequireJS 2.1.0+,并且假定backbone.js、underscore.js 、jquery.js都装于baseUrl目录下。如果没有,则你可能需要为它们设置paths config:

requirejs.config({
    //Remember: only use shim config for non-AMD scripts,
    //scripts that do not already call define(). The shim
    //config will not work correctly if used on AMD scripts,
    //in particular, the exports and init config will not
    //be triggered, and the deps config will be confusing
    //for those cases.
    shim: {
        'backbone': {
            //These script dependencies should be loaded before loading
            //backbone.js
            deps: ['underscore', 'jquery'],
            //Once loaded, use the global 'Backbone' as the
            //module value.
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        },
        'foo': {
            deps: ['bar'],
            exports: 'Foo',
            init: function (bar) {
                //Using a function allows you to call noConflict for
                //libraries that support it, and do other cleanup.
                //However, plugins for those libraries may still want
                //a global. "this" for the function will be the global
                //object. The dependencies will be passed in as
                //function arguments. If this function returns a value,
                //then that value is used as the module export value
                //instead of the object found via the 'exports' string.
                //Note: jQuery registers as an AMD module via define(),
                //so this will not work for jQuery. See notes section
                //below for an approach for jQuery.
                return this.Foo.noConflict();
            }
        }
    }
});

//Then, later in a separate file, call it 'MyModel.js', a module is
//defined, specifying 'backbone' as a dependency. RequireJS will use
//the shim config to properly load 'backbone' and give a local
//reference to this module. The global Backbone will still exist on
//the page too.
define(['backbone'], function (Backbone) {
  return Backbone.Model.extend({});
});

RequireJS 2.0.*中,shim配置中的"exports"属性可以是一个函数而不是字串。这种情况下它就起到上述示例中的"init"属性的功能。 RequireJS 2.1.0+中加入了"init"承接库加载后的初始工作,以使exports作为字串值被enforceDefine所使用。

那些仅作为jQuery或Backbone的插件存在而不导出任何模块变量的"模块"们,shim配置可简单设置为依赖数组:

requirejs.config({
    shim: {
        'jquery.colorize': ['jquery'],
        'jquery.scroll': ['jquery'],
        'backbone.layoutmanager': ['backbone']
    }
});

但请注意,若你想在IE中使用404加载检测以启用path备选(fallbacks)或备错(errbacks),则需要给定一个字串值的exports以使loader能够检查出脚本是否实际加载了(init中的返回值不会用于enforceDefine检查中):

requirejs.config({
    shim: {
        'jquery.colorize': {
            deps: ['jquery'],
            exports: 'jQuery.fn.colorize'
        },
        'jquery.scroll': {
            deps: ['jquery'],
            exports: 'jQuery.fn.scroll'
        },
        'backbone.layoutmanager': {
            deps: ['backbone']
            exports: 'Backbone.LayoutManager'
        }
    }
});

"shim"配置的重要注意事项:

  • shim配置仅设置了代码的依赖关系,想要实际加载shim指定的或涉及的模块,仍然需要一个常规的require/define调用。设置shim本身不会触发代码的加载。

  • 请仅使用其他"shim"模块作为shim脚本的依赖,或那些没有依赖关系,并且在调用define()之前定义了全局变量(如jQuery或lodash)的AMD库。否则,如果你使用了一个AMD模块作为一个shim配置模块的依赖,在build之后,AMD模块可能在shim托管代码执行之前都不会被执行,这会导致错误。终极的解决方案是将所有shim托管代码都升级为含有可选的AMD define()调用。

"shim"配置的优化器重要注意事项:

  • 您应当使用 mainConfigFile build配置项来指定含有shim配置的文件位置,否则优化器不会知晓shim配置。另一个手段是将shim配置复制到build profile中。

  • 不要在一个build中混用CDN加载和shim配置。示例场景,如:你从CDN加载jQuery的同时使用shim配置加载依赖于jQuery的原版Backbone。不要这么做。您应该在build中将jQuery内联而不是从CDN加载,否则build中内联的Backbone会在CDN加载jQuery之前运行。这是因为shim配置仅延时加载到所有的依赖已加载,而不会做任何define的自动装裹(auto-wrapping)。在build之后,所有依赖都已内联,shim配置不能延时执行非define()的代码。define()的模块可以在build之后与CDN加载代码一并工作,因为它们已将自己的代码合理地用define装裹了,在所有的依赖都已加载之前不会执行。因此记住:shim配置仅是个处理非模块(non-modular)代码、遗留代码的将就手段,如可以应尽量使用define()的模块。

  • 对于本地的多文件build,上述的CDN加载建议仍然适用。任何shim过的脚本,它们的依赖必须加载于该脚本执行之前。这意味着要么直接在含有shim脚本的build层build它的依赖,要么先使用require([], function (){})调用来加载它的依赖,然后对含有shim脚本的build层发出一个嵌套的require([])调用。

  • 如果您使用了uglifyjs来压缩代码,不要将uglify的toplevel选项置为true,或在命令行中不要使用 -mt。 该选项会破坏shim用于找到exports的全局名称。

ps:shim配置是所有配置中最繁琐也是最容易出问题的选项,我一般用来处理css依赖,最佳实践是能不用,尽量不用shim配置。

map

对于给定的模块前缀,使用一个不同的模块ID来加载该模块。

该手段对于某些大型项目很重要:如有两类模块需要使用不同版本的"foo",但它们之间仍需要一定的协同。 在那些基于上下文的多版本实现中很难做到这一点。而且,paths配置仅用于为模块ID设置root paths,而不是为了将一个模块ID映射到另一个。

map示例:

requirejs.config({
    map: {
        'some/newmodule': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

如果各模块在磁盘上分布如下:

  • foo1.0.js
  • foo1.2.js
  • some/

    • newmodule.js
    • oldmodule.js

当“some/newmodule”调用了“require('foo')”,它将获取到foo1.2.js文件;而当“some/oldmodule”调用“`require('foo')”时它将获取到foo1.0.js。

该特性仅适用于那些调用了define()并将其注册为匿名模块的真正AMD模块脚本。并且,请在map配置中仅使用绝对模块ID,“../some/thing”之类的相对ID不能工作。

另外在map中支持“*”,意思是“对于所有的模块加载,使用本map配置”。如果还有更细化的map配置,会优先于“*”配置。示例:

requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

意思是除了“some/oldmodule”外的所有模块,当要用“foo”时,使用“foo1.2”来替代。对于“some/oldmodule”自己,则使用“foo1.0”。

PS:map提供了统一脚本,不同版本的支持。

config

常常需要将配置信息传给一个模块。这些配置往往是application级别的信息,需要一个手段将它们向下传递给模块。在RequireJS中,基于requirejs.config()的config配置项来实现。要获取这些信息的模块可以加载特殊的依赖“module”,并调用module.config()。示例:

requirejs.config({
    config: {
        'bar': {
            size: 'large'
        },
        'baz': {
            color: 'blue'
        }
    }
});

//bar.js, which uses simplified CJS wrapping:
//http://requirejs.org/docs/whyamd.html#sugar
define(function (require, exports, module) {
    //Will be the value 'large'
    var size = module.config().size;
});

//baz.js which uses a dependency array,
//it asks for the special module ID, 'module':
//https://github.com/jrburke/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#wiki-magic
define(['module'], function (module) {
    //Will be the value 'blue'
    var color = module.config().color;
});

若要将config传给包,将目标设置为包的主模块而不是包ID:

requirejs.config({
    //Pass an API key for use in the pixie package's
    //main module.
    config: {
        'pixie/index': {
            apiKey: 'XJKDLNS'
        }
    },
    //Set up config for the "pixie" package, whose main
    //module is the index.js file in the pixie folder.
    packages: [
        {
            name: 'pixie',
            main: 'index'
        }
    ]
});

PS: config在我的项目中没有被用到过,不过值得关注,另外对于第二种情况,本人不太熟悉,后期会开一篇文章介绍。

packages

从CommonJS包(package)中加载模块。参见从包中加载模块。(链接:http://requirejs.cn/docs/commonjs.html

PS:目前还没遇到这样的场景,关注中...

nodeIdCompat

Node treats module ID example.js and example the same. By default these are two different IDs in RequireJS. If you end up using modules installed from npm, then you may need to set this config value to true to avoid resolution issues。

(翻译:Node对待模块example.js和example是一样的.默认在RequireJS中有两个不同的标识。如果你通过npm安装且使用模块结束,那么你可能需要设置这个参数为true来避免这个问题。)

waitSeconds

在放弃加载一个脚本之前等待的秒数。设为0禁用等待超时。默认为7秒。

context

命名一个加载上下文。这允许require.js在同一页面上加载模块的多个版本,如果每个顶层require调用都指定了一个唯一的上下文字符串。想要正确地使用,请参考多版本支持一节。

deps

指定要加载的一个依赖数组。当将require设置为一个config object在加载require.js之前使用时很有用。一旦require.js被定义,这些依赖就已加载。使用deps就像调用require([]),但它在loader处理配置完毕之后就立即生效。它并不阻塞其他的require()调用,它仅是指定某些模块作为config块的一部分而异步加载的手段而已。

callback

在deps加载完毕后执行的函数。当将require设置为一个config object在加载require.js之前使用时很有用,其作为配置的deps数组加载完毕后为require指定的函数。

enforceDefine

如果设置为true,则当一个脚本不是通过define()定义且不具备可供检查的shim导出字串值时,就会抛出错误。参考在IE中捕获加载错误一节。

xhtml

如果设置为true,则使用document.createElementNS()去创建script元素。

urlArgs

RequireJS获取资源时附加在URL后面的额外的query参数。作为浏览器或服务器未正确配置时的“cache bust”手段很有用。使用cache bust配置的一个示例:

urlArgs: "bust=" +  (new Date()).getTime()

在开发中这很有用,但请记得在部署到生成环境之前移除它。

scriptType

指定RequireJS将script标签插入document时所用的type=""值。默认为“text/javascript”。想要启用Firefox的JavaScript 1.8特性,可使用值“text/javascript;version=1.8”。

skipDataMain

Introduced in RequireJS 2.1.9: If set to true, skips the data-main attribute scanning done to start module loading. Useful if RequireJS is embedded in a utility library that may interact with other RequireJS library on the page, and the embedded version should not do data-main loading.

(翻译:在2.1.9中被引入:如果设置为true,就会跳过data-main属性的扫描启动模块的加载。如果RequireJS嵌入到一个通用的库中以其他页面中的RequireJS交互,且嵌入的版本不会使用data-main加载)

PS:翻译不标准,请告知,另外这个属性用的不多,暂时没什么实践经验。

帮助文档

官网:http://requirejs.org/docs/api.html#config
中文翻译:http://requirejs.cn/docs/api.html#config

ps:翻译不标准、涉及文章侵权、文章有错误的地方请告知。