Typescriptでブラウザ用ライブラリを作成する時に困ったこと


はじめに

Typescriptがスタンダードになりつつある流れを感じ、勉強がてら既存のライブラリを書き換えようと思ったら、なかなか苦労した話です。

困ったこと

既存のライブラリは、以下のような使い方をします。

use.js
(function(){
    // ライブラリの処理 
    // この中でwindow.Libraryを定義
})();
var setting = {
    text: "text",
    num: 1
};
window.Wapper = window.Wapper || {
    start: function(setting) { 
        window.Library.start(setting);
    }
};

なぜこの様な使い方なのかは、様々な事情があるので、割愛します。

ブラウザのwindowオブジェクトにプロパティを追加したいのですが、typescriptの場合、どのように書けばいいかわからなかったので、色々試してみました。

バージョン

typescriptのバージョンは、3.5.3です。

既存ライブラリの書き方

既存ライブラリは、ES2015で書かれていて、babel+webpackで最終的に1ファイルに出力されます。

index.js
(() => {
    function init(setting) {
        // init処理
    };

    window.Library = window.Library || {
        start: (setting) => {
            init(setting);
        },
    };
})()

Typescriptで書いてみる

とりあえず、ほぼそのまま貼ってみました。

index.ts
(() => {
    function init(setting: any) {
        console.log(setting);
    }

    window.Lib = window.Lib || {
        start: (setting: any) => {
            init(setting);
        }
    }
})();

以下、エラーで怒られました。

Property 'Lib' does not exist on type 'Window'.ts(2339)

WindowにLibなんてないとのことです。
そりゃそうなんですが、どうやって解決するのがググりました。

Typescriptで手っ取り早く外部ライブラリを使う

Interfaceでwindowを定義してやることでエラーが回避できるらしい。

index.ts
interface Window {
    Lib: any;
}

(() => {
    function init(setting: any) {
        console.log(setting);
    }

    window.Lib = window.Lib || {
        start: (setting: any) => {
            init(setting);
        }
    }
})();

一応エラーがでなくなったので、これでコンパイルしてみると、問題なく動きました。

webpackとの兼ね合い

一応、上記の方法でwindowオブジェクトにプロパティを追加できたのですが、気になったことが。

webpackには、libraryというオプションがあって、これを使うと同様にwindowオブジェクトにプロパティを追加できます。

module.exports = {
    mode: 'production',
    entry: './src/index.ts',
    output: {
      path: `${__dirname}/lib`,
      filename: 'index.js',
      library: "Lib",
      libraryExport: "default",
      libraryTarget: "umd"
    },
    ・・・

これも有効にした場合、どのように動くのかやってみました。

結果は、問題なく動いたんですが、たぶん、コードでwindowに追加されているので、webpackの設定は、関係なくなっているような気がします。

そこで、コード内でwindowを指定しないように書き直してみました。

index.ts
class Lib {
    init(setting: any) {
        console.log(setting);
    }
}

export default class {
    static start = (setting: any) => {
        const lib: Lib = new Lib();
        lib.init(setting);
    }
}

こんな感じに書き直して、コンパイルを実行。
これも問題なく動きました。

まとめ

個人的に、コード内には、windowの記述を書かないで、webpackの設定でプロパティを追加する方法の方がスマートなのかなと思いました。

他にも方法があるのかもしれないのですが、一旦、やりたいことはできたので、よかったです。

後、引数をanyにしていますが、実際にはちゃんと型書いてます。anyは使わないようにしてます。

おまけ

webpackのlibrary設定ですが、コード内にexport defaltの記述があるクラスが存在しないとうまく動かなかったです。

class Lib {
    init(setting: any) {
        console.log(setting);
    }
}

export class Main {
    static start = (setting: any) => {
        const lib: Lib = new Lib();
        lib.init(setting);
    }
}

以上です。