2016年5月2日 星期一

[JavaScript] 模組化規範 - CommonJS, AMD, CMD


[JavaScript] 模組化規範 - CommonJS, AMD, CMD

常見的作法... 我們把頁面當作一個一個的模組,借用 jQuery 或是原生的 dom library 進行初始化
jQuery(function{
    // init here
});
//or
document.ready(function(){
    // init here
});
至於功能模組化或是函式庫重用很少在我們的系統中出現
(根據本人不專業觀察)
一方面是業務邏輯等級的設計是真的很困難的,應該只有非常有經驗(重做很多次)的人有辦法處理
指的"絕對"不是只很會寫程式的人,而是很懂 Domain 而且會架構的人
因為分析階段完成後,並沒有有效的銜接設計階段方法,來確切地設計出有彈性地模組
這個情況並不是因為偷懶不進行設計
而是設計完不容易應付修改,尤其只能保證需求一定會改變...
不容易調整的設計,成為殺死團隊的第一把刀

一方面如果系統不是產品,只是一次開發進入維護階段的系統,其實靠人力跟運氣來處理就可以
誰敢重構已經能用的東西,一定是立馬被鞭 (想像是美麗的、現實是殘酷的)
所以這種設計一般發生在共用的字串處理、連線處理、非常常用的業務邏輯元件上
設計到業務邏輯等級,感覺還是在維護階段,針對常修改的功能進行來的合理些

再一方面在台(ㄍㄨㄟˇ)灣(ㄉㄠˇ)了解業務邏輯 、懂設計又那麼閒的工程師,應該蠻快就變成管理職...
(抱怨文完畢,正文開始)
------
CommonJS (http://www.commonjs.org/) 訂定了在非瀏覽器上面的許多 JavaScript 規範,其中正包括模組 (Modules) 。
而在瀏覽器裡面,使用 AMD 模式來處理,這邊主要參考 RequireJS (http://requirejs.org/docs/whyamd.html) 與 AmdJS (https://github.com/amdjs/amdjs-api/blob/master/AMD.md)

CommonJS - Modules/1.0 (http://wiki.commonjs.org/wiki/Modules/1.0)

基本上是這樣,完整點可以直接看定義,另外 1.1 與 1.1.1 版本看起來有再擴充,適用情境也更完整。
模組:使用 exports 變數承接定模組,一般使用檔案名稱當作模組 id
引用:使用 require 函數來指定引用的模組 id
math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
        sum += args[i++];
    }
    return sum;
};
increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};
program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2

AMD - Asynchronous Module Definition

Module
//Calling define with a dependency array and a factory function
define(['dep1', 'dep2'], function (dep1, dep2) {

    //Define the module value by returning a value.
    return function () {};
});
Named Module
//Calling define with module ID, dependency array, and factory function
define('myModule', ['dep1', 'dep2'], function (dep1, dep2) {

    //Define the module value by returning a value.
    return function () {};
});
Sugar1
define([ "require", "jquery", "blade/object", "blade/fn", "rdapi",
         "oauth", "blade/jig", "blade/url", "dispatch", "accounts",
         "storage", "services", "widgets/AccountPanel", "widgets/TabButton",
         "widgets/AddAccount", "less", "osTheme", "jquery-ui-1.8.7.min",
         "jquery.textOverflow"],
function (require,   $,        object,         fn,         rdapi,
          oauth,   jig,         url,         dispatch,   accounts,
          storage,   services,   AccountPanel,           TabButton,
          AddAccount,           less,   osTheme) {

});
Sugar2 Mixing
define(function (require) {
    var dependency1 = require('dependency1'),
        dependency2 = require('dependency2');

    return function () {};
});
當不是都透過 define 撰寫,還可以使用 requirejs.config() 來更有彈性的設定模組與模組間的 dependency
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({});
});

CMD - Common Module Definition(https://github.com/cmdjs/specification/blob/master/draft/module.md)

math.js
define(function(require, exports, module) {
  exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
      sum += args[i++];
    }
    return sum;
  };
});
increment.js
define(function(require, exports, module) {
  var add = require('math').add;
  exports.increment = function(val) {
    return add(val, 1);
  };
});
program.js
define(function(require, exports, module) {
  var inc = require('increment').increment;
  var a = 1;
  inc(a); // 2

  module.id == "program";
});

UMD - Universal Module Definition

只是收攏上面那些定義不過相對彈性也較差
(function (root, factory) { 
    if (typeof exports === 'object') { 
        // CommonJS 
        module.exports = factory(require('b')); 
    } else if (typeof define === 'function' && define.amd) { 
        // AMD 
        define(['b'], function (b) {
            return (root.returnExportsGlobal = factory(b)); 
        }); 
    } else { 
        // Global Variables 
        root.returnExportsGlobal = factory(root.b); 
    } 
}(this, function (b) { 
    // Your actual module 
    return {}; 
}));

沒有留言:

張貼留言