模块化工具seajs

来源:互联网 发布:乐高nxt编程 编辑:程序博客网 时间:2024/06/14 16:33

一、引入sea.js

<script type="text/javascript" src="js/sea.js"></script>

二、如何变成模块

define(function(require,exports,module){})包裹
require模块之间依赖的接口
exports对外提供方法的接口

define(function(require,exports,module) {    function show(){        alert('aa');    }});

三、如何调用模块

exports用来对外提供接口

define(function(require,exports,module) {    function show(){        alert('aa');    }    exports.show=show;});

四、如何使用模块

seajs.use('module.js',function(ex){}),第一个参数是请求模块的路径module.js,第二个参数是请求完成后的回调函数function(ex){},回调用有一个参数ex,这个参数就是模块提供的接口
默认根目录是sea.js所在的目录。如果想以当前页面所在的目录为根目录,那么只需要加上相对表示符./

seajs.use('module.js');seajs.use('./js/module.js',function(ex){    ex.show();});

五、如何依赖模块(模块与模块之间进行调用,即依赖关系)

通过require('./module3.js')来加载依赖
①如果module3.js是一个普通的模块(即没有define(function(require,exports,module){})包裹起来的模块)可以通过如下方式来加载依赖:

define(function(require,exports,module) {    require('./module3.js');    function show(){        alert(a)    }    exports.show=show;});

②如果module3.js是一个标准的seajs模块(即define(function(require,exports,module){})包裹起来的模块),当引入依赖依赖模块时,require执行完的结果就是exports
在控制台打印require('./module3.js'),会发现打印出来的是一个对象 Object {a: 100},所以要通过如下方式来加载依赖:

define(function(require,exports,module) {    var a=require('./module3.js').a;    function show(){        alert(a)    }    exports.show=show;});

六、Grunt构建(grunt-cmd-contact grunt-cmd-transport contrib-clean)

如果用构建工具合并所有的模块之后,模块将会多两个参数 id[]。分别表示,当前模块的标识(id)和当前模块的依赖([])注意:依赖是一个数组可以为空
define(id,['',''],function(require,exports,module){})

七、use如何引用多个模块

如果想用use同时引入多个模块,就需要用一个数组来存放依赖,如下。回调函数中的参数,分别代表对应的模块提供的方法即ex1==module.exports==exports

seajs.use(['./js/module1.js','./js/module2.js'],function(ex1,ex2) {    ex1.show();    ex2.show();});

八、sea加ID有利于提取

<script type="text/javascript" src="js/sea.js"></script>    

九、如何改造文件为CMD模块

define(function(require,exports,module){})包裹,并输出相应的接口exports.show=show;

十、调试接口cache

seajs.cache

十一、模块标识

模块标识是一个字符串,用来标识模块。在 require、 require.async 等加载函数中,第一个参数都是模块标识。

1、一个模块标识由斜线(/)分隔的多项组成。
2、每一项必须是小驼峰字符串、 ...
3、模块标识可以不包含文件后缀名,比如 .js
4、模块标识可以是 相对 或 顶级 标识。如果第一项是 ...,则该模块标识是相对标识。
5、顶级标识根据模块系统的基础路径来解析。
6、相对标识相对 require 所在模块的路径来解析。

注意,符合上述规范的标识肯定是 Sea.js 的模块标识,但 Sea.js 能识别的模块标识不需要完全符合以上规范。 比如,除了大小写字母组成的小驼峰字符串,Sea.js 的模块标识字符串还可以包含下划线_和连字符-, 甚至可以是 http://https://file:/// 等协议开头的绝对路径。

11.1相对标识

相对标识以 .开头,只出现在模块环境中(definefactory 方法里面)。相对标识永远相对当前模块的 URI 来解析

// 在 http://example.com/js/a.js 的 factory 中:require.resolve('./b');  // => http://example.com/js/b.js// 在 http://example.com/js/a.js 的 factory 中:require.resolve('../c');  // => http://example.com/c.js

11.2顶级标识

顶级标识不以点(.)或斜线(/)开始, 会相对模块系统的基础路径(即 Sea.jsbase 路径)来解析
1、假设 base 路径是:http://example.com/assets/
在模块代码里:

require.resolve('gallery/jquery/1.9.1/jquery');  // => http://example.com/assets/gallery/jquery/1.9.1/jquery.js

2、模块系统的基础路径即 base 的默认值,与 sea.js 的访问路径相关
如果 sea.js 的访问路径是http://example.com/assets/sea.js则 base 路径为http://example.com/assets/
3、当 sea.js 的访问路径中含有版本号时,base 不会包含 seajs/x.y.z 字串。 当 sea.js 有多个版本时,这样会很方便。
如果 sea.js 的路径是:http://example.com/assets/seajs/1.0.0/sea.js则 base 路径是:http://example.com/assets/

4、当然,也可以手工配置 base 路径:

seajs.config({  base: 'http://code.jquery.com/'});// 在模块代码里:require.resolve('jquery');  // => http://code.jquery.com/jquery.js

11.3普通路径

除了相对和顶级标识之外的标识都是普通路径。普通路径的解析规则,和 HTML 代码中的 <script src="..."></script> 一样,会相对当前页面解析。
假设当前页面是 http://example.com/path/to/page/index.html

// 绝对路径是普通路径:require.resolve('http://cdn.com/js/a');  // => http://cdn.com/js/a.js// 根路径是普通路径:require.resolve('/js/b');  // => http://example.com/js/b.js// use 中的相对路径始终是普通路径:seajs.use('./c');  // => 加载的是 http://example.com/path/to/page/c.jsseajs.use('../d');  // => 加载的是 http://example.com/path/to/d.js

提示:
1、顶级标识始终相对 base 基础路径解析。
2、绝对路径和根路径始终相对当前页面解析。
3、require 和 require.async 中的相对路径相对当前模块路径来解析。
4、seajs.use 中的相对路径始终相对当前页面来解析。

十二、如何改造现有文件为 CMD 模块

12.1、改造主流模块这里指的是 jQuery、Moment、Backbone、underscore 等业界主流模块

这些模块一般都有对 AMD 和 CommonJS 的支持代码,例如 jQuery 源文件的最后几行

if ( typeof module === "object" && module && typeof module.exports === "object" ) {    module.exports = jQuery;} else {    window.jQuery = window.$ = jQuery;    if ( typeof define === "function" && define.amd ) {        define( "jquery", [], function () { return jQuery; } );    }}

还有 Backbone 里

var Backbone;if (typeof exports !== 'undefined') {  Backbone = exports;} else {  Backbone = root.Backbone = {};}

对于有这些兼容代码的,只需要去掉 define.amd的支持,或是直接包装上 define 就可以了。

define(function(require, exports, module) {  // code here ...});

如果没有模块化的兼容代码,有时候需要手动引入依赖,以及暴露对应的接口。

define(function(require, exports, module) {  var $ = require('$');  // code here ...  module.exports = Placeholders;});

12.2、现有的 JS 文件

对于一些现有的普通 JS 文件,相对简单的多,参考 CMD 的书写规范,把那些暴露到全局命名空间的接口用 CMD 的方式进行改造就可以了。

12.3、改造 jQuery 插件

我们推荐以下的包装方式:

// jquery-plugin-abcdefine(function(require, exports, module) {  var $ = require('$');  // 插件的代码  $.fn.abc = function() {};});

这样的改造方式实际上是强化了原有的 ),在实际调用时,可以用下面的方式:

seajs.use(['$', 'jquery-plugin-abc'], function($) {  // $ 有了 jquery-plugin-abc 所提供的插件功能  $.abc();});

十三、配置

seajs.config(options)

seajs.config({  // 别名配置(当模块标识很长时,可以使用 alias 来简化)  alias: {    'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',    'json': 'gallery/json/1.0.2/json',    'jquery': 'jquery/jquery/1.10.1/jquery'  },  // 路径配置(当目录比较深,或需要跨目录调用模块时,可以使用 paths 来简化书写)  paths: {    'gallery': 'https://a.alipayobjects.com/gallery'  },  // 变量配置(有些场景下,模块路径在运行时才能确定,这时可以使用 vars 变量来配置)  vars: {    'locale': 'zh-cn'  },  // 映射配置(该配置可对模块路径进行映射修改,可用于路径转换、在线调试等)  map: [    ['http://example.com/js/app/', 'http://localhost/js/app/']  ],  // 预加载项(使用 preload 配置项,可以在普通模块加载前,提前加载并初始化好指定模块)  preload: [    Function.prototype.bind ? '' : 'es5-safe',    this.JSON ? '' : 'json'  ],  // 调试模式(值为 true 时,加载器不会删除动态插入的 script 标签。插件也可以根据 debug 配置,来决策 log 等信息的输出。)  debug: true,  // Sea.js 的基础路径(Sea.js 在解析顶级标识时,会相对 base 路径来解析)  base: 'http://example.com/path/to/base/',  // 文件编码(获取模块文件时,<script> 或 <link> 标签的 charset 属性。 默认是 utf-8)  charset: 'utf-8'});

十四、ID 和路径匹配原则 (注意:这是一个万人坑)

所谓 ID 和路径匹配原则 是指,使用 seajs.userequire 进行引用的文件,如果是具名模块(即定义了 ID 的模块),会把 IDseajs.use 的路径名进行匹配,如果一致,则正确执行模块返回结果。反之,则返回 null。例如:

seajs.use('lib/jquery', function($) {    // use $});

或者在模块中 require :

define(function(require, exports, module) {    var $ = require('lib/jquery');    // use $});

jQuery 文件是下面的情况时,上述的变量 $ 能拿到正确的返回结果。

// 文件路径是 lib/jquery.js// ID 和实际路径匹配了(.js 后缀会自动补上)define('lib/jquery', function(require, exports, module) {    // jquery code});

下面的代码则返回 null:

// 文件路径是 lib/jquery.js// 但是 ID 是 lib/jquery.min.js// ID 和路径不匹配define('lib/jquery.min', function(require, exports, module) {    // jquery code});

而匿名模块始终能正确返回结果:

// lib/jquery.js// 匿名模块,不需要进行匹配// 但是文件中只能有一个 define 块define(function(require, exports, module) {    // jquery code});

注意这里用于匹配的 ID 都是经过 aliaspath 解析并且补完后缀之后的。为什么要有这个原则
首先,Sea.js 的模块启动接口秉承的是路径即 ID 的设计原则。seajs.use 的方法的第一个参数被规定为文件路径(而不是 ID),这样的设计减轻了记忆模块 ID 的负担,无论是匿名模块还是具名模块,开发者只需要知道文件放在哪儿就行了。
进一步的,之所以有这个 ID 和路径匹配原则,是因为在 CMD 的书写规范中,一个文件对应一个模块,所有的模块都是匿名模块(即 define(factory)的形式)。那么当 seajs.use 某模块时,这个模块对应的文件里的唯一的 define 方法理所当然的是这个模块的执行代码,这时可以正确返回结果。
但是在生产环境下,静态文件不可避免地需要进行合并打包或者进行 combo,以优化请求数提高页面性能。这时,一个 js 文件可能有很多 define() 方法。

define(funtion(require, exports, module) {    // module a});define(funtion(require, exports, module) {    // module b});define(funtion(require, exports, module) {    // module c});

那么请问,当 seajs.use 这个文件时,应该返回哪个模块?
所以这时候 ID 就派上了用场,我们可以这样写:

// path/a.jsdefine('path/a', funtion(require, exports, module) {    // module a});define('path/b', funtion(require, exports, module) {    // module b});define('path/c', funtion(require, exports, module) {    // module c});

我们定义好每个模块的 id ,在 Sea.js 里,那个和文件路径匹配的 ID的模块就是这个文件的主模块。此时:

seajs.use('path/a', function(a) {    // got a, not b or c});

这个原则保证了我们能够自由合并模块来优化性能,seajs-combospm-build 的构建机制都是基于此原则。
RequireJS 中,也有类似的原则:http://requirejs.org/docs/errors.html#mismatch更深一步可能有人要问为啥一定要把 ID 定为文件路径,Sea.js 不是可以自定义 ID 吗,像下面这样:

define('module-id', funtion(require, exports, module) {    // module id});// 然后就可以seajs.use('module-id', function(Module) {    // Module});

上面的代码当然可以运行。但是有一点,任何一个模块的运行都涉及到两个步骤:模块定义 和 模块执行,上面的代码两个步骤都包括在内。而使用了 Sea.js ,我们不希望用户去手动写 script 标签引用模块。希望只需要 seajs.use 模块的文件路径即可(入口唯一):

seajs.use('path/to/module', function(Module) {    // Module});

Sea.js 会自动插入 script 标签,完成定义步骤,然后执行模块,拿到模块的输出。所以当一个文件里有多个 define 时,只能用 ID 是否匹配 use 中的路径来判断是否主模块。
当然可以回避掉这个原则,你只需要自己负责模块的定义部分,再自己 seajs.use 之前定义好的模块 ID 就行。

<!-- 各种模块的定义 define define define --><script src="http://example.com/modules.js"></script><script>// 这时 use 的第一个参数就可以不必是文件路径了,因为已经有定义好的模块 ID 了seajs.use('jquery', function($) {    // $});</script>

或者通过 alias 来帮助 ID 匹配上最终的路径,这样就和 RequireJS 的方案基本一致了。

  // lib/jquery-1.7.2.js 的内容如下define('$', funtion(require, exports, module) {  // jQuery});

这样就不需要自己去引用上面的文件,可以直接通过 seajs.use 调用。

seajs.config({  alias: {    $: 'lib/jquery-1.7.2.js'  }});seajs.use('$', function() {  // Got $ !});
0 0