Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

TypeScriptで作ったライブラリをブラウザ対応、CommonJS対応させる

(この記事はQiitaで僕が書いたものを移行した記事です。記事中のコメントはQiitaの該当記事を参照ください)

結構試行錯誤したのでメモ。

全ての元凶(?)

最近の行儀のよい JavaScript の書き方 はじめはこの記事のやり方をTypeScriptに流用しようとしました。プレーンなJavaScript及びCoffeeScriptではできるので。そう、CoffeeScriptではできてしまうんです。 しかし、TypeScriptだと黒魔術的なことしないとできないのです。そして多分、Microsoftはその黒魔術を非推奨としています。

まず結論から

browserifyを使い、出力ファイルをブラウザ用、CommonJS用に分けます。分ける方法は簡単でこんな感じです。これが多分精神的に一番いいしMicrosoftも推奨するやり方です。

CommonJS用にコンパイルした後で↓のbrowser.jsをbrowserifyします。

// browser.js
window.MyModule = require('./mymodule.commonjs.js');

ファイルのリンクは適当につないでください。

試行錯誤の原因

さて、なんでこんなに試行錯誤する羽目になったのか、その経緯について書きます。

  1. 「最近の行儀のよい JavaScript の書き方」がCoffeeScriptではできる。
  2. じゃあTypeScriptでもいけるのでは??
  3. 書いてみる
  4. 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としては非推奨です。絶対に真似してはいけません。