模块和加载器规范

    李玉北、erik、黄后锦、王杨、张立理、赵雷、陈新乐、顾轶灵、林志峰、刘恺华。

    本文档由审校发布。

    要求

    在本文档中,使用的关键字会以中文+括号包含的关键字英文表示: 必须(MUST) 。关键字”MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL”被定义在rfc2119中。

    模块定义 必须(MUST) 采用如下的方式:

    推荐采用define(factory)的方式进行模块定义。使用匿名moduleId,从而保证开发中模块与路径相关联,有利于模块的管理与整体迁移。

    SHOULD NOT使用如下的方式:

    1. define( moduleId, deps, factory );

    moduleId

    moduleId的格式应该符合 中的约束条件。

    1. moduleId的类型应该是string,并且是由/分割的一些term来组成。例如:this/is/a/moduleId
    2. term应该符合[a-zA-Z0-9_]+这个规则。
    3. moduleId不应该有.js后缀。
    4. moduleId应该跟文件的路径保持一致。

    moduleId在实际使用(如require)的时候,又可以分为如下几种类型:

    1. relative moduleId:是以./或者../开头的moduleId。例如:./foo, ../../bar
    2. top-level moduleId:除上面两种之外的moduleId。例如foobar/abar/b

    在模块定义的时候,define的第一个参数如果是moduleId必须(MUST)top-level moduleId不允许(MUST NOT)relative moduleId

    factory

    AMD风格与CommonJS风格

    模块的factory有两种风格,AMD推荐的风格CommonJS的风格AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exportsexports的属性赋值来达到暴露模块对象的目的。

    建议(SHOULD) 使用AMD推荐的风格,其更符合Web应用的习惯,对模块的数据类型也便于管理。

    1. // AMD推荐的风格
    2. define( function( require ) {
    3. return {
    4. method: function () {
    5. var foo = require("./foo/bar");
    6. // blabla...
    7. }
    8. };
    9. });
    10. // CommonJS的风格
    11. define( function( require, exports, module ) {
    12. module.exports = {
    13. method: function () {
    14. var foo = require("./foo/bar");
    15. // blabla...
    16. }
    17. };
    18. });

    参数

    模块的factory默认有三个参数,分别是require, exports, module

    1. define( function( require, exports, module ) {
    2. // blabla...
    1. define( function( require ) {
    2. // blabla...
    3. });

    开发者 不允许(MUST NOT) 修改require, exports, module参数的形参名称。下面就是错误的用法:

    1. define( function( req, exp, mod ) {
    2. // blablabla...
    3. });

    类型

    factory可以是任何类型,一般来说常见的就是三种类型function, string, object。当factory不是function时,将直接做为模块对象。

    1. // src/foo.js
    2. define( "hello world. I'm {name}" );
    3. // src/bar.js
    4. define( {"name": "fe"} );

    上面这两种写法等价于:

    require

    这个函数的参数是moduleId,通过调用require我们就可以引入其他的模块。require有两种形式:

    1. require( {string} moduleId );
    2. require( {Array} moduleIdList, {Function} callback );

    require存在local requireglobal require的区别。

    factory内部的requirelocal require,如果require参数中的moduleId的类型是relative moduleId,那么相对的是当前模块id

    在全局作用域下面调用的requireglobal requireglobal require不支持relative moduleId

    1. // src/foo.js
    2. define( function( require ) {
    3. var bar = require("./bar"); // local require
    4. });
    5. // src/main.js
    6. // global require
    7. require( ['foo', 'bar'], function ( foo, bar ) {
    8. // blablalbla...
    9. });

    exports

    exports是使用CommonJS风格定义模块时,用来公开当前模块对外提供的API的。另外也可以忽略exports参数,直接在factory里面返回自己想公开的API。例如下面三种写法功能是一样的:

    1. define( function( require, exports, module ) {
    2. exports.name = "foo";
    3. });
    4. define( function( require, exports, module ) {
    5. return { "name" : "foo" };
    6. });
    7. define( function( require, exports, module ) {
    8. module.exports.name = "foo";
    9. });

    module是当前模块的一些信息,一般不会用到。其中module.exports === exports

    模块和模块的依赖关系需要通过require函数调用来保证。

    1. // src/js/ui/Button.js
    2. define( function( require, exports, module ) {
    3. require("css!../../css/ui/Button.css");
    4. require("tpl!../../tpl/ui/Button.tpl.html");
    5. var Control = require("ui/Control");
    6. /**
    7. * @constructor
    8. * @extends {Control}
    9. */
    10. function Button() {
    11. Control.call(this);
    12. var foo = require("./foo");
    13. foo.bar();
    14. }
    15. ...
    16. // exports = Button;
    17. // return Button;
    18. });

    具体实现的时候是通过正则表达式分析factory的函数体来识别出来的。因此为了保证识别的正确率,请尽量
    避免在函数体内定义require变量或者require属性。例如不要这么做:

    1. var require = function(){};
    2. var a = {require:function(){}};
    3. a.require("./foo");
    4. require("./bar");

    1. <script src="${amdloader.js}"></script>
    2. <script>
    3. require.config({
    4. ....
    5. });
    6. </script>

    baseUrl

    类型应该是string。在ID-to-path的阶段,会以baseUrl作为根目录来计算。如果没有配置的话,就默认以当前页面所在的目录为baseUrl
    如果的值是relative,那么相对的是当前页面,而不是AMD Loader所在的位置。

    paths

    类型应该是Object.<string, string>。它维护的是moduleId前缀到路径的映射规则。这个对象中的key应该是moduleId的前缀,value如果是一个相对路径的话,那么相对的是baseUrl。当然也可以是绝对路径的话,例如:/this/is/a/path//www.google.com/this/is/a/path

    ID-to-path的阶段,如果模块或者资源是以ui, ui/Panel, tangram开头的话,那么就会去配置指定的地方去加载。例如:

    • ui/Button => /fe/code/path/esui/v1.0/ui/Button.js
    • ui/Panel => /fe/code/path/esui/v1.2/ui/Panel.js
    • js!tangram => /fe/code/path/third_party/tangram/v1.0/tangram.js
    • css!themes/base => //www.baidu.com/css/styles/blue/base.css

    另外,需要支持为插件指定不同的的paths,语法如下:

    1. {
    2. baseUrl: '/fe/code/path',
    3. paths: {
    4. 'css!': '//www.baidu.com/css/styles/blue',
    5. 'css!foo': 'bar',
    6. 'js!': '//www.google.com/js/gcl',
    7. 'js!foo': 'bar'
    8. }
    9. }

    该文档不限定使用何种AMD Loader,但是一个AMD Loader应该支持至少三种插件(css,js,tpl)才能满足我们的业务需求。

    插件语法

    1. [Plugin Module ID]![resource ID]

    Plugin Module Id是插件的moduleId,例如cssjstpl等等。!是分割符。

    resource ID资源Id,可以是top-level或者relative。如果resource IDrelative,那么相对的是当前模块的Id,而不是当前模块Url。例如:

    1. // src/Button.js
    2. define( function( require, exports, module ){
    3. require( "css!./css/Button.css" );
    4. require( "css!base.css" );
    5. require( "tpl!./tpl/Button.tpl.html" );
    6. });

    如果当前模块的路径是${root}/src/ui/Button.js,那么该模块依赖的Button.cssButton.tpl.html的路径就应该分别是${root}/src/css/ui/Button.css${root}/src/tpl/Button.tpl.html;该模块依赖的base.css的路径应该是${baseUrl}/base.css

    参考上面的示例。如果resource ID省略后缀名的话,默认是.css;如果有后缀名,以具体的后缀名为准。例如:.less

    js插件

    用来加载不符合该文档规范的js文件,例如jquerytangram等等。例如:

    1. // src/js/ui/Button.js
    2. define( function( require, exports, module ) {
    3. require( "js!jquery" );
    4. require( "js!./tangram" );
    5. });

    tpl插件

    如果项目需要前端模板,需要通过tpl插件加载。tpl插件由模板引擎提供方实现。插件的语法应该跟上述jscss插件的语法保持一致,例如:

    1. require( "tpl!./foo.tpl.html" );

    为什么不能采用define(moduleId, deps, factory)来定义模块?

    define(moduleId, deps, factory)这种写法,很容易出现很长的deps,影响代码的风格。

    1. define(
    2. "module/id",
    3. [
    4. "module/a",
    5. "module/b",
    6. "module/c"
    7. ],
    8. function ( require ) {
    9. // blabla...
    10. );

    还是看