SOMPO Digital Lab の小菅です。ちょっとしたツールを Google Apps Script (GAS) で作ろうとしました。最初は手軽に Web エディタで書き始めたものの、少し複雑なロジックを書き始めると、やはり型とテストが欲しくなってきます。これを実現するために、他のプロジェクトで採用することが多い TypeScript & Vitest でローカル開発環境を整えました。その際の構築メモです。
要件
- Google Apps Script を用いた standalone app
- TypeScript を使用して記述
- Vitest を利用したユニットテストの作成
- プロジェクトは1ファイルに収まる程度のコード量
導入編
GAS のローカル開発は Google 謹製の clasp を使います。clasp は TypeScript に対応しているため、公式のドキュメントをなぞるだけでプロジェクトを作っていくことができます。
clasp のインストール
npm が使える前提で進んでいきます。clasp コマンドが使えるよう global に clasp をインストールします。
npm install -g @google/clasp
ログイン
clasp login
プロジェクト作成
clasp create
コマンドはリモートにプロジェクトを作成し、ローカルの現在のディレクトリに設定ファイルを展開します。
また、このディレクトリ名がプロジェクト名にも適用されてしまうため、先にプロジェクトディレクトリを作成します。
(--title 引数でプロジェクト名を指定することもできます。また、Web UI から後で変更することも可能です。)
mkdir my-standalone-app && cd my-standalone-app clasp create
生成された appsscript.json の timeZone を変更
不要な不幸を避けるため、東京に変更しておきます。
{ "timeZone": "Asia/Tokyo", "dependencies": {}, "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8" }
GASの型定義を追加
@types/google-apps-script
をインストールすることで、GAS上で利用できるGoogleサービスの型を適用することができます。
npm install -D @types/google-apps-script
以下の内容の tsconfig.json を追加
{ "compilerOptions": { "lib": ["esnext"], "experimentalDecorators": true } }
ここで一旦プロジェクトが作成されたため、script.ts
を作成して実際のコードを書いていきます。
export const greeter = (person: string) => { return `Hello, ${person}!`; } export function testGreeter() { const user = 'Grant'; Logger.log(greeter(user)); }
clasp push
で .gs
へのトランスコンパイル(ES6+ から ES3 へ)と Apps Script server へのアップロードを行ってくれます。
clasp open
でプロジェクトを開くことができますので、まずは一旦問題なく動くか確認しましょう。
ユニットテストを書く
作成した functions に対してユニットテストを書いていきます。まずは Vitest のインストール。 Vitest は比較的新しいテストフレームワークで、jest とある程度の互換性があります。 test() や expect() などの関数が暗黙的にグローバルに生えず、高速に動作する等のメリットがあるため気に入っています。
npm install -D vitest
script.test.ts
ファイルを作成し、先程のスクリプトに対応するテストを書いていきます。
import { expect, vi, test } from "vitest"; import { testGreeter, greeter } from "./script"; const log = vi.fn((data: any) => {}); vi.stubGlobal("Logger", { log, }); test("greeter", () => { expect(greeter("Grant")).toBe("Hello, Grant!"); }); test("testGreeter", () => { expect(testGreeter()).toBe(undefined); expect(log.mock.calls[0]).toEqual(["Hello, Grant!"]); });
npx vitest
でテストを実行します。 GAS では Logger や PropertiesService 等、その他追加したライブラリ(Calendar など)がグローバルに生えてきます。わざわざ GAS を使う以上こういったライブラリを利用することになるはずですので、Vitest の場合は stubGlobal を使ってスタブを作成します。
テストファイルをclaspから除外する
ここまででローカルのテストが動きましたが、このまま push するとテストまでアップロードされてしまい、「vi が存在しない」といったエラーが出てしまい動かしたい関数も動かなくなります。.claspignore
ファイルで除外することができますので、テストファイルは push されないようにしましょう。
*.test.ts
以上により、現代的な GAS の開発ができるようになりました。
この記事で諦めていること
GAS は ES modules に対応していないため、import/export が使えません。ローカルのエディタではエラーが無いように見えてしまいますが、push しても import 元が見つからず動かないです。つまり npm パッケージのライブラリも使えないわけですが、今回の用途ではそこまでは不要だったため手を付けていません。npm パッケージを使いたい、あるいはファイル分割が必要なほどのコードを書きたい場合は Rollup や Webpack、esbuild-gas-plugin 等を利用してバンドルする必要があります。
SOMPO Digital Labでは一緒に働くソフトウェアエンジニアを募集しています
弊社では Rollup を使ってバンドルするところまでやれるソフトウェアエンジニアを募集中です。
以下のリンクからカジュアル面談の応募ができるので、興味を持っていただけた方は是非話を聞きに来て下さい。