SOMPO Digital Lab 開発チームブログ

安心・安全・健康に資する開発情報を発信します

Google Apps Scriptのローカル開発環境をTypeScriptとVitestで整える

SOMPO Digital Lab の小菅です。ちょっとしたツールを Google Apps Script (GAS) で作ろうとしました。最初は手軽に Web エディタで書き始めたものの、少し複雑なロジックを書き始めると、やはり型とテストが欲しくなってきます。これを実現するために、他のプロジェクトで採用することが多い TypeScript & Vitest でローカル開発環境を整えました。その際の構築メモです。

要件

  • Google Apps Script を用いた standalone app
  • TypeScript を使用して記述
  • Vitest を利用したユニットテストの作成
  • プロジェクトは1ファイルに収まる程度のコード量

導入編

GAS のローカル開発は Google 謹製の clasp を使います。clasp は TypeScript に対応しているため、公式のドキュメントをなぞるだけでプロジェクトを作っていくことができます。

github.com

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 を使ってバンドルするところまでやれるソフトウェアエンジニアを募集中です。

以下のリンクからカジュアル面談の応募ができるので、興味を持っていただけた方は是非話を聞きに来て下さい。

www.wantedly.com