Chapter 04

ES モジュールと bin

Kei Touge
Kei Touge
2022.02.27に更新

🧰 ES モジュール

前章までで Node.js 環境で利用できるライブラリを作成できるようになったわけですが、Node.js モジュールシステム (= CommonJS) からだけではなく、TypeScript の ES Modules システム(以下、ESM)からも利用できるようにするには、ESM 形式のライブラリも用意する必要があります。

また、序章で述べたように CLI としての実行ファイルも作成しなければなりません。

ESM 出力用の tsconfig.json を作成する

プロジェクトフォルダ直下へ tsconfig.esm.json を作成します。

tsconfig.esm.json
{
  "extends": ".",
  "compilerOptions": {
    "module": "ESNext",
    "outDir": "dist/esm",
    "declaration": true,
    "declarationMap": true
  },
  "include": ["src"]
}

"extends": "." で基本的には ./tsconfig.json での設定を継承します。また、ESNext 形式のモジュールを dist/esm ディレクトリへ出力し、併せて型定義ファイルとそのソースマップも出力します。

便利ツールのインストール

ESM 向けのビルド工程が増えるため、これらを整理するためのユーティリティをインストールしましょう。

zsh
% npm i -D npm-run-all

npm-run-all では、複数の NPM スクリプトのタスクをコントロールできます。

  • run-s: NPM スクリプトを 順番に 実行します。
  • run-p: NPM スクリプトを 並列に 実行します。

NPM スクリプトのアップデート

この tsconfig.esm.json を読み込んだビルドができるように NPM スクリプトを更新します。

package.json
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "run-p build:*",
    "build:common": "tsc",
    "build:esm": "tsc -p tsconfig.esm.json"
  },

package.json へ ESM 向けエントリを追加

ESM として読み込まれた場合に、呼出先へモジュールとその型定義ファイルの場所を提示するようにエントリを追加します。

package.json
  "main": "dist/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/esm/index.d.ts",

ビルドふたたび

では、さっそく再ビルドしてみましょう。

zsh
% npm run build

> my-hello-lib@1.0.0 prebuild
> rimraf dist

> my-hello-lib@1.0.0 build
> run-p build:*

> my-hello-lib@1.0.0 build:esm
> tsc -p tsconfig.esm.json

> my-hello-lib@1.0.0 build:common
> tsc

dist ディレクトリへ Node.js 向けライブラリと ESM 向けライブラリの両方が出力されたことを確認しましょう。

zsh
% tree -a -I 'node_modules'
.
├── dist
│   ├── esm
│   │   ├── index.d.ts
│   │   ├── index.d.ts.map
│   │   └── index.js
│   └── index.js
├── package-lock.json
├── package.json
├── src
│   └── index.ts
├── tsconfig.esm.json
└── tsconfig.json

3 directories, 9 files

🧨 bin

CLI から下のように実行すると Hello. と出力されるコマンドライン向けインターフェイスも用意します。

zsh
% npx my-hello-lib

Hello.

実行ファイルの作成

srcbin ディレクトリを作成し、index.ts を配置します。

zsh
% mkdir -p src/bin
% touch src/bin/index.ts

index.ts は以下のような Node.js 向けスクリプトとします。

src/bin/index.ts
#!/usr/bin/env node

import { hello } from '..';
hello();

package.json のアップデート

CLI として呼び出されたときに実行するファイルの場所を bin エントリに指定します。

package
  "bin": {
    "my-hello-lib": "dist/bin/index.js"
  },

tsconfig.esm.json のアップデート

ESM 向けのビルドでこの bin フォルダを無視するよう tsconfig.esm.jsonexclude を指定します。

tsconfig.esm.json
{
  "extends": ".",
  "compilerOptions": {
    "module": "ESNext",
    "outDir": "dist/esm",
    "declaration": true,
    "declarationMap": true
  },
  "include": ["src"],
  "exclude": ["src/bin", "node_modules", "dist"]
}

exclude 指定の注意点

tsconfig.json では、デフォルトの状態、つまり exclude を指定しない場合には、自動的に以下のファイル・フォルダを無視します。

  • node_modules
  • outDir で指定されたフォルダ内のファイル

しかし exclude に 1 つでもフォルダ/ファイルを指定すると、このデフォルト設定は上書きされてしまいます

https://qiita.com/ryokkkke/items/390647a7c26933940470

そこで、ここでは src/bin を除外するために node_modulesdist も付け加えています。

ビルド三たび

再ビルドしてみましょう。

zsh
% npm run build

> my-hello-lib@1.0.0 prebuild
> rimraf dist

> my-hello-lib@1.0.0 build
> run-p build:*

> my-hello-lib@1.0.0 build:common
> tsc

> my-hello-lib@1.0.0 build:esm
> tsc -p tsconfig.esm.json

以下のようなファイル/フォルダ構成となります。

zsh
% tree -I 'node_modules'
.
├── dist
│   ├── bin
│   │   └── index.js
│   ├── esm
│   │   ├── index.d.ts
│   │   ├── index.d.ts.map
│   │   └── index.js
│   └── index.js
├── package-lock.json
├── package.json
├── src
│   ├── bin
│   │   └── index.ts
│   └── index.ts
├── tsconfig.esm.json
└── tsconfig.json

5 directories, 11 files

📦 配布物(NPM パッケージ)の内容を確認する

パッケージングするコマンドを --dry-run オプション(実際には何も出力しない)付きで実行しましょう。

zsh
% npm pack --dry-run

npm notice
npm notice 📦  my-hello-lib@1.0.0
npm notice === Tarball Contents ===
npm notice 137B dist/bin/index.js
npm notice 75B  dist/esm/index.d.ts
npm notice 142B dist/esm/index.d.ts.map
npm notice 59B  dist/esm/index.js
npm notice 176B dist/index.js
npm notice 510B package.json
npm notice === Tarball Details ===
npm notice name:          my-hello-lib
npm notice version:       1.0.0
npm notice filename:      my-hello-lib-1.0.0.tgz
npm notice package size:  748 B
npm notice unpacked size: 1.1 kB
npm notice shasum:        dd0d4c477346e0d8657399d6dc79d296721613d8
npm notice integrity:     sha512-IwSgzMz7xLSV0[...]d8l4+SBihGBVw==
npm notice total files:   6
npm notice
my-hello-lib-1.0.0.tgz

ここまでで my-hello-lib パッケージ の作成はいったん完了です。