2022年度のTypeScript製ES Modulesレシピ

はじめに

この記事は、2022年におけるTypeScript製ES Modulesの作成方法を解説、共有するためのものです。

node.jsにおけるCommonJSの歴史は長く、それに関する記事が大量にあります。どの設定がCommonJSに関わり、どの設定がES Modulesに関わるのかを読み解くには労力がかかります。この記事ではPure ESM作成の最小限構成を通して、ES Modulesの設定を整理します。

想定する読者

  • JavaScript、TypeScriptの開発経験者
  • node.jsおよびnpmの基礎的な知識がある
  • CommonJSのサポートは考えず、新規にPure ESMのパッケージを開発したい

想定する環境

  • node.js v16.18.0 ~ v18.11.0
  • TypeScript v4.8
ES Modulesに関わる環境は頻繁に更新されます。この記事は2022/10/15時点での環境設定をまとめたものです。

node.jsのメジャーバージョンが更新された場合、この記事のレシピは適用できない可能性があります。お手元のnode.jsが20以降の場合はご注意ください。

tsconfig.json

ES Modules対応のJavaScriptを出力するよう、TypeScriptを設定します。

Node Target Mapping · microsoft/TypeScript Wiki
https://github.com

TypeScriptのWikiに、それぞれのnode.jsバージョンに合わせた推奨設定がまとめられています。

GitHub - tsconfig/bases: Hosts TSConfigs to extend in a TypeScript app, tuned to a particular runtime environment
https://github.com

また@tsconfig/recommendedというパッケージにも、目的とする環境に合わせた推奨設定がまとめられています。ES Modulesをターゲットとした推奨設定もあります。

これらのサンプルをまとめると、ES Modules開発のためのtsconfig.jsonの設定は以下のようになります。

▼tsconfig.json

{
  "compilerOptions": {
    //必須の設定
    "lib": ["ES2021"],
    "module": "ES2022",
    "target": "ES2021",

    //オプション
    "declaration": true
  }
}

declarationは型定義ファイルを出力するか否かの指定です。必須ではありませんが、作業効率が向上するので出力しましょう。

package.json

モジュールがES Modules対応であることをpackage.jsonで明示します。

▼package.json

{
  //必須の設定
  "name": "あなたのパッケージの名前",
  "main": "index.js",
  "type": "module",

  //オプション
  "types": "index.d.ts",
}

mainフィールドはこのモジュールのエントリーポイントを指します。この指定が必須なのはES ModulesもCommonJSも変わりません。

typeフィールドは、拡張子jsのファイルをES Modulesファイル(.mjs)と扱うか、CommonJSファイル(.cjs)ファイルとして扱うかを指定します。moduleを指定すると拡張子jsのファイルはmjsファイルとして扱われます。このフィールドを指定してもファイル名で.cjs .mjsを明示すれば、拡張子での指定が優先されます。

typesフィールドはTypeScriptの型定義ファイルを指定します。TypeScriptでES Modulesを開発するなら、作業効率が向上しますので指定しましょう。

typetypesは見間違えそうになりますが、それぞれ別のフィールドです。ご注意ください。

TypeScript

importと拡張子

TypeScriptのimportでは拡張子を省略します。TypeScriptのimportは

  • 型情報が残っているtsファイル
  • トランスパイル済みのjsファイル
  • 型情報のみのd.tsファイル

を区別なく読み込むためです。この方針は公式ドキュメントでも示されてきました。

しかし後発であるES Modulesのimportは、拡張子を指定するという方針を示しました。ここでES ModulesとTypeScriptの仕様に矛盾が生じました。

さまざまな解決方法が検討された結果、TypeScriptはES Modules対応のスクリプトを記述する時のみimport文に拡張子jsを明記するようになりました。tscはimportを変換しません。この方針はこのコメントで示されています。

▼ファイル構成

index.ts
ClassA.ts
tsconfig.json

このファイル構成でindex.tsからClassA.tsをimportする場合 ▼index.ts

import { ClassA } from "./ClassA.js"
                                 ^^

このように存在しないトランスパイル済みファイルClassA.jsファイルを指定しなくてはいけません。

この拡張子つきimport指定は、TypeScriptのGitHubリポジトリで現在もディスカッションが続いています。今後TypeScriptのバージョンアップで新たなオプションが追加されるかもしれません。ご注意ください。

jsonのimport

CommonJSにおいて、外部JSONファイルはrequire関数で簡単に読み込めました。しかし、ES Modulesにはrequire関数はありません。

const data = require("./data.json");

node.js v16において、実験的機能としてJSON modulesが実装されています。

import packageConfig from './package.json' assert { type: 'json' };

この機能は実験的なため、将来のバージョンでも使い続けられるかわかりません。

import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const data = require("./data.json");

この問題を解決するのがcreateRequire関数です。require関数を生成して、外部jsonファイルを読み込みます。


以上、ありがとうございました。