生JSのReactプロジェクトにTypeScriptを導入してみた【Zendeskアプリ開発環境】

Zendeskのアプリ開発の際に型安全性を高めるためにTypeScriptを導入する手順を解説します。TypeScript関連パッケージのインストール、設定ファイルの作成、JSファイルのTypeScript化、ESLintとPrettierの導入などの手順を記載しています。
2023.07.01

こんにちは、ゲームソリューショングループの入井です。

今回の記事では、TypeScriptを追加導入して型安全な状態でZendeskのアプリ開発を行えるようにする手順をご紹介します。

生JSで複雑なアプリを書くのはつらい

Zendeskアプリ開発では、アプリのビルドやテスト・デプロイを行うためにZCLIというコマンドラインツールを使用します。

ZCLIでは、以下のコマンドを使用することでZendeskアプリ開発用のコード・設定ファイルが揃ったReactのプロジェクト環境を自動的にセットアップできます。

zcli apps:new --scaffold=react

しかし、セットアップされるReactプロジェクトはTypeScriptに対応しておらず、いわゆる生JSで書くことが前提になっています。

型安全性の無い生JSでの開発は、コードの記述や環境構築が簡潔に済ませられるメリットはあります。しかし、開発対象のアプリがある程度の規模や複雑さを超えると、型チェック漏れによる実行時エラーやバグが頻発したり、エディタのサポートツールの機能が十分に使用できなかったり、コードが読みづらくなり保守性が低下する等、開発が途端に大変になってしまいます。

設定手順

生JSで生成されたプロジェクトであっても、専用のパッケージをインストールして設定を調整することで、TypeScript環境に移行することが可能です。

ここからは、TypeScriptを導入するための手順を記載していきます。

プロジェクトの初期状態の確認

最初に、ZCLIでセットアップされたプロジェクトの初期状態を見ていきます。

JSに関係する部分では、以下のようにディレクトリやファイルが生成されています。

├── lib
│   ├── helpers.js
│   ├── i18n.js
├── locations
│   └── ticket_sidebar.js
└── modules
    ├── app.js
    └──  event_timeline.js

上記のように、生成されたソースファイルはすべて.jsであり.tsのものはありません。当然、npmでもTypeScript関係のパッケージは入っていません。

webpack.config.jsのmoduleの設定は以下の通りです。

module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env', '@babel/preset-react']
        }
      },
      // その他ファイルについての記述は省略
    ]
  },

BabelにてJSファイルをトランスパイルする設定が書かれています。古いブラウザでも動くように変換する@babel/preset-envや、ReactのJSXを変換する@babel/preset-reactが設定されていています。

TypeScript関連パッケージインストール

npmでTypeScript関係のパッケージをインストールします。Reactで使用する型定義パッケージなども導入しています。

npm install --save-dev typescript @babel/preset-typescript \ 
                        @types/node \
                        @types/react \
                        @types/react-dom \
                        @types/jest

パッケージをインストールしたら、TypeScriptの設定ファイルであるtsconfig.jsonという設定ファイルを作成します。

{
  "compilerOptions": {
    "target": "ES2015",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "jsx": "react-jsx",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "sourceMap": true,
    "allowJs": true
  },
  "exclude": [
    "node_modules"
  ]
}

基本的には標準的な設定ですが、jsxreact-jsxを指定することでReactのJSX構文に対応したり、allowJsで通常のJavaScriptファイルもコンパイル対象に設定しています。

webpack設定変更

BabelでTypeScriptのトランスパイルを行うようにwebpack.config.jsの設定を変更します。

resolve: {
    extensions: ['.js', '.ts', '.tsx', '.json', '.css']
  },
module: {
  rules: [
    {
      test: /\.(js|ts|tsx)$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      options: {
        presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript']
      }
    },
    // その他ファイルについての記述は省略
  ]
}

resolve.extensionsを指定することで、インポート時に拡張子を省略できるようにしています。

moduleでは、testで.jsの他TypeScript関係の拡張子である.ts、.tsxもトランスパイル対象に含め、presetsにてBabelでTypeScriptをJavaSriptに変換するため@babel/preset-typescriptを設定しています。

既存コードのTS対応

これでTypeScriptをトランスパイルするための設定は完了したので、最後に既存の.jsファイルをTypeScript化していきます。

ただ、アプリの起動部分であるapp.jsファイルについては、以下のようにJavaScriptコードのままにしています。ZAF(Zendesk Apps framework)等のZendeskアプリ開発に使用するSDKの型定義を用意するのが大変だからです。

import React from "react";
import { render } from "react-dom";
import { resizeContainer } from "../lib/helpers";
import { Container } from "./container";

const MAX_HEIGHT = 1000;

class App {
  constructor(client, appData) {
    this._client = client;
    this._appData = appData;
    this.initializePromise = this.init();
  }

  async init() {
    const container = document.querySelector(".main");
    render(<Container client={this._client} />, container);
    return resizeContainer(this._client, MAX_HEIGHT);
  }
}

export default App;

上記のコードではContainerというReactコンポーネントがZAFクライアントが受け取っていますが、以下のように引数の型定義はanyで妥協しています。

export const Container: React.FC<{ client: any }> = ({ client }) => {
// 中身は省略
}

ESLintとPrettierの導入

TypeScript開発に必ず必要なわけではありませんが、便利なのでESLintとPrettierも導入します。

npm install --save-dev eslint prettier eslint-config-prettier

ESLintの設定は以下のような形にし、Prettierと連携させています。

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: ["plugin:react/recommended", "standard-with-typescript", "prettier"],
  overrides: [],
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    project: "./tsconfig.json",
  },
  plugins: ["react"],
  rules: {
    "@typescript-eslint/explicit-function-return-type": "off",
  },
};

まとめ

ZCLIで生成したZendeskアプリ開発プロジェクトへTypeScriptを導入する手順をご紹介しました。アプリ開発は機能が複雑化しやすいため、型システムを活用して安全で読みやすいコードを書いていくのがオススメです。

なお、今回設定を行った実際のプロジェクトは、以下のGitHubリポジトリに保存してあります。リポジトリ名の通り、ZendeskとEメール配信サービスのSendGridを連携させてアプリを開発しています。