📝

TypeScriptで外部moduleをexportするときのまとめ

2021/07/04に公開

こんにちは、@armorik83です。今年もよろしくお願いします。

今回はTypeScript (with commonjs)のexport周りについて、あまりにも何度も同じ過ちを犯しているためメモを残します。

TypeScriptでのexportの書き方は多い?

TypeScriptで外部モジュールをexportさせるとき、書きうるスタイルが多いイメージを勝手に持っていましたが、実際には一通りしかありません。

その前にJavaScriptのexportの方法をおさらい

多いイメージを持っていた原因ですが、まずJavaScriptには用途別の2つのexport方法があります。(追記: 勝手に2つあるように思い込んでいただけでした

exports.foo = foo;

fooは関数です。ソース内に複数のパブリックな関数がある場合に、

function foo() {
  // ...
}

function bar() {
  // ...
}

exports.foo = foo;
exports.bar = bar;

というように書きます。使うときは、

var mod = require('./mod');
mod.foo();
mod.bar();
// mod() これはできない

mod変数内のプロパティfoo, barに関数が入ってます。

module.exports = foo;

こちらはソース内に公開する関数が単一の場合に用います。Gruntfile書いてたらおなじみですね。

module.exports = function(grunt) {
  // ...
}

もしmod.jsでこのような書き方をした場合、使う側では、

var mod = require('./mod');
mod();

いきなり変数名mod()を付けて使えるわけです。

TypeScriptでのこれらの書き方

TypeScriptでの書き方は上記JavaScriptでの書き方と一緒ではありません。しかし、一緒の書き方でも動いてしまいます。このJavaScriptの書き方でも動いてしまうところが、TypeScriptってexportの書き方多いよな…と変なイメージを持つ理由でした。

正しくは次のようになります。

出力結果をexports.fooにしたい

export function foo() {
  // ...
}

export function bar() {
  // ...
}

classの場合export class ClassNameです。

出力結果をmodule.exportsにしたい

function foo() {
  // ...
}

export = foo;

export = と書きます。classの場合はexport = ClassName;です。

ダメな例

function foo() {
  // ...
}

exports = foo;

これ最悪。exportsは宣言済み変数名なので、エラーは出んわ動かんわと一番やってはいけない例。こんなの書いてたら恥ずかしいよ!(わたしです)

なぜTypeScriptの書き方をすべきか

前に書いた通りJavaScriptの書き方でも動いてしまいますが、TS上でJSの書き方をするとimport mod = require('mod');で型情報を引っ張って来られません。TS上でJSの書き方をすると、大半のケースで型エラーを起こすはずです。

本質

(投稿後追記)…と上のようなぬるい記事をのたまっていたところ、毎度お世話になりっぱなしの@vvakame先生から「仕様書とか実装読んだほうが早いし正確です」と、ごもっともすぎる意見を頂いたのでDocを読みました。

モジュール内で利用出来る exports 変数は、最初は module.exports への参照です。
ガイドラインとして、もし exports と module.exports の間の関係が魔法のように 見えるなら、exports を無視して module.exports だけを使うようにしてください。

ということです。前提知識が間違ってます。


昨今はBrowserifyやwebpackといったブラウザでexports, requireする方式が主流。いいかげんさっさとTypeScriptのexportを叩き込んでおくのが良さそうです。

それではまた!

Discussion