【TypeScript】よく使う処理を複数のプロジェクトで使い回す

## 環境
- npm: 6.4.1
- typescript: 3.8.3

## 使いどころ

JavaScript(TypeScript)を書いているときに「こういうメソッドがあったら便利なのにな」と感じることがしばしばあると思います。例えば以下のような

```ts:.ts
// 配列の最初の要素を取得(自分で定義しないと使えない)
[1, 2, 4].first() // 1
[].first()        // undefined

// 1個次の要素を取得(自分で定義しないと使えない)
[1, 2, 4].nextOf(2) // 4
[1, 2, 4].nextOf(3) // undefined
```

もちろん以下のように`prototype`を操作したりライブラリを導入したりすることで使えるようにはなりますが、

```ts
Array.prototype.first = function<T>(): T | undefined {
  const arr = this as Array<T>
  return arr.length > 0 ? arr[0] : undefined
}
```

例えば複数のアプリを作っているとして、各アプリ内で毎回こういった定義をするのかということを考えると、できれば別プロジェクトに切り出したいところではあります。
ということで色々と試行錯誤したので備忘録を兼ねて投稿

## 概要

- 共通処理専用のプロジェクトを作成する
- それをGitHubにアップする
- 共通処理利用側プロジェクトで`npm install 上記GitHubリポジトリのURL`

## 手順

### 共通処理プロジェクトの作成
※ TypeScriptでちゃんと型が参照できるようにするためにその手順も含んでいます。

#### TypeScriptをインストール
`npm install -D typescript`

#### tsconfig.json を作成
`./node_modules/.bin/tsc --init`
以下のコンフィグで動作を確認済みです。

```js:tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "declaration": true, // .d.ts ファイルが生成されるようにする
    "outDir": "dist" // ビルド後ファイル生成先ディレクトリ名
  }
}
```

#### 共通処理を作成
```ts:my-array-extensions.ts
// 拡張メソッドの型の宣言をします。
// これを書くことで、ビルド時に型定義ファイル(aaa.d.ts)が作成され、
// 共通処理利用側プロジェクトのtypescriptでこの関数を参照できるようになります
declare global {
  interface Array<T> {
    nextOf: (item: T) => T | undefined
  }
}

export default function() {
  // 関数'nextOf'の実装を定義します。
  // たとえ関数が宣言されていても、この行を通らないと、
  // 実行時に '~~~.nextOf is not function' としてエラーになります。
  Array.prototype.nextOf = function<T>(item: T) {
    const arr = this as Array<T>
    const index = arr.indexOf(item)
    return index === -1 || index >= arr.length - 1 ? undefined : arr[index + 1]
  }
}
```

```ts:index.ts
// exportしないと .d.ts ファイルが作成されず、他のプロジェクトから nextOf の定義を参照できない
export * from './my-array-extensions'

// 上記のファイルで定義した関数を実行し、prototypeに関数を代入します。
// 他のプロジェクトから実行されるのはpackage.jsonのmainに記述したビルド後のファイルなので、
// そのファイルの中でprototypeへの代入処理が実行されないと処理が定義されません。
// myArrayExtensionsの名前は何でも可
import myArrayExtensions from './my-array-extensions'
myArrayExtensions()
```

#### package.json を作成
`npm init -y`

`main`, `build`, `postinstall`の行が重要です。

```js:package.json
{
  "name": /* 適当なパッケージ名 */,
  "version": "1.0.0",
  "description": "",

  // 他のプロジェクトからの参照時にはこれが実行されます。
  "main": "dist/index.js",

  "dependencies": {
    "typescript": "^3.8.3"
  },
  "devDependencies": {},
  "scripts": {

    // tscの実行により .ts ファイルが .js ファイルに変換されます。
    "build": "tsc",

    // postinstallという名前は他のプロジェクトで `npm install 共通処理のプロジェクト` を
    // したときに自動的に実行されるコマンドです。
    // tsc（TypeScriptのトンラスパイル）を実行し、tsconfig.jsonに記載した./distディレクトリにトランスパイル後ファイルを出力します。
    "postinstall": "npm run build"

  },
  "author": "",
  "license": "MIT"
}
```

#### GitHubにアップロード

リポジトリを作ってアップロードします（詳細割愛）。gitignoreの中身はこちらです。

```:.gitignore
/node_modules
/dist
```

### 共通処理利用側プロジェクトでの準備

#### インストール

`npm install https://github.com/リポジトリのURL.git`

```js:package.json
{
  /* 略 */

  "dependencies": {
    /* 共通処理のパッケージ名 */: "git+https://github.com/GitHubのリポジトリのURL.git"
  }

  /* 略 */
}
```

#### インポート
あとは利用側の方のプロジェクトでimportすれば利用可能です。

```ts:利用側プロジェクトのソース.ts
console.log([1, 2, 4].nextOf(2)) // TypeError: [1, 2, 4].nextOf is not a function

import '共通処理のパッケージ名' // import後に利用可能

console.log([1, 2, 4].nextOf(2)) // 4
```

### 共通処理プロジェクトで更新があった場合
GitHubのリポジトリにpushしたあと、利用側プロジェクトで `npm install 共通処理プロジェクトのパッケージ名` で更新します。