こんにちは、ゲームソリューショングループの入井です。
今回の記事では、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"
]
}
基本的には標準的な設定ですが、jsx
でreact-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を連携させてアプリを開発しています。