- Notifications
You must be signed in to change notification settings - Fork8
RewriteToWebModulePattern
このエントリでは、レガシーなスタイルで書かれた JavaScript を、WebModule に使われている 実装パターン(WebModulePattern) で書き直す手順を説明します。
以下は ES3当時の古いスタイルで書かれた MyExample.js です。
MyExample をグローバル空間にエクスポートしています。
// Before (古いスタイルで書かれた、変更前のソースコード)(function(win,undefined){functionMyExample(value){this._value=value||"";}MyExample.prototype.concat=function(a){returnthis._value+a;};win.MyExample=MyExample;})(window);
<scriptsrc="MyExample.js"></script><script>newMyExample("Hello").concat("JavaScript");// "HelloJavaScript"</script>
先ほどのES3基準の古いスタイルのコードは、残念ながらブラウザ以外の環境で動作しません。頑張って全ての行を書き直す必要があります。スタイルを新しくしつつ、Node.js や WebWorkers でも動作するようにモジュール化していきます。
// for WebWorkersimportScripts("WebModule.js");importScripts("MyExample.js");newWebModule.MyExample("Hello").concat("JavaScript");// "HelloJavaScript"
// for Node.jsrequire("./WebModule.js");require("./MyExample.js");newWebModule.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 ----------------------------------functionMyExample(value){this._value=value||"";}// --- implements ------------------------------------------MyExample.prototype.concat=function(a){returnthis._value+a;};})(window);
Node.js でも global オブジェクトが取得できるように
(function(win) { ... })(window);
の部分を書き換えます- コードの先頭に
moduleExporter
とmoduleClosure
関数を追加します - ファイルの最後に
return MyExample; }
を追加します
// before(function(win){ ...})(window);
// after(functionmoduleExporter(name,closure){"use strict";varentity=GLOBAL["WebModule"]["exports"](name,closure);if(typeofmodule!=="undefined"){module["exports"]=entity;}returnentity;})(MyExample,functionmoduleClosure(global){"use strict"; : :returnMyExample;// 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 も定義できるようにしておきます
// beforeMyExample.prototype.concat=function(a){returnthis._value+a;};
// after// --- class / interfaces ----------------------------------MyExample.prototype=Object.create(MyExample,{constructor:{value:MyExample},concat:{value:MyExample_concat,},});// --- implements ------------------------------------------functionMyExample_concat(a){returnthis._value+a;}
- function 式を function 文に直すことで、メソッドの実体(Body)とAPIの宣言部(Header)を分離できます
MyExample.repository
を追加します- class / interfaces ブロックに、モジュールのリポジトリURL を追加します。
MyExample.repository
の値はMyExample.help
,Help("MyExample")
実行時に使用します
// --- class / interfaces ----------------------------------functionMyExample(...){ ...}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 ----------------------------------functionMyExample(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 = ""):MyExampleconcat:{value:MyExample_concat,},// MyExample#concat(a:String):String});// --- implements -------------------------------------------functionMyExample_concat(a){//@arg String//@ret Stringreturnthis._value+a;}
$valid
や$type
などのバリデーションコードを @dev コードブロックの内側に配置します。functionMyExample(value){//@arg String = ""//{@dev$valid($type(value,"String|omit"),MyExample,"value");//}@devthis._value=value||"";}// --- implements -------------------------------------------functionMyExample_concat(a){//@arg String//@ret String//{@dev$valud($type(a,"String"),MyExample_concat,"a");//}@devreturnthis._value+a;}
Minifier にリネームされたくない識別子やプロパティを
object.concat
のドットシンタックス形式からobject["concat"]
のブラケット形式に書き換えます- Closure Compiler の ADVANCED_OPTIMIZATION MODE に対応するためには、この作業が必要です
- モジュールの外部から参照する名前はリネームされないようにガードが必要です
// beforeMyExample.prototype.concat=MyExample_concat;
// afterMyExample["repository"]="https://github.com/{GitHubユーザ名}/MyExample.js";MyExample["prototype"]=Object.create(MyExample,{"constructor":{"value":MyExample},"concat":{"value":MyExample_concat,},});
ここまでのコードをまとめると、こうなります。
(functionmoduleExporter(name,closure){"use strict";varentity=GLOBAL["WebModule"]["exports"](name,closure);if(typeofmodule!=="undefined"){module["exports"]=entity;}returnentity;})(MyExample,functionmoduleClosure(global){"use strict";// --- dependency modules ----------------------------------// --- define / local variables ----------------------------// --- class / interfaces ----------------------------------functionMyExample(value){//@arg String = "" - comment//{@dev$valid($type(value,"String|omit"),MyExample,"value");//}@devthis._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 ------------------------------------------functionMyExample_concat(a){//@arg String//@ret String//{@dev$valud($type(a,"String"),MyExample_concat,"a");//}@devreturnthis._value+a;}returnMyExample;// return entity});