21
7

2022年度のTypeScript製ES Modulesレシピ

Last updated at Posted at 2022-10-21

はじめに

この記事は、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を設定します。

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

また@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": "あなたのパッケージの名前",
  "type": "module",
  "exports": {
    ".": {
      "import": {
          "types": "./esm/index.d.ts",
          "default": "./esm/index.js"
      },
  },
  "types": "./esm/index.d.ts" // 古いTypeScript用の型情報Fall-back
}

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

exportsの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ファイルを読み込みます。


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

21
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
7