(この記事はQiitaで僕が書いたものを移行した記事です。記事中のコメントはQiitaの該当記事を参照ください)
結構試行錯誤したのでメモ。
全ての元凶(?)
最近の行儀のよい JavaScript の書き方 はじめはこの記事のやり方をTypeScriptに流用しようとしました。プレーンなJavaScript及びCoffeeScriptではできるので。そう、CoffeeScriptではできてしまうんです。 しかし、TypeScriptだと黒魔術的なことしないとできないのです。そして多分、Microsoftはその黒魔術を非推奨としています。
まず結論から
browserifyを使い、出力ファイルをブラウザ用、CommonJS用に分けます。分ける方法は簡単でこんな感じです。これが多分精神的に一番いいしMicrosoftも推奨するやり方です。
CommonJS用にコンパイルした後で↓のbrowser.jsをbrowserifyします。
// browser.js window.MyModule = require('./mymodule.commonjs.js');
ファイルのリンクは適当につないでください。
試行錯誤の原因
さて、なんでこんなに試行錯誤する羽目になったのか、その経緯について書きます。
- 「最近の行儀のよい JavaScript の書き方」がCoffeeScriptではできる。
- じゃあTypeScriptでもいけるのでは??
- 書いてみる
- exportなんちゃらの罠にかかる
そう、exportがキワモノだったのです。…というかMicrosoftが多分意図的にこういう仕様としたのでしょうね。この一見罠に見えるexport、回避しようとするとろくなことが無いです。
exportががが
CommonJSとブラウザに1ファイルで対応させる時、何が必要になるかといえば自分の作ったクラス群、ユーティリティ群を外部に漏らさないようにしつつwindowとmodule.exportsにいれることですよね。
↓こんな感じ
(function() { var myModule = { Hoge: function() {} // コンストラクタ Fuga: function() {} // コンストラクタ }; // isBrowserというブラウザかどうか判断する変数を仮定します if (!isBrowser) { module.exports = myModule; } else { window.myModule = myModule; } })();
ただ、これができないのです。
(function(global) { class Hoge{}; class Fuga{}; var myModule = { Hoge: Hoge, Fuga: Fuga }; if ("process" in global) { export = myModule; } global.myModule = myModule; })((this || 0).self || global);
まず、これだとglobalの宣言に引っかかります。まあ、こっちはさして重要でないです。 次の、exportがmodule内でしか使えない。これが重要です。
TypeScriptとしては「module.exports を変なとこで使うのはどう考えてもお前の設計が悪いよね。1ファイルで両方対応とか黒魔術だし。素直にBrowserifyでも使いな」ということでexportをmodule内限定に制限しているのです(全く調べてはいませんが、エラー出る辺りおそらくそうです)。
はじめに私が書いた、Browserifyによる解決をしろ、ということでしょう。
黒魔術による解決。しかも非推奨と思われる
なんと、exportキーワードを使わないという黒魔術 方法を取ることで強引ながら解決は一応できます。
declare var global:any; declare var module:any; (function(global) { class Hoge{}; class Fuga{}; var myModule = { Hoge: Hoge, Fuga: Fuga }; if ("process" in global) { module.exports = myModule; } global.myModule = myModule; })((this || 0).self || global);
まあmoduleをdeclareで宣言しないと使えないような仕様になっていますし、この方法は間違いなくMicrosoftとしては非推奨です。絶対に真似してはいけません。