JS のソースコードに TS を取り入れたおはなし

既存プロジェクトの JavaScript アプリケーションのソースコードに TypeScript を含め始めました。
その際やったことや参考にした記事などをメモします。

## 前提

- 環境
    - OS: macOS
    - エディタ: VSCode
    - TypeScript: 2.9.2
- プロジェクトはこれまで ES2015+ で開発をしてる。
  - `Webpack@3.x` (`laravel-mix`) + `Babel`
- 既存のコードベースを TypeScript に置き換えることはしない。
    - たいへんなので・・・
- TypeScript を触るのは初めて。
- TypeScript の Linter については今回は考慮しない。

## 達成したこと

- TS での開発ができるようになった。 TS in JS ができた。
    - JS のコード内で `import SomeTsFile from './some-ts-file'` みたいな事ができた。
    - TS ファイル を JS のファイルとして出力せずにイケた。
    - このやり方だと TS のコードは Babel を通らないのでそこだけ注意しないといけない。
- Jest で TS のコードを読み込んでテスト出来るようになった。
    - テストコード自体は JS でもいいっぽい。
    - でも TS で書いたほうがインテリセンス利いて嬉しい。

## 参考にさせて頂いたもの

- TypeScript with Laravel Mix — Sebastian De Deyne https://sebastiandedeyne.com/typescript-with-laravel-mix
- JavaScriptのライブラリを徐々にTypeScriptに移行する | Web Scratch https://efcl.info/2017/07/17/javascript-to-typescript/

JS に TS は持ち込めないのではと不安だったんですが、似た先例があったようで助かりました。ありがとうございます。

## 実施した作業

### TS のトランスパイル環境を用意

TypeScript とローダーをインストール。
`laravel-mix` の Webpack が 3 系だったので `ts-loader` のバージョンを指定した。

```sh
npm i -D typescript ts-loader@3.x
```

`tsconfig.json` を作成。

```sh
node_modules/.bin/tsc --init
```

#### tsconfig.json

まだよく理解してない部分も多いんですが、次のような感じに落ち着いています。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "es6",
    "module": "es2015",
    "strict": true,
    "lib": [
      "dom",
      "es2015",
      "es2016",
      "es2017",
      "es2018",
    ],
    "strictNullChecks": true,
    "moduleResolution": "node",
  },
  "include": [
    "resources/assets/js/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ],
  "typeAcquisition": {
    "enable": true
  }
}
```

嬉しいことに、最近のブラウザは ES2015 までならほとんど動くんですね。やさしい世界。
https://kangax.github.io/compat-table/es6/

#### Webpack

Webpack (`laravel-mix`) の設定はこんな感じ。（関係無い部分は省いてます）

```js
mix
  .webpackConfig({
    module: {
      rules: [
        {
          test: /\.ts$/,
          loader: 'ts-loader',
        }
      ]
    },
    resolve: {
      extensions: ['*', '.js', '.vue', '.ts']
    }
  })
```

### 利用する JS モジュールの型定義をインストールする

TS 内で JS 製のモジュールを読み込む場合、モジュールの型定義情報が必要になるみたいです。  

#### axios

モジュールが提供してくれてたので、作法的に正しいのか分かりませんが以下のように使いました。  
利用できる型の定義はモジュールのディレクトリ内の `index.d.ts` を確認します。 (`axios` の場合は `node_modules/axios/index.d.ts`)

```js
import axios, { AxiosRequestConfig } from 'axios'
```

#### lodash, nprogress, qs など

DefinitelyTyped で提供されていたので、それをインストールするようにしました。

```sh
npm i -D @types/lodash @types/nprogress @types/qs
```

#### 型定義が存在しないモジュールの場合

極稀に、型定義が提供されていないし Definitely Typed でも共有されていないようなモジュールを使っている場合もあると思います。
その場合は自作するしかないのですが、 Microsoft が npm の JavaScript モジュールから型定義ファイルを生成してくれるツールを公開しています。

> Microsoft/dts-gen: dts-gen creates starter TypeScript definition files for any module or library. https://github.com/Microsoft/dts-gen

このツールを使うことでモジュールの型定義を簡単に生成できます。
ただし、これさえ使えば OK という訳では無いようで、モノによっては自分で手を入れないと VSCode やトランスパイラーが意図通り解釈してくれないみたいです。

かと言って定義を１から作るのはしんどいので、ひとまずその場凌ぎ的に使うのがいいのかなという感触です。

・・・しかし先日、定義を仮で出力して手を入れて、ビルドがちゃんと通るようになっても、テストコードが通らないという現象に遭遇しました。 `Jest` + `module.exports を import で扱う` とかの関係で起きているのかなと推測しています。
その場合は対象のモジュールや関数をモック化するなどが良いのかな…というのが現状の認識です。
より良いやり方がある場合は教えていただけると嬉しいです！

でもこういう、なんの保証も無い型定義を使っていく状況は極力避けていきたいですね。

### Jest で `.ts` をテスト出来るようにする

TS を解決するプリプロセッサと Jest の型定義をインストールします。
Jest のコードも TS で書けるようになるので補間が利いて便利です。

```sh
npm i -D ts-jest @types/jest
```

Jest の設定を `.ts` も処理するよう書き換えます。

```json:package.json
{
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "vue",
      "ts"
    ],
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
      "^.+\\.(vue)$": "<rootDir>/node_modules/vue-jest",
      "^.+\\.ts$": "<rootDir>/node_modules/ts-jest"
    },
    "globals": {
      "ts-jest": {
        "tsConfigFile": "tsconfig.json",
        "useBabelrc": true
      }
    },
    "testMatch": [
      "**/__tests__/**/*.js?(x)",
      "**/__tests__/**/*.ts?(x)",
      "**/?(*.)+(spec|test).js?(x)",
      "**/?(*.)+(spec|test).ts?(x)"
    ]
  }
}
```

以上です :raised_hands: 

---

`.vue` も TS で書こうと思ったんですが、これをやるには `.vue` を import しているファイルから TS で書かないとダメという記述を見たので、今はちょっと手が付けられない状況かなぁという感じ。