-
Notifications
You must be signed in to change notification settings - Fork 8
RewriteToWebModulePattern
このエントリでは、レガシーなスタイルで書かれた JavaScript を、WebModule に使われている 実装パターン(WebModulePattern) で書き直す手順を説明します。
以下は ES3当時の古いスタイルで書かれた MyExample.js です。
MyExample をグローバル空間にエクスポートしています。
// Before (古いスタイルで書かれた、変更前のソースコード)
(function(win, undefined) {
function MyExample(value) {
this._value = value || "";
}
MyExample.prototype.concat = function(a) {
return this._value + a;
};
win.MyExample = MyExample;
})(window);
<script src="MyExample.js"></script>
<script>
new MyExample("Hello").concat("JavaScript"); // "HelloJavaScript"
</script>
先ほどのES3基準の古いスタイルのコードは、残念ながらブラウザ以外の環境で動作しません。頑張って全ての行を書き直す必要があります。スタイルを新しくしつつ、Node.js や WebWorkers でも動作するようにモジュール化していきます。
// for WebWorkers
importScripts("WebModule.js");
importScripts("MyExample.js");
new WebModule.MyExample("Hello").concat("JavaScript"); // "HelloJavaScript"
// for Node.js
require("./WebModule.js");
require("./MyExample.js");
new WebModule.MyExample("Hello").concat("JavaScript"); // "HelloJavaScript"
古い ES3 スタイルな MyExample.js に施す変更は以下の通りです。
-
先頭部分に
"use strict"
を追加します(function(win, undefined) { "use strict";
-
undefined を除去します
- ES5 strict mode では undefined は上書きできません
- 引数を1つ少なく渡す事で undefined を作成するアドホックなコードは lint ツールが普及する前のカビが生えた古いやり方です。削りましょう
(function(win) { "use strict";
-
ヘッダ(宣言)とボディ(実装)が癒着しないようにコード内にコメントを追加し、ブロックを明確にします
- ヘッダとボディを分離する理由をご覧ください
(function(win) { "use strict"; // --- dependency modules ---------------------------------- // --- define / local variables ---------------------------- // --- class / interfaces ---------------------------------- function MyExample(value) { this._value = value || ""; } // --- implements ------------------------------------------ MyExample.prototype.concat = function(a) { return this._value + a; }; })(window);
-
Node.js でも global オブジェクトが取得できるように
(function(win) { ... })(window);
の部分を書き換えます- コードの先頭に
moduleExporter
とmoduleClosure
関数を追加します - ファイルの最後に
return MyExample; }
を追加します
// before (function(win) { ... })(window);
// after (function moduleExporter(name, closure) { "use strict"; var entity = GLOBAL["WebModule"]["exports"](name, closure); if (typeof module !== "undefined") { module["exports"] = entity; } return entity; })(MyExample, function moduleClosure(global) { "use strict"; : : return MyExample; // return entity });
- コードの先頭に
-
MyExample.prototype.concat = function(a) { ... };
と直書きしている function 式を
function 文 (function MyExample_concat(a) { ... }
) の形に直します- function 式を function 文に直すことで、メソッドの実体(Body)とAPIの宣言部(Header)を分離できます
- DevTools のプロファイラを使ったパフォーマンス・チューニングするためにも function 文への変換が必要になります
- 実装(Body)を implements ブロックに記述し、宣言部分(Header)を class / interfaces ブロックに記述します
- Object.create を伴った宣言を行い、将来 getter や setter も定義できるようにしておきます
// before MyExample.prototype.concat = function(a) { return this._value + a; };
// after // --- class / interfaces ---------------------------------- MyExample.prototype = Object.create(MyExample, { constructor: { value: MyExample }, concat: { value: MyExample_concat, }, }); // --- implements ------------------------------------------ function MyExample_concat(a) { return this._value + a; }
- function 式を function 文に直すことで、メソッドの実体(Body)とAPIの宣言部(Header)を分離できます
-
MyExample.repository
を追加します- class / interfaces ブロックに、モジュールのリポジトリURL を追加します。
-
MyExample.repository
の値はMyExample.help
,Help("MyExample")
実行時に使用します
// --- class / interfaces ---------------------------------- function MyExample(...) { ... } MyExample.repository = "https://github.com/{GitHubユーザ名}/MyExample.js"; MyExample.prototype = Object.create(MyExample, { ... });
-
MyExample のコンストラクタや concatメソッドに Function Attribute を追加します
- JavaDoc(JsDoc) スタイルは使わず Validate 形式で記述します
-
MyExample#concat
はMyExample.prototype.concat
を省略したものです
// --- class / interfaces ---------------------------------- function MyExample(value) { // @arg String = "" this._value = value || ""; } MyExample.repository = "https://github.com/{GitHubユーザ名}/MyExample.js"; MyExample.prototype = Object.create(MyExample, { constructor: { value: MyExample }, // new MyExample(value:String = ""):MyExample concat: { value: MyExample_concat, }, // MyExample#concat(a:String):String }); // --- implements ------------------------------------------- function MyExample_concat(a) { // @arg String // @ret String return this._value + a; }
-
$valid
や$type
などのバリデーションコードを @dev コードブロックの内側に配置します。function MyExample(value) { // @arg String = "" //{@dev $valid($type(value, "String|omit"), MyExample, "value"); //}@dev this._value = value || ""; } // --- implements ------------------------------------------- function MyExample_concat(a) { // @arg String // @ret String //{@dev $valud($type(a, "String"), MyExample_concat, "a"); //}@dev return this._value + a; }
-
Minifier にリネームされたくない識別子やプロパティを
object.concat
のドットシンタックス形式からobject["concat"]
のブラケット形式に書き換えます- Closure Compiler の ADVANCED_OPTIMIZATION MODE に対応するためには、この作業が必要です
- モジュールの外部から参照する名前はリネームされないようにガードが必要です
// before MyExample.prototype.concat = MyExample_concat;
// after MyExample["repository"] = "https://github.com/{GitHubユーザ名}/MyExample.js"; MyExample["prototype"] = Object.create(MyExample, { "constructor": { "value": MyExample }, "concat": { "value": MyExample_concat, }, });
ここまでのコードをまとめると、こうなります。
(function moduleExporter(name, closure) {
"use strict";
var entity = GLOBAL["WebModule"]["exports"](name, closure);
if (typeof module !== "undefined") {
module["exports"] = entity;
}
return entity;
})(MyExample, function moduleClosure(global) {
"use strict";
// --- dependency modules ----------------------------------
// --- define / local variables ----------------------------
// --- class / interfaces ----------------------------------
function MyExample(value) { // @arg String = "" - comment
//{@dev
$valid($type(value, "String|omit"), MyExample, "value");
//}@dev
this._value = value || "";
}
MyExample["repository"] = "https://github.com/uupaa/MyExample.js";
MyExample["prototype"] = Object.create(MyExample, {
"constructor": { "value": MyExample }, // new MyExample(value:String = ""):MyExample
"concat": { "value": MyExample_concat }, // MyExample#concat(a:String):String
});
// --- implements ------------------------------------------
function MyExample_concat(a) { // @arg String
// @ret String
//{@dev
$valud($type(a, "String"), MyExample_concat, "a");
//}@dev
return this._value + a;
}
return MyExample; // return entity
});