TypeScriptでnpmライブラリ開発ことはじめ

この記事では、わざわざTypeScriptでnpmライブラリ開発したいという読者を対象としています。

とはいえ今どきは[VS Code](https://code.visualstudio.com/)さんという素晴らしいエディターのおかげもあって、型が提供されていないnpmライブラリは使いづらい立場にあります。

では早速ですが、TypeScriptで"Hello だれだれ"とコンソールに表示されるnpmライブラリを開発してみましょう。

## 基本的なnpmライブラリ開発の知識

この記事では「TypeScriptで」npmライブラリ開発することに主眼を置きたいため、基本的なnpmライブラリ開発の知識は「[本当にやさしいnpmライブラリ開発入門](https://qiita.com/saltyshiomix/items/0306e17cde8f2475f193)」くらいは知っている前提で進めます。

## `package.json`のあれこれ

私の趣味は人の書いた`package.json`を見ることです。（趣味悪いでしょうｗ）

ではまずは今回お題にする`package.json`を見ていただきましょう。

```json:package.json
{
  "name": "hello-world-typescript",
  "version": "1.0.0",
  "main": "dist/index.js",
  "license": "MIT",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "devDependencies": {
    "typescript": "^3.3.4000"
  }
}
```

とても綺麗ですよね。

ひとつひとつ意味があるので解説していきます。

### `package.json#name`

npmライブラリの名前です。`require('xxx')`の`xxx`にあたるアレです。

### `package.json#version`

npmライブラリはこのバージョン情報を毎回インクリメントして公開する必要があります。

TypeScriptが一度`3.3.3333`というアレなバージョンをつけてしまったために、`3.3.4`にできずに`3.3.4000`になってしまったのは、笑えない笑える話として有名かもしれませんね。

### `package.json#main`

ここに相対パスで有効なJavaScriptファイルが指定されていないと`require('xxx')`できません。
今回はTypeScriptのビルド結果を読み込ませたいので、`dist/index.js`になっています。

### `package.json#files`

`package.json`の`files`プロパティは、使う人と使わない人がいると思います。
Webpackの`include`オプションと`exclude`オプションに似ていて、前者は`package.json`の`files`プロパティ、後者は`.npmignore`です。

要はnpmはライブラリを`npm publish`コマンドで公開する際に、`files`プロパティが指定してあればそれらのみを公開対象とします。

TypeScriptは`tsc`コマンドでビルドする前提なので、`files`プロパティにビルド結果フォルダを指定してやると楽だよ、という話になるわけです。

### `package.json#scripts`

`npm run xxx`を作れるプロパティですね。

`npm run build`は実際には`tsc`コマンドを実行するところはみなさんお分かりかと思います。

`prepublishOnly`がニッチなやつで、`npm publish`をする「前に」必ず実行されるという意味になります。
今回は`npm run build`を必ず走らせてからライブラリ公開したいので、これを指定しています。（開発したのにビルド忘れてバージョンしか上がらなかったという事故を防ぐやつです。）

ちなみに、`scripts`プロパティにはいくつかのマジックがありまして、それが今回の`pre`にあたります。

例えば`npm run xxx`を実行する場合、`prexxx`と`postxxx`を作成しておくと、それぞれ実行前と後に指定されたコマンドが叩かれたりします。
（なかなか便利なのですが駆使しすぎると`package.json`の管理が大変になるので注意。）

## `tsconfig.json`

お待たせしました。
みなさんこれが早く見たかったですよね。

ご査収ください。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "pretty": true,
    "newLine": "lf",
    "outDir": "dist"
  }
}
```

ニッチなところだけ説明していきますね。

`target`と`module`はどのようなライブラリの種類にするのか（Node.jsで利用されるのか、ブラウザで利用されるのか等）によって変わってきます。今回は最新のNode.jsで叩くだけのライブラリですので`target: "esnext"`の`module: "commonjs"`になります。Node.jsで利用される場合は常に`module: "commonjs"`と覚えておいても構いません。

せっかくなのでTypeScriptにきちんと仕事をさせたい。なので`strict: true`にします。
でもライブラリのエラーが無限に出てくることがあるので、それは無視するために`skipLibCheck: true`にします。

型定義をきちんと出力したいので、`declaration: true`にします。

あとWindowsで開発したい方は、クロスプラットフォームでの利用を念頭に改行コードをLFに寄せます。そのために`newLine: "lf"`します。
（例えばコマンドラインツール系のnpmライブラリをWindowsで開発した際に、Linux等で実行するとエラーになります。それを防ぐために改行コードをLFに寄せます。）

最後に今回は`dist`フォルダにコンパイル結果を出力したいので、`outDir: "dist"`とします。

## TypeScriptでnpmライブラリ開発

やっと開発に入りますが、もう終わったようなものです。

```ts:src/index.ts
function hi(name: string) {
  console.log(`Hello ${name}`);
}

export default hi;
```

ビルドしてみます。

```bash
$ npm run build
```

`dist`にビルド結果が出ました。

```js:dist/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function hi(name) {
    console.log(`Hello ${name}`);
}
exports.default = hi;
```

型定義も出力されています。

```ts:dist/index.d.ts
declare function hi(name: string): void;
export default hi;
```

最後にnpmライブラリとして公開してみます。

```bash
$ npm publish
```

ちゃんと`npm run build`(`tsc`)されてから公開されましたね。

## まとめ

- TypeScriptはコンパイル作業が必要なので、事前準備がひと手間かかる
- それをクリアすればTypeScriptでもサクサクnpmライブラリ開発ができる！

いかがでしたでしょうか。
私は.NETやらTypeScriptやらでMicrosoftさんにはお世話になっていますが、型安全なのはとても気持ちいいです。

少しでも皆さまのお役に立てたらと思います。

では良いTypeScriptライフを！！