Lazy-umd.js - 懶人 UMD (Universal Module Definition)

意象說明

Open source < 模組 < beans

前言

最近在整理過去寫的程式庫,想要統一支援 script loader,同時又很貪心地想要:

1.同時支援 AMD (RequireJS)、CommonJS (node.js) 及 browser,解決不同語法差異。
2.模組套用時,避免修改 script loader 程式碼的需要。

研究了一下,發現偉大的 Addy Osmani 已經寫了 UMD。UMD 可以滿足第一點,但是應該是為了避免 script loader 程式碼擁腫,所以套用的時候,一定要修改 script loader 的部分。所以我自己整理出兩種做法,放在 Github 供大家參考:

Fork me on GitHub

方法一、傳統的 factory 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(function (name, / optional /deps, factory) {
‘use strict’;

/ global define, require, module, window /
var i;

// deps is not an array, means the module has no dependencies, and deps is a factory function.
if (typeof deps === ‘function’) {
factory = deps;
deps = [];
}

// AMD (RequireJS)
if (typeof define === ‘function’ && define.amd) {
define(deps, factory);
}
// CommonJS (node.js)
else if (typeof module !== ‘undefined’ && module.exports) {
if (deps.length) {
for (i = 0; i < deps.length; ++i) {
deps[i] = require(deps[i]);
}
}
module.exports = factory.apply(null, deps);
}
// Browser globals
else {
if (deps.length) {
for (i = 0; i < deps.length; ++i) {
deps[i] = window[deps[i]];
}
}
window[name] = factory.apply(null, deps);
}
})(‘MyModule’, [‘Promise’], function (Promise) {
‘use strict’;

return {
};
});

模組的定義寫在後半段的 factory 方法中,然後當作參數傳遞給前面的 script loader 函數,控制權在 script loader 函數。這種寫法在語法上比較接近 RequireJS 的做法。

優點:

  1. 大量的程式庫都是採取與 UMD 類似的做法。容易了解。

缺點:

  1. 如果相依性較多,則會有很長的相依陣列,這是 RequireJS 原本就有的問題。
  2. 模組的主體定義在後面,強迫程式設計師先看到與模組無關的程式碼。
  3. 針對 RequireJS 而言,無法支援不匯出模組的 require() 函數。 (這其實不算缺點,畢竟就是要定義模組,讓模組能盡量支援各種不同的執行環境,才需要考慮 script loader 的相容性問題。一般應用程式通常執行環境早已確認。)

方法二、builder 串接語法模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
(function (module) {
‘use strict’;

module(‘MyModule’)
.require(‘Promise’)
.define(function (Promise) {
return {
};
});

})(function (name) {
‘use strict’;

/ global define, require, module, window /
var deps = [];

// AMD (RequireJS)
if (typeof define === ‘function’ && define.amd) {
return {
require: function (lib) {
deps.push(lib);
return this;
},
define: function (factory) {
define(deps, factory);
},
run: function (script) {
require(deps, script);
}
};
}
// CommonJS (node.js)
else if (typeof module !== ‘undefined’ && module.exports) {
return {
require: function (lib) {
deps.push(require(lib));
return this;
},
define: function (factory) {
module.exports = factory.apply(null, deps);
},
run: function (script) {
script();
}
};
}
// Browser globals
else {
return {
require: function (lib) {
deps.push(window[lib]);
return this;
},
define: function (factory) {
window[name] = factory.apply(null, deps);
},
run: function (script) {
script();
}
};
}
});

這是模仿 melchior.js 的做法。

在後半段先定義好 script loader,然後當作參數傳遞給模組定義函數,控制權在模組定義函數。

優點:

  1. 模組的主體定義在前面,避免與模組無關的程式碼的干擾。
  2. 相依模組的定義清晰易懂;若無相依也可以直接略掉 .require() 的呼叫。

缺點:

  1. Script loader 部分的程式碼變得更為冗長。

參考資料: