ShopifyアプリをTypescriptで開発する方法

# はじめに

Shopifyアプリとは、Shopifyストアに機能を追加するWebアプリケーションです。
下記URLのチュートリアルでは、Node.js、React、GraphQLを使用して埋め込みShopifyアプリを構築するプロセスについて説明されておりますが、Typescriptで開発したいと思い、実装する方法を調べて実現できたのでご紹介したいと思います。

https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react

# 開発環境
下記環境で確認しています。

- Mac
- VSCode
- node.js
- Shopify App CLI

# プロジェクト作成
Shopify App CLIでベースのnodeのプロジェクトを作成します。

- PublicかCustomを選択します。

```shell-session
% shopify create node --name=starter-embedded-app
? What type of app are you building? (Choose with ↑ ↓ ⏎, filter with 'f')
> 1. Public: An app built for a wide merchant audience.
  2. Custom: An app custom built for a single client.
```

- 次にどの開発ストア向けで作成するか選択します。

```shell-session
% shopify create node --name=starter-embedded-app
? What type of app are you building? (You chose: Public: An app built for a wide merchant audience.)
Organization [Shopify Partnerアカウントのビジネス名] 
? Select a Development Store (Choose with ↑ ↓ ⏎, filter with 'f')                                                                                                                                           
  1. xn-xxxxx1.myshopify.com
> 2. xn-xxxxx2.myshopify.com
```

- プロジェクトが生成されます。

```shell-session
% shopify create node --name=starter-embedded-app
? What type of app are you building? (You chose: Public: An app built for a wide merchant audience.)
Organization [Shopify Partnerアカウントのビジネス名]                                                                                                                                                                      
? Select a Development Store (You chose: xn-xxxxx2.myshopify.com)
✓ node v14.3.0                                                                                                                                                                                              
✓ npm 6.14.4                                                                                                                                                                                                
┏━━ Cloning into starter-embedded-app... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃ Cloning into 'starter-embedded-app'...
┃ remote: Enumerating objects: 3, done.        
remote: Counting objects: 100% (3/3), done.        
remote: Compressing objects: 100% (3/3), done.        
remote: Total 1071 (delta 0), reused 0 (delta 0), pack-reused 1068        
Receiving objects: 100% (1071/1071), 1.83 MiB | 1.97 MiB/s, done.
Resolving deltas: 100% (720/720), done.
┃                                                                                                                                                                                                       100%
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (3.11s) ━━
✓ Cloned into starter-embedded-app
┏━━ Installing dependencies with npm... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃ ✓ 37 npm dependencies installed
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (53.38s) ━━
✓ Dependencies installed
✓ .env saved to project root
✓ starter-embedded-app was created in your Partner Dashboard https://partners.shopify.com/xxxxx/apps/xxxxx
⭑ Run shopify serve to start a local server
⭑ Then, visit https://partners.shopify.com/xxxxx/apps/xxxxx/test to install starter-embedded-app on your Dev Store
```

- アプリを起動し、アプリをストアにインストールして動作を確認します。

# Typescript開発セットアップ

Typescript開発で必要なパッケージをインストールします。
※ 先にncuで既存パッケージの最新化もします。
※ typescriptに関しては、最新の3.9.3ではエラーがでてたのでバージョンを3.8.3に指定してインストールしています。

```shell-session
% ncu -u
% npm install
% npm install --save-dev typescript@3.8.3 ts-node @types/node @types/react @types/react-dom @types/koa @types/koa-session @types/koa-router @types/js-cookie
```

tsconfig.json, tsconfig.server.json, next-env.d.ts, nodemon.json ファイルを作成します。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "jsx": "preserve",
    "lib": ["dom", "es2017"],
    "baseUrl": ".",
    "moduleResolution": "node",
    "strict": true,
    "allowJs": true,
    "noEmit": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "isolatedModules": true,
    "removeComments": false,
    "preserveConstEnums": true,
    "sourceMap": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "exclude": ["dist", ".next", "out", "next.config.js"],
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
```

```json:tsconfig.server.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "outDir": "dist",
    "target": "es2017",
    "isolatedModules": false,
    "noEmit": false
  },
  "include": ["server/**/*.ts"]
}
```

```json:nodemon.json
{
  "watch": ["server"],
  "exec": "ts-node --project tsconfig.server.json server/server.ts",
  "ext": "js ts"
}
```

```ts:next-env.d.ts
/// <reference types="next" />
/// <reference types="next/types/global" />
```

package.jsonを下記の通り編集します。

```json:package.json
〜省略〜
  "scripts": {
    "test": "jest",
    "dev": "NODE_ENV=development nodemon",
    "build": "next build && tsc --project tsconfig.server.json && cp server/index.js dist/",
    "start": "NODE_ENV=production node dist/index.js"
  },
〜省略〜
```

# JSファイルをTS/TSXファイルに変更する

JSファイルをTS/TSXファイルに変更して、エラー箇所を修正していきます。

- pages/index.js は pages/index.tsx にリネームのみ
- pages/_app.js は pages/_app.tsx にリネームし、下記の通り編集します。

```tsx:pages/_app.tsx
〜省略〜
import translations from "@shopify/polaris/locales/en.json";

declare var API_KEY: string; // 追加

const client = new ApolloClient({
〜省略〜
    const { Component, pageProps } = this.props;
    const shopOrigin = Cookies.get("shopOrigin") ?? "error"; // 変更
    return (
〜省略〜
```

- server/server.js は server/server.ts にリネームし、下記の通り編集します。

```ts:server/server.ts
〜省略〜
import session from "koa-session";
// import * as handlers from "./handlers/index"; コメントアウト
dotenv.config();
const port = parseInt(process.env.PORT || '8081', 10); // 変更
const dev = process.env.NODE_ENV !== "production";
〜省略〜
const handle = app.getRequestHandler();
const SHOPIFY_API_SECRET = process.env.SHOPIFY_API_SECRET ?? "error"; // 変更
const SHOPIFY_API_KEY = process.env.SHOPIFY_API_KEY ?? "error"; // 変更
const SCOPES = process.env.SCOPES ?? "error"; // 変更
app.prepare().then(() => {
〜省略〜
      async afterAuth(ctx) {
        //Auth token and shop available in session
        //Redirect to shop upon auth
        const shop = ctx.session?.shop; // 変更
        ctx.cookies.set("shopOrigin", shop, {
          httpOnly: false,
          secure: true,
          sameSite: "none"
        });
        ctx.redirect("/");
      }
〜省略〜
```

上記の必要最低限の修正のみで初期プロジェクトは動作するようになると思います。
エラーを回避する修正しかしていないので、適宜修正してください。

# まとめ

ここまで変更を加えたときのフォルダ構成は下記の通りになります。

```shell-session
starter-embedded-app
├── .dependabot
│   └── config.yml
├── .editorconfig
├── .env
├── .env.example
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmrc
├── .prettierignore
├── .shopify-cli.yml
├── .stylelintignore
├── .stylelintrc.js
├── .travis.yml
├── LICENSE.md
├── README.md
├── next-env.d.ts
├── next.config.js
├── nodemon.json
├── package-lock.json
├── package.json
├── pages
│   ├── _app.tsx
│   └── index.tsx
├── server
│   ├── handlers
│   │   ├── client.js
│   │   ├── index.js
│   │   ├── mutations
│   │   │   ├── get-one-time-url.js
│   │   │   └── get-subscription-url.js
│   │   └── register-webhooks.js
│   ├── index.js
│   └── server.ts
├── tsconfig.json
└── tsconfig.server.json
```

あとは、チュートリアルに沿って機能を追加していけば、Typescriptでも開発できることが確認できると思います。

本記事のリポジトリは下記においてますので何かの参考になれば幸いです。
https://github.com/winuim/starter-embedded-app

# 参考サイト
[Shopify App CLI でアプリを開発する](https://qiita.com/t-kurasawa/items/1b18a7afd6fc854e70b7)
[TypeScript Next.js example](https://github.com/zeit/next.js/tree/canary/examples/with-typescript)
[Custom server with TypeScript + Nodemon example](https://github.com/zeit/next.js/tree/canary/examples/custom-server-typescript)