js模块化开发——require.js的用法详细介绍(含jsonp)

RequireJS的目标是鼓励代码的模块化,它使用了不一样于传统<script>标签脚本加载步骤。能够用它回事、优化代码,但其主要的目的仍是为了代码的模块化。它鼓励在使用脚本以moudle ID替代URL地址。javascript

RequireJS以一个相对于baseUrl的地址来加载全部的代码。页面顶层<script>标签含有一个特殊的属性data-main,require.js使用它来启动脚本加载过程,而baseUrl通常设置到与该属性相一致的目录。下列示例中展现了baseUrl的设置。css

     <!—将baseUrl设置到”scripts”目录,而且加载module ID 为’main’的一个脚本--->html

      <script data-main=”script/main.js” src= “scripts/require.js”></script>java

baseUrl也能够经过RequrieJS config拖动设置。若是没有显示指定config及data-main,则默认的baseUrl为包含RequireJS的html页面所在目录。node

requireJS默认假定全部的依赖资源都是js脚本,所以无需在module ID上加载”.js”后缀,RequireJS在进行module ID到path的解析时会自动补上后缀。你能够经过paths config设置一组脚本,这些有助于咱们在使用脚本时写更少的字。jquery

有时候你想避开“baseUrl+paths”的解析过程,而是直接指定加载某一个目录下的脚本。此时能够这样作:若是一个module ID符合下述规则之一,其ID解析会避开常规的的“baseUrl+paths”配置,而是直接将其加载为一个相对于当前html文档的脚本:git

n  以“.js”结尾;github

n  以“/”开头;web

n  包含URL协议,“http:”,”https:”;ajax

通常来讲,最好仍是使用baseUrl及”path” config去设置module ID。它会给你带来额外灵活性,如便于脚本的重命名、重定位。同时,为了不凌乱的配置,最好不要使用多级嵌套目录层次来组织代码,而是要么将全部的脚本 都放置到baseUrl中,要么分置为项目库/第三方库的一个扁平结构,以下:

www/

      index.html

      js/

        app/

              sub.js

         lib/

              jquery.js

              canvas.js

         app.js

         require.js

在index.html中:

   <script data-main=”js/app.js” src=”js/require.js”></script>

在app.js中:

    requirejs.config({

            //默认从js/lib加载全部的module ID

            baseUrl:’js/lib’,

           //除非,module ID以“app”开关,不然从js/app目录加载。

           //注意path,config是相对于baseUrl的,

           //并且不要包含”.js”的后缀,由于一个path

           //有多是个目标

           paths:{

                   app:’../app’

            }

    });

    //启动main app

     requirejs([‘jquery’,’canvas’,’app/sub’],function($,canvas,sub){

           自此,jQuery,canvas以及app/sub模块都已加载并开始使用了

     });

 

注意在示例中,三方库如jQuery没有将版本号包含在他们的文件名中。咱们建议版本信息放置在单独的文件中来进行跟踪。使用诸如volo这类的工 具,能够将package.json打上版本信息,并在磁盘上保持文件我为“jquery.js”。这有助于你保持配置的最小化,避免为每一个库版本设置一 条path。

理想情况下,每一个加载的脚本都是经过define()来定义的一个模块;但这些“浏览器全局变量注入”型的传统/遗留库并无使用define()来定义它们的依赖关系,你必须为此使用shim config来指明它们的依赖关系。若是你没有指明依赖关系,加载可能报错。这是由于基于速度的缘由,RequireJS会异步地以无序的形式加载这些库。

定义模块

模块不一样于传统的脚本文件,它良好地定义了一个做用域避免全局名称空间的污染。它能够显式地列出其依赖关系,并以函数参数的形式将这些依赖进行注 入,而无需引用全局变量。RequireJS的模块是模块模式的一个扩展,其好处是无需全局地引用其它模块。RequireJS的模块语法容许它尽快地加 载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。同时由于无需建立全局变量,甚至能够作到在同一个页面上同时加载同一模块的不一样版本。

多个模块可使用内置优化工具(optimization)将其组织打包。

简单的值对

define({color:”block”,size:”unisize”});

函数式定义

若是一个模块没有任何依赖,可是须要一个作setup工做的函数,则在define()中定义该函数,并将其传给define():

    define(function(){

     //Do setup work here

       return {

            color:”black”,

            size:”unisize”

       }

    })

存在依赖的函数式定义

若是模块存在依赖:则第一个参数是依赖的名称数组;第二个参数是函数,在模块的全部依赖加载完毕后,该函数会被调用来定义该模块,所以该模块应该返回一个定义了本模块的object。依赖关系会以参数的形式注入到该函数上,参数列表与依赖名称列表-----对应。

   //my/shirt.js如今对同目录下的cart及inventory存在依赖

   define([‘./cart’,’./inventory’],function(cart,inventory){

          //返回一个定义了该”my/shirt”模块的object

           retrun {

                  color:”blue”,

                  size:”large”,

                  addToCart:function(){

                        inventory.decrement(this);

                         cart.add(this)

                  }

           }

   });

 

严重不鼓励模块定义全局变量。遵循此处的定义模式,可使得同一模块的不一样版本并存于同一个页页面上。另外,参数的顺序与依赖顺序保存一致。返回的object定义”my/shirt”模块。这种定义模式下,”my/shirt”不做为一个全局变量而存在。

 

将模块定义为一个函数

对模块的返回值类型并无强制为必定是个object,任何函数的返回值都是容许。此处是一个返回了函数的模块定义:

   //foo/title.js像以前同样使用my/cart及my/inventory模块

   //但foo/bar.js位于不一样于”my”模块的目录下,它在模块依赖名称

   //中使用”my”来定位它们。依赖名称中的”my”可能映射到任意一个目录,

   //但默认,假定它邻接着”foo”目录

     define([“my/cart”,”my/inventory”],function(cart,inventory){

          //返回一个函数以定义”foo/title”

          //它获取/设置window的title

             return function(title){

                          returntitle?(window.title=title):inventory.storeName+”  “+cart.name;

             }

      });

 

 

简单包装CommonJS来定义模块

若是你如今有一些以CommonJS模块格式编写的代码,而这些代码难于使用上述依赖名称数组参数的形式来重构,你能够考虑直接将这些依赖对应到一些本地变量中进行使用。你可使用一个CommonJS的简单包装来实现:

    define(function(require,exports,module){

        var a = require(‘a’), b = require(‘b’);

         //Return the module value

         return function(){};

    });

该包装方法依靠Function.prototype.toString()将函数内容赋予一个有意义的字符串,但在一些设备如ps3及一些老的Opera手机浏览器中不起做用。考虑在这些设备上使用优化器将依赖导出为数组形式。

 

定义一个命名模块

你可能会看到一些define()中包含一个模块名称做为首个参数:

    define(“foo/title”, [“my/cart”,”my/inventory”],function(cart,inventory){

               //此处定义foo/title object

      })

这些常由优化工具生成。你也能够本身显式指定模块名称,但这使模块更不具有行移植性----就是说你惹将文件移动到其它目录下,你就得重命名。通常最好避免模块硬编码,而交给优化工具去生成。优化工具须要生成模块名以将多个模块打成一个包,加快到浏览器的载入速度。

其余注意事项

一个文件一个模块:每一个js文件应该只定义一个模块,这是模块名到文件名查找机制的天然要求。多个模块会被优化工具组织优化,但你在使用优化工具时应将多个模块放置到一个文件中。

define()中的相对模块(注意是模块)名:为了能够在define()内部使用诸如require(“./relative/name”)的调用以正确解析相对名称,记得将”require”自己做为一个依赖注入到模块中:

    define([“require”,”./relative/name”],function(require){

              var mod = require(”./relative/name”);

     });

或者更好地,使用下述为转换CommonJS模块所设置的更短的语法:

    define(function(require){

          var mod = require(”./relative/name”);

    });

该形式利用了Function.prototype.toString()去查找require()调用,而后将其与”require”一块儿加入到依赖数组中,这样的代码能够正确地解析相对路径了。

生成相对于模块的URL地址:你可能须要生成一个相对于模块的URL地址。你能够将”require”做为一个依赖注入进来,而后调用require.toUrl()以生成该URL:

    define([“require”],function(require){

          var cssUrl = require.toUrl(“./style.css”);

    })

控制台调式:若是你须要处理一个已经过require([“module/name”],function(){})调用加载了的模块,可使用模块名做为字符串参数的require()调用来获取它:

    require(“module/name”).callSomeFunction() 注意这种形式仅在”module/name”已经由其异步形式的require([“module/name”])加载后才有效。只能在define内部 使用形如”./module/name”的相对路径。

 

循环依赖

若是你定义了一个循环依赖(a依赖b,b同时依赖a),则在这种情形下当b的模块函数被调用的时候,它会获得一个undefined的a。b能够在模块已经定义好后用require()方法不规则获取(记得require做为依赖注入进来)

    //b.js:

      define([“require”,”a”],

                  function(require,a){

                       //”a”将是null,若是a/b间是循环依赖

                           returnfunction(title){

                                      return require(“a”).doSomething();

                           }

                   }

     );

通常状况下你无需使用require()方法获取一个模块,而是应当使用注入到模块函数参数中的依赖。循环依赖比较罕见,它也是一个重构从新设计的警示灯。但无论怎样,有时候仍是要用到循环依赖,这种情形下就使用上述的require()方式来解决。

若是你熟悉CommonJS,你能够考虑使用exports为模块创建一个空object,该object能够当即被其余模块引用。在循环依赖的两 头都如引操做以后,你就能够安全持有其余模块了。这种方法仅在每一个模块都是输出object做为模块值的时候有效,换成函数效。

//b.js:

define(function(require,exports, module) {

    //若"a"使用了exports,则此处咱们就拥有了一个真正的object引用。

    //但在b返回值以前咱们没法使用a的任何属性。

    var a = require("a");

 

    exports.foo = function () {

        return a.bar();

    };

});

或者,若是你使用依赖注入数组的步骤,则可用注入特殊的"exports"来解决:

//b.js:

define(['a','exports'], function(a, exports) {

    //若"a"使用了exports,则此处咱们就拥有了一个真正的object引用。

    //但在b返回值以前咱们没法使用a的任何属性。

 

    exports.foo = function () {

        return a.bar();

    };

});

JSONP服务依赖

JSONP是在javascript中服务调用的一种方式。它仅需简单地经过一个script标签发起http get请求,是实现跨域调用的一种公认手段。为了在RequireJS中使用JSON服务,需要将callback参数的值指定为“define”。这意 味着你能够获取到的JSONP URL的值当作是一个模块定义。

下面是一个调用JSONP API端点的示例。该示例中,JSONP的callback参数为”callback”,所以”callback=define”告诉API将JSON响应包裹到一个”define()”中:

require(["http://example.com/api/data.json?callback=define"],

    function (data) {

        //data将做为此条JSONP data调用的API响应

        console.log(data);

    }

);

JSONP的这种用法应仅限于应用的初始化中,一旦JSONP服务超时,其余经过define()定义了的模块也可能得不到执行,错误处理不是十分健壮。

仅支持返回值类型为json objectJSONP服务,其余返回类型如数组、字串、数字等都不能支持。这种功能不应用于long-polling类的jsonp链接即那些用来处理实时流的API。这些API在接收到响应后通常会作script的清理,而RequireJS则只能获取该JSONP URL一次,后继使用require()或define()发起的对同一URL的依赖(请求)只会获得一个缓存过的值。

undefine一个模块

有一个全局函数requirejs.undef()用来undefine一个模块。它会重置loader的内部状态以使其忘记以前定义的一个模块。

可是如有其余模块已将此模块做为依赖使用了,该模块就不会被清除,因此该功能仅在无其余模块持有该模块时错误处理中,或者当将来须要加载该模块时有点有。

 

机理

RequireJS使用head.appendChild()将每个依赖加载为一个script标签。RequireJS等待全部依赖加载完毕, 计算出模块定义函数正确调用顺序,而后依次调用它们。在同步加载的服务器端javascript环境中,可简单地重定义require.load()来使 用RequireJS。build系统就是这么作的。该环境中的require.load实现可在build/jslib /requirePatch.js中找到。

将来可能将该部分代码置入require/目录下做为一个可选模块,这样你能够在你的宿主环境中使用它来得到正确的加载顺序。

配置项

当在顶层html页面(或不做为一个模块定义的顶层脚本文件)中,可将配置做为首项放入:

<scriptsrc="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) {

        //该函数会在上述全部的依赖加载完毕后调用。

        //注意该函数可在页面加载完毕前被调用。

        //本回调函数是可选的。

    }

  );

</script>

或者,你将配置做为全局变量“require”在require.js加载以前进行定义,它会被自动应用。下面的示例定义的依赖会在require.js一旦定义了require()以后即被加载

<script>

    var require = {

        deps: ["some/module1","my/module2", "a.js", "b.js"],

        callback: function(module1, module2) {

            //该函数会在上述全部的依赖加载完毕后调用。

            //注意该函数可在页面加载完毕前被调用。

            //本回调函数是可选的。

        }

    };

</script>

<scriptsrc="scripts/require.js"></script>

注意:最好使用 var require = {}的形式而不是 window.require = {}的形式。后者在IE中运行不正常。

支持的配置项:

 

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可跟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加载失败则从本地位置中加载这类的机制。

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

下面有个示例,它须要 RequireJS2.1.0+,而且假定backbone.js、underscore.js 、jquery.js都装于baseUrl目录下。若是没有,则你可能须要为它们设置pathsconfig:

      requirejs.config({

    shim: {

        'backbone': {

            //下述依赖脚本应在backbone.js以前加载

            deps:['underscore', 'jquery'],

            //一旦加载,使用全局变量'Backbone'做为模块值

            exports:'Backbone'

        },

        'underscore': {

            exports: '_'

        },

        'foo': {

            deps: ['bar'],

            exports: 'Foo',

            init: function (bar) {

                //使用该函数容许你调用库所支持的noConflict方法,或其余的清理工做。

                //可是这些库的一些插件们可能依然须要一个全局引用,函数中的"this"提供这个全局引用。

                //依赖会以函数参数的形式被注入。

                //若是本函数具有返回值, 则该值会被用作模块的export值,而不是使用上述'exports'中的字串。

                return this.Foo.noConflict();

            }

        }

    }

});

 

//而后,在一个单独的文件中,如'MyModel.js',定义一个模块,

//指定'backbone'做为依赖。RequireJS会使用shim配置去合理

//地加载'backbone'并给予该模块一个本地的引用。全局的Backbone引用一并

//存在于页面上。

define(['backbone'],function (Backbone) {

  return Backbone.Model.extend({});

});

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

 

那些仅做为jQueryBackbone的插件存在而不导出任何模块变量的"模块"们,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的全局名称。

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”。

 

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

 

requirejs.config({

    config: {

        'bar': {

            size: 'large'

        },

        'baz': {

            color: 'blue'

        }

    }

});

 

//bar.js用了最简单的CJS装裹:

//http://requirejs.org/docs/whyamd.html#sugar

define(function(require, exports, module) {

    //其值是'large'

    var size = module.config().size;

});

 

//baz.js使用了一个依赖数组,

//并要求一个特殊的依赖“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({

    //将API key传递给包的主模块:

    config: {

        'pixie/index': {

            apiKey: 'XJKDLNS'

        }

    },

    //设置“pixie”包的主模块为pixie目录下的index.js

    packages: [

        {

            name: 'pixie',

            main: 'index'

        }

    ]

});

packages:从CommonJS包(package)中加载模块。参见从包中加载模块。

 

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”。

 

进阶应用

从包中加载模块

RequireJS支持从CommonJS包结构中加载模块,但须要一些额外的配置。具体地,支持以下的CommonJS包特性:

一个包能够关联一个模块名/前缀。

package config可为特定的包指定下述属性:

name:包名(用于模块名/前缀映射)

location: 磁盘上的位置。位置是相对于配置中的baseUrl值,除非它们包含协议或以“/”开头

main:当以“包名”发起require调用后,所应用的一个包内的模块。默认为“main”,除非在此处作了另外设定。该值是相对于包目录的。

重要事项:

 

虽然包能够有CommonJS的目录结构,但模块自己应为RequireJS可理解的模块格式。例外是:若是你在用r.js Node适配器,模块能够是传统的CommonJS模块格式。你可使用CommonJS转换工具来将传统的CommonJS模块转换为 RequireJS所用的异步模块格式。

一个项目上下文中仅能使用包的一个版本。你可使用RequireJS的多版本支持来加载两个不一样的模块上下文;但若你想在同一个上下文中使用依赖了不一样版本的包C的包A和B,就会有问题。将来可能会解决此问题。

若是你使用了相似于入门指导中的项目布局,你的web项目应大体以以下的布局开始(基于Node/Rhino的项目也是相似的,只不过使用scripts目录中的内容做为项目的顶层目录):

 

project-directory/

project.html

scripts/

require.js

而下面的示例中使用了两个包,cart及store:

 

project-directory/

project.html

cart/

main.js

store/

main.js

util.js

main.js

require.js

project.html会有以下的一个script标签:

 

<scriptdata-main="scripts/main"src="scripts/require.js"></script>

这会指示require.js去加载scripts/main.js。main.js使用“packages”配置项来设置相对于require.js的各个包,此例中是源码包“cart”及“store”:

 

//main.js的内容

//传递一个config object到require

require.config({

    "packages": ["cart","store"]

});

 

require(["cart","store", "store/util"],

function(cart,   store,   util) {

    //正常地使用模块

});

对“cart”的依赖请求会从scripts/cart/main.js中加载,由于“main”是RequireJS默认的包主模块。对“store/util”的依赖请求会从scripts/store/util.js加载。

 

若是“store”包不采用“main.js”约定,以下面的结构:

 

project-directory/

project.html

scripts/

cart/

main.js

store/

store.js

util.js

main.js

package.json

require.js

则RequireJS的配置应以下:

 

require.config({

    packages: [

        "cart",

        {

            name: "store",

            main: "store"

        }

    ]

});

减小麻烦期间,强烈建议包结构听从“main.js”约定。

 

多版本支持

如配置项一节中所述,能够在同一页面上以不一样的“上下文”配置项加载同一模块的不一样版本。require.config()返回了一个使用该上下文配置的require函数。下面是一个加载不一样版本(alpha及beta)模块的示例(取自test文件中):

 

<scriptsrc="../require.js"></script>

<script>

var reqOne =require.config({

  context: "version1",

  baseUrl: "version1"

});

 

reqOne(["require","alpha", "beta",],

function(require,   alpha,  beta) {

  log("alpha version is: " +alpha.version); //prints 1

  log("beta version is: " +beta.version); //prints 1

 

  setTimeout(function() {

    require(["omega"],

      function(omega) {

        log("version1 omega loaded withversion: " +

             omega.version); //prints 1

      }

    );

  }, 100);

});

 

var reqTwo =require.config({

      context: "version2",

      baseUrl: "version2"

    });

 

reqTwo(["require","alpha", "beta"],

function(require,   alpha,  beta) {

  log("alpha version is: " +alpha.version); //prints 2

  log("beta version is: " +beta.version); //prints 2

 

  setTimeout(function() {

    require(["omega"],

      function(omega) {

        log("version2 omega loaded withversion: " +

            omega.version); //prints 2

      }

    );

  }, 100);

});

</script>

注意“require”被指定为模块的一个依赖,这就容许传递给函数回调的require()使用正确的上下文来加载多版本的模块。若是“require”没有指定为一个依赖,则极可能会出现错误。

 

在页面加载以后加载代码

上述多版本示例中也展现了如何在嵌套的require()中迟后加载代码。

 

对Web Worker的支持

从版本0.12开始,RequireJS可在Web Worker中运行。能够经过在web worker中调用importScripts()来加载require.js(或包含require()定义的JS文件),而后调用require就行了。

 

你可能须要设置baseUrl配置项来确保require()可找到待加载脚本。

你能够在unit test使用的一个文件中找到一个例子。

 

对Rhino的支持

RequireJS可经过r.js适配器用在Rhino中。参见r.js的README。

 

处理错误

一般的错误都是404(未找到)错误,网络超时或加载的脚本含有错误。RequireJS有些工具来处理它们:require特定的错误回调(errback),一个“paths”数组配置,以及一个全局的requirejs.onError事件。

 

传入errback及requirejs.onError中的error object一般包含两个定制的属性:

 

requireType:含有类别信息的字串值,如“timeout”,“nodefine”, “scripterror”

requireModules: 超时的模块名/URL数组。

若是你获得了requireModules错,可能意味着依赖于requireModules数组中的模块的其余模块未定义。

 

 在IE中捕获加载错

 

Internet Explorer有一系列问题致使检测errbacks/paths fallbacks中的加载错 比较困难:

 

IE 6-8中的script.onerror无效。没有办法判断是否加载一个脚本会致使404错;更甚地,在404中依然会触发state为complete的onreadystatechange事件。

IE 9+中script.onerror有效,但有一个bug:在执行脚本以后它并不触发script.onload事件句柄。所以它没法支持匿名AMD模块 的标准方法。因此script.onreadystatechange事件仍被使用。可是,state为complete的 onreadystatechange事件会在script.onerror函数触发以前触发。

所以IE环境下很难一箭双鵰:匿名AMD(AMD模块机制的核心优点)和可靠的错误检测。

但若是你的项目里使用了define()来定义全部模块,或者为其余非define()的脚本使用shim配置指定了导出字串,则若是你将 enforceDefine配置项设为true,loader就能够经过检查define()调用或shim全局导出值来确认脚本的加载无误。

所以若是你打算支持Internet Explorer,捕获加载错,并使用了define()或shim,则记得将enforceDefine设置为true。参见下节的示例。

 

注意:若是你设置了enforceDefine: true,并且你使用data-main=""来加载你的主JS模块,则该主JS模块必须调用define()而不是require()来加载其所需的代 码。主JS模块仍然可调用require/requirejs来设置config值,但对于模块加载必须使用define()。

若是你使用了almond而不是require.js来build你的代码,记得在build配置项中使用insertRequire来在主模块中插入一个require调用 —— 这跟data-main的初始化require()调用起到相同的目的。

 

require([]) errbacks

 

当与requirejs.undef()一同使用errback时,容许你检测模块的一个加载错,而后undefine该模块,并重置配置到另外一个地址来进行重试。

一个常见的应用场景是先用库的一个CDN版本,若是其加载出错,则切换到本地版本:

 

requirejs.config({

    enforceDefine: true,

    paths: {

        jquery:'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min'

    }

});

 

//Later

require(['jquery'],function ($) {

    //使用$

}, function (err){

    //errback

    //error含有出错的模块列表

    var failedId = err.requireModules&& err.requireModules[0],

    if (failedId === 'jquery') {

        //undef是全局的requirejs object上的一个函数。

        //用它来清空jQuery的信息。任何依赖于jQuery或处于加载中的模块都再也不

        //加载,它们会等待有效的jQuery加载完毕。

        requirejs.undef(failedId);

 

        //将jQuery设置到本地版本上

        requirejs.config({

            paths: {

                jquery: 'local/jquery'

            }

        });

 

        //重试。注意上述含有“使用$”一句的require回调会在新的

        //jQuery加载成功后被调用。

        require(['jquery'], function () {});

    } else {

        //其余错。考虑报错给用户。

    }

});

使用“requirejs.undef()”,若是你配置到不一样的位置并从新尝试加载同一模块,则loader会将依赖于该模块的那些模块记录下来并在该模块从新加载成功后去加载它们。

 

注意:errback仅适用于回调风格的require调用,而不是define()调用。define()仅用于声明模块。

 paths备错配置

 

上述模式(检错,undef()模块,修改paths,重加载)是一个常见的需求,所以有一个快捷设置方式。paths配置项容许数组值:

 

requirejs.config({

    //为了在IE中正确检错,强制define/shim导出检测

    enforceDefine: true,

    paths: {

        jquery: [

           'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min',

            //若CDN加载错,则从以下位置重试加载

            'lib/jquery'

        ]

    }

});

 

//后面

require(['jquery'],function ($) {

});

上述代码先尝试加载CDN版本,若是出错,则退回到本地的lib/jquery.js。

 

注意:paths备错仅在模块ID精确匹配时工做。这不一样于常规的paths配置,常规配置可匹配模块ID的任意前缀部分。备错主要用于很是的错误恢复,而不是常规的path查找解析,由于那在浏览器中是低效的。

全局的 requirejs.onError

 

为了捕获在局域的errback中未捕获的异常,你能够重载requirejs.onError():

 

requirejs.onError= function (err) {

    console.log(err.requireType);

    if (err.requireType === 'timeout') {

        console.log('modules: ' +err.requireModules);

    }

 

    throw err;

};

5加载器插件

RequireJS支持加载器插件。使用它们可以加载一些对于脚本正常工做很重要的非JS文件。RequireJS的wiki有一个插件的列表。本节讨论一些由RequireJS一并维护的特定插件:

 

§ 5.1指定文本文件依赖

若是都能用HTML标签而不是基于脚本操做DOM来构建HTML,是很不错的。但没有好的办法在JavaScript文件中嵌入HTML。所能作的仅是在js中使用HTML字串,但这通常很难维护,特别是多行HTML的状况下。

RequireJS有个text.js插件能够帮助解决这个问题。若是一个依赖使用了text!前缀,它就会被自动加载。参见text.js的README文件。

 

§ 5.2页面加载事件及DOM Ready

RequireJS加载模块速度很快,颇有可能在页面DOM Ready以前脚本已经加载完毕。须要与DOM交互的工做应等待DOM Ready。现代的浏览器经过DOMContentLoaded事件来知会。

可是,不是全部的浏览器都支持DOMContentLoaded。domReady模块实现了一个跨浏览器的方法来断定什么时候DOM已经ready。下载并在你的项目中如此用它:

 

require(['domReady'],function (domReady) {

  domReady(function () {

    //一旦DOM准备就绪,本回调就执行。

    //在此函数中查询及处理DOM是安全的。

  });

});

基于DOM Ready是个常规需求,像上述API中的嵌套调用方式,理想状况下应避免。domReady模块也实现了Loader PluginAPI,所以你可使用loader plugin语法(注意domReady依赖的!前缀)来强制require()回调函数在执行以前等待DOM Ready。当用做loader plugin时,domReady会返回当前的document:

 

require(['domReady!'],function (doc) {

    //本函数会在DOM ready时调用。

    //注意'domReady!'的值为当前的document

});

注意:若是document须要一段时间来加载(也许是由于页面较大,或加载了较大的js脚本阻塞了DOM计算),使用domReady做为 loader plugin可能会致使RequireJS“超时”错。若是这是个问题,则考虑增长waitSeconds配置项的值,或在require()使用 domReady()调用(将其当作是一个模块)。

§ 5.3define I18N bundle

一旦你的web app达到必定的规模和流行度,提供本地化的接口和信息是十分有用的,但实现一个扩展良好的本地化方案又是很繁贅的。RequireJS容许你先仅配置一 个含有本地化信息的基本模块,而不须要将全部的本地化信息都预先建立起来。后面能够将这些本地化相关的变化以值对的形式慢慢加入到本地化文件中。

i18n.js插件提供i18n bundle支持。在模块或依赖使用了i18n!前缀的形式(详见下)时它会自动加载。下载该插件并将其放置于你app主JS文件的同目录下。

将一个文件放置于一个名叫“nls”的目录内来定义一个bundle——i18n插件当看到一个模块名字含有“nls”时会认为它是一个i18n bundle。名称中的“nls”标记告诉i18n插件本地化目录(它们应当是nls目录的直接子目录)的查找位置。若是你想要为你的“my”模块集提供 颜色名的bundle,应像下面这样建立目录结构:

 

my/nls/colors.js

该文件的内容应该是:

 

//my/nls/colors.js文件内容:

define({

    "root": {

        "red": "red",

        "blue": "blue",

        "green": "green"

    }

});

以一个含有“root”属性的object直接量来定义该模块。这就是为往后启用本地化所需的所有工做。你能够在另外一个模块中,如my/lamps.js中使用上述模块:

 

//my/lamps.js内容

define(["i18n!my/nls/colors"],function(colors) {

    return {

        testMessage: "The name for red inthis locale is: " + colors.red

    }

});

my/lamps模块具有一个“testMessage”属性,它使用了colors.red来显示红色的本地化值。

往后,当你想要为文件再增长一个特定的翻译,如fr-fr,能够改变my/nls/colors内容以下:

 

//my/nls/colors.js内容

define({

    "root": {

        "red": "red",

        "blue": "blue",

        "green": "green"

    },

    "fr-fr": true

});

而后再定义一个my/nls/fr-fr/colors.js文件,含有以下内容:

 

//my/nls/fr-fr/colors.js的内容

define({

    "red": "rouge",

    "blue": "bleu",

    "green": "vert"

});

RequireJS会使用浏览器的navigator.language或navigator.userLanguage属性来断定my/nls/colors的本地化值,所以你的app不须要更改。若是你想指定一个本地化方式,你可以使用模块配置将该方式传递给插件:

 

requirejs.config({

    config: {

        //为i18n作配置

        //module ID

        i18n: {

            locale: 'fr-fr'

        }

    }

});

注意 RequireJS老是使用小写版本的locale值来避免大小写问题,所以磁盘上i18n的全部目录和文件都应使用小写的本地化值。 RequireJS有足够智能去选取合适的本地化bundle,使其尽可能接近my/nls/colors提供的那一个。例如,若是locale值时 “en-us”,则会使用“root” bundle。若是locale值是“fr-fr-paris”,则会使用“fr-fr” bundle。

RequireJS也会将bundle合理组合,例如,若french bundle以下定义(忽略red的值):

 

//my/nls/fr-fr/colors.js内容:

define({

    "blue": "bleu",

    "green": "vert"

});

则会应用“root”下的red值。全部的locale组件是如此。若是以下的全部bundle都已定义,则RequireJS会按照以下的优先级顺序(最顶的最优先)应用值:

 

my/nls/fr-fr-paris/colors.js

my/nls/fr-fr/colors.js

my/nls/fr/colors.js

my/nls/colors.js

若是你不在模块的顶层中包含root bundle,你可像一个常规的locale bundle那样定义它。这种情形下顶层模块应以下:

 

//my/nls/colors.js内容:

define({

    "root": true,

    "fr-fr": true,

    "fr-fr-paris": true

});

root bundle应看起来以下:

 

//my/nls/root/colors.js内容:

define({

    "red": "red",

    "blue": "blue",

    "green": "green"

});

 

http://blog.csdn.net/pigpigpig4587/article/details/23427573

相关文章
相关标签/搜索