vue-tsc を使った TypeScript Strict Mode の漸進的導入

こんにちは、ビザスクアドバイザー開発チーム、フロントエンドエンジニアの山元(@yamagen0915)です。

はじめに

弊社にはいくつかのフロントエンド環境があり、その中には TypeScript の strict mode が有効でない環境もあり、それらを strict mode が有効な環境へ移行を進めようとしています。

しかし移行と並行して新規開発も行われるため、strict mode でエラーとなるコードを増やさないようにする必要があることと、各チームのメンバーに strict mode な環境に慣れてもらう必要がありました。

そこで vue-tsc を使ってデプロイには影響を与えずに開発時のみ strict mode が有効な環境で開発ができるようにし始めたのでその方法を紹介したいと思います。

vue-tsc とは

vue-tsc とは VSCode の Vue 向け Extention である Volar をベースとした Type Checking ツールです。
tsc 単体では .vue ファイルをコンパイルすることができませんが、vue-tsc は template 内も含めて型チェックが行えるというツールです。

どうやって使っているか

vue-tsc は tsc と同じ形式でエラーを出力することができるため、reviewdog と組み合わせ、差分があった箇所のエラーだけをプルリク上に表示して利用しています。

当初はエラーを修正するために差分が追加され、その差分内でさらにエラーがでて…というループが発生し、開発に影響が出るのではないかと懸念していましたが、しばらく運用してみるとそういうケースはなかったため、表示されたエラーは必ず修正するという運用を行っています。

tsconfig.json の設定

エディタや vue-tsc にデフォルトで strict mode な設定を読み込ませるため、まず tsconfig.json は strict: true に変更しました。

{
  "compilerOptions": {
    "strict": true,
    // 中略
  },
  // 中略
}

これでは production ビルド時にエラーが大量に発生しビルドできなくなってしまうため、追加で tsconfig.webpack.json を用意し、ビルド時はそれを読み込ませるようにしました。

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "strict": false,
  },
}

弊社では vue-cli を利用しているため vue.config.js の chainWebpack に下記のような処理を追加し tsconfig.webpack.json を読み込ませています。

module.exports = {
  // 中略
  chainWebpack: config => {
    config.module
      .rule('ts')
      .use('ts-loader')
      .tap(options => ({
        ...options,
        configFile: 'tsconfig.webpack.json',
      }));

    config.plugin('fork-ts-checker').tap(args => {
      args[0].tsconfig = 'tsconfig.webpack.json';
      return args;
    });
  },
}

GitHub Actions の設定

下記のような GitHub Actions のワークフローを登録するとプルリクの作成、 push したタイミングで vue-tsc による type check が行われるようになります。

name: vue-tsc

on: pull_request

jobs:
  vue-tsc:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2-beta
        with:
          node-version: 14
      - uses: reviewdog/action-setup@v1
        with:
          reviewdog_version: v0.11.0
      - uses: actions/cache@v2
        with:
          path: ~/.npm
          key: npm-packages-cache-v1-${{ hashFiles('./package-lock.json') }}
          restore-keys: |
            npm-packages-cache-v1-${{ hashFiles('./package-lock.json') }}
            npm-packages-cache-v1-
      - name: Install vue-tsc
        run: npm install -g vue-tsc@0.0.25
      - name: Install dependencies
        run: npm ci
      - name: Run vue-tsc
        run: $(npm root -g)/vue-tsc/vue-tsc.js --pretty false --noEmit | reviewdog -f=tsc -reporter=github-pr-check -fail-on-error=true
        env:
          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

問題点 1

tsc による型チェックでは漏れがちな template 上の型チェックも行ってくれるのは大変便利ですが template 上では型アサーションなど TypeScript の機能が利用できないため、実装を変えるかエラーを無視しなければならないケースがいくつかありました。

例えば下記のように template 上で無名関数を使ってイベントを登録した場合に 「Parameter ‘value’ implicitly has an ‘any’ type. 」というエラーが発生してしまいます。
これは型を明示した関数を定義することで解決することができますが、すべてのイベントを関数化するのは面倒な場合もあり、エラーを無視したくなってしまうこともありました。

<template>
  <FooComponent 
    @input="value => state.value = doSomeThing(value)"
  />
</template>

問題点 2

Vue の filters の機能を使っている場合、ビット論理和演算子とみなされエラーになることがありました。
しかし filter は Vue3 にて廃止される機能なため、filter の利用をやめ単純な関数呼び出しの形に修正し解決しました。

<template>
  <div>
    <!-- Bad -->
    <FooComponent :text="start_at | formatDate" />

    <!-- Good -->
    <FooComponent :text="formatDate(start_at)" />
  </div>
</template>

まとめ

今回紹介した vue-tsc による漸進的な strict mode の導入は差分がある部分のチェックのみであり、差分がない部分で新たにエラーが発生しても気付けないため、本質的にはあまり意味はありません。

しかし新規のコードでエラーになるコードを増やさないこと、strict node が有効な環境の開発になれてもらうという意味では非常に便利だと感じました。

同じように strict mode への移行を考えている場合は参考にしていただけるのではないかと思います。

また既に strcit mode が有効な環境であっても template 内の型チェックも行えるのは便利なので、そういった環境にも同じように導入してみるのもいいのではないでしょうか。

エンジニアを募集しています

ビザスクでは、エンジニアとして働きたい方を募集しています。
ご興味のある方は下記よりお気軽にご連絡ください。