【npm】`Typescript`でJSライブラリ

## 前提条件

以下手順を実施済み

[【npm】Nodejsプロジェクトの準備](https://qiita.com/onkz/items/de3ec36724ee8956922c)

## 1. パッケージのインストール

`typescript`本体と、`typescript`での開発をサポートするパッケージをインストールします。

```cmd
> npm i -D typescript ts-node @types/node
```

- `ts-node`: `.ts`ファイルを、コマンドライン上で直接実行できるようにするモジュール。
- `@types/node`: Nodejs の型定義ファイル。Nodejs のモジュールを使用する場合に必要。

コマンドラインで実行する場合は、以下のようにします。

```cmd
> npx tsc -v
Version 4.7.4
```

## 2. 設定ファイルの作成

以下コマンドを実行することで、プロジェクトルート直下に`tsconfig.json`ファイルが作成されます。

```cmd
> npx tsc --init
```

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}
```

各オプションのより詳しい内容は、[公式のドキュメント](https://www.typescriptlang.org/ja/tsconfig)で確認できます。

## 3. コンパイラオプションの修正

作成した`tsconfig.json`ファイルを修正します。

```json:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".", // 非絶対モジュール名を解決するためのベースディレクトリを設定
    "target": "es6", // サポートするJS機能の設定
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true, // 大文字と小文字を区別
    "strict": true,
	  "types": ["node"],
    "outDir": "dist"
  },
  "include": ["src"]
}
```

これは、`src`ディレクトリ内の`*.ts`ファイルをコンパイルして、`dist`ディレクトリへ`CommonJS`形式のモジュールとして出力するようにした設定です。

## 4. スクリプトの追加

`typescript`でのビルド実行を容易にするため、スクリプトを追加します。

```json:package.json
{
  ...,
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "tsc"
  }
}
```

これで、`npm run build`コマンドの発行でビルドが行えるようになります。

## 5. Typescript ファイルの用意

ここまでで基本的な`typescript`実行環境が整ったので、試しに`.ts`ファイルを用意して実行してみます。

まず、コンパイラオプションの`include`で`src`を指定しているため、プロジェクトルート直下に`src`ディレクトリを作成します。

```cmd
> mkdir src
```

次に、作成した`src`ディレクトリ内に任意のファイル名で`.ts`ファイルを作成し、以下のように記述します。  
この例では、`index.ts`とします。

```ts:src/index.ts
export function hello(v: string): void {
  console.log(`Hello ${v}`);
}
```

これは、コンソールに`Hello <引数>`と出力するだけの関数を`hello`という名前でエクスポートしています。

名前付きエクスポートなので、例えば以下のような実行スクリプトを用意して使用することができます。

```ts:temp.ts
import { hello } from './src';

hello('World!');
```

:::note warn
このファイルはコンパイルに含めない為、`src`ディレクトリ内に作成する必要はありません。  
ここでは、プロジェクトルート直下に作成しています。
:::

## 6. `ts-node`で実行する

作成した実行スクリプトを、コマンドラインで実行します。

```cmd
> npx ts-node temp.ts
Hello World!
```

コンソールに`Hello World!`と表示されていれば成功です。

## 7. コンパイルの実行と動作確認

`tsc`コマンドを使用することで、`.ts`ファイルをコンパイルできます。  
ここまでの手順で既に準備は整っているので、`npm run build`コマンドを実行するだけです。

```cmd
> npm run build
```

プロジェクトルート直下に`dist`ディレクトリが作成され、その中に`index.js`ファイルが作成されていれば成功です。

試しに、以下のような実行スクリプトを作成して動作を確認してみます。

```js:temp.js
const { hello } = require('./dist');

hello('World!');
```

```cmd
> node temp.ts
Hello World!
```

## 8. `ESM`（ESM Modules システム）形式で出力

ここまでで`Nodejs`環境で利用できるライブラリを作成できるようになりましたが、このままでは `esm`（ES Modules システム）から利用することができません。
これを解決する為、ビルドを実行する際に`ESM`形式のファイルを同時に出力するよう設定を追加します。

### `tsconfig.json`の修正と、`CommonJS`用の設定ファイル追加

`ESM`出力用の設定ファイルを追加する前に、`tsconfig.json`の設定が共通項目のみとなるよう修正して、`CommonJS`出力用の`tsconfig.cjs.json`ファイルを別途追加します。

```json:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".", // 非絶対モジュール名を解決するためのベースディレクトリを設定
    "target": "es6", // サポートするJS機能の設定
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true, // 大文字と小文字を区別
    "strict": true,
    "types": ["node"]
  },
  "include": ["src"]
}
```

```json:tsconfig.cjs.json
{
	"extends": ".",
  "compilerOptions": {
    "module": "CommonJS",
    "outDir": "dist/cjs"
  }
}
```

### `ESM`出力用の設定ファイルを追加

プロジェクトルート直下に、`tsconfig.esm.json`ファイルを作成します。

```json:tsconfig.esm.json
{
  "extends": ".",
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "module": "es6",
    "outDir": "dist/esm"
  }
}
```

### NPM スクリプトの修正

`package.json`の`scripts`を修正します。

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

これで、`npm run build`を実行すると、`ESM`形式のファイルが同時に出力されるようになります。

また、出力先は以下のようになります。

- `dist/cjs/index.js`: CommonJS
- `dist/esm/index.js`: ESModules

## 9. `ESM`形式での実行と動作確認

通常、`Nodejs`は`CommonJS`形式で動作しますが、`package.json`の`type`フィールドで、JSファイルのモジュール形式を指定できます。

```json:package.json
{
  "type": "module"
}
```

この状態で`temp.js`ファイルを以下のように書き換えて、`node`コマンドで実行してみます。

```js:temp.js
import { hello } from './dist/esm/index.js'

hello('World!');
```

```cmd
> node temp.js
Hello World!
```

`Hello World!`と表示されていれば成功です。