🍜

ClaspとZodでGASのバリカタ(Vali型)開発環境を作る

2022/11/16に公開

TL;DR

Zod の導入設定をした GAS プロジェクトのテンプレートを作って公開しました。

https://github.com/cloud-ace/zenn-gas-zod-template

はじめに

こんにちは、クラウドエースの伊藝です。

突然ですが、皆さんは バリカタ は好きですか?

私は豚骨ラーメン屋さんに行くと、いつも バリカタ を頼んでいます。

家でインスタント麺を作るときも、プログラムを作るときも バリカタ です。

そうです。「Validation 型 (バリデーション・カタ)」、略して バリカタ です。

型ってなに?

型とは、プログラムで扱われるデータがどういう形式のものかを示すものです。

型があることで、我々プログラマーはデータがどういう形式であるかを認識しやすくなります。

バリデーションってなに?

バリデーションとは、入力されたデータが適切な形であるかをチェックして、不適切であれば例外としてエラーを返す処理のことです。

GAS ってなに?

GAS とは Google Apps Script の略称で、JavaScript をベースとしたプログラミング言語です。

https://developers.google.com/apps-script

GAS は Google のプロダクトと連携することができ、例えば Gmail や Google Sheets の処理を自動化することができます。

また、HTTP API を通して Google 以外が提供している外部サービスと連携することもできます。

Clasp

Clasp とは、GAS を開発するためのツールで、GAS のプロジェクトの作成・編集・ビルド・デプロイをすることができます。

https://github.com/google/clasp

GAS は通常 JavaScript で記述するのですが、Clasp を使うことにより、TypeScript で記述することができます。

TypeScript とは JavaScript の型の機能を強化した言語で、型安全な開発を実現できます。

TypeScript は非常に依存性が高く、一度使ってしまうと二度と普通の JavaScript が書けなくなってしまう安全な代物です。

Clasp の TypeScript で記述できる機能を使うことで、トランスパイルするまではある程度の型安全性が保たれます。

しかし、実行時に違う型のデータを入力されると、そのデータの型が自動で検証 (バリデーション) されることはなく、予期しない動作が発生してしまう恐れがあります。

これでは バリカタ ではなく、ただの カタ です。

Zod

Zod は TypeScript ファーストなバリデーションライブラリです。

https://github.com/colinhacks/zod

サイズがとても小さく、依存関係がゼロとなっています。

他の様々なライブラリやフレームワークでも採用が増えており、注目されています。

GAS をバリカタで書く

本記事では、Clasp と一緒に Zod を導入して作る GAS のバリカタ (Validation 型) 開発環境の手順を紹介していきます。

Clasp

インストール・設定

ディレクトリを作成して yarn で初期化します。

mkdir gas-zod-template
cd gas-zod-template
yarn init

clasp をインストールします。

yarn add @google/clasp

TypeScript でソースコードを記述するために typescript を、GAS 関連の補完を使うために @types/google-apps-script をインストールします、

yarn add --dev typescript @types/google-apps-script

clasp コマンドで Google アカウントにログインします。

yarn clasp login

既に GAS のプロジェクトがある場合はクローンします。

[scriptId] は GAS プロジェクト固有の ID です。

これは GAS プロジェクトを開いたときの URL に含まれています。

https://script.google.com/home/projects/[scriptId]/edit
yarn clasp clone [scriptId]

新しく作成する場合は以下のコマンドを実行します。

yarn clasp create

新規作成すると、.clasp.jsonappsscript.json が生成されます。

.clasp.json には、どの GAS プロジェクトと連携されているか、どのスクリプトをアップロードするかなどの Clasp のローカルの設定が記述されています。

詳しい説明は以下のページにあります。(英語)

https://github.com/google/clasp#project-settings-file-claspjson

appsscript.json には、GAS プロジェクトの設定が記述されています。本記事で編集することはありません。

こちらも詳しい説明は以下のページにあります。(英語)

https://developers.google.com/apps-script/manifest

.clasp.json を開いて、以下のように変更します。

.clasp.json
{
  "scriptId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
-  "rootDir": "/XXXX/YYYY/ZZZZ"
+  "rootDir": "./src"
}

src ディレクトリを作成し、appsscript.jsonsrc の下に移動します。

mkdir src
mv ./appsscript.json ./src

src/main.ts を作成し、以下のように記述します。

src/main.ts
type User = {
  name: string;
  age: number;
};

function main() {
  const user: User = {
    name: 'John',
    age: 20,
  };

  console.log(`${user.name} is ${user.age}`);
}

この時点でのディレクトリ構成は以下のようになっています。

$ tree -a
.
├── .clasp.json
├── node_modules
~~~
├── package.json
├── src
│   ├── appsscript.json
│   └── main.ts
└── yarn.lock

実行

ソースコードを GAS にプッシュします。

yarn clasp push

以下のコマンドを実行して GAS のプロジェクトの Web エディタを開きます。

yarn clasp open

画面上部の「実行」をクリックすると main 関数を実行することができます。

ソースコードに注目すると、TypeScript が JavaScript にトランスパイルされていることがわかります。

Webpack

インストール・設定

npm パッケージを GAS に導入するために Webpack をインストールします。

yarn add --dev webpack webpack-cli gas-webpack-plugin

Babel をインストールします。

yarn add --dev @babel/core @babel/preset-typescript babel-loader

ESLint と Prettier をインストールします。

yarn add --dev eslint prettier eslint-config-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-webpack-plugin @babel/eslint-parser

インストールしたツール群の設定を行うために以下のファイルを作成・編集します。

  • tsconfig.json: TypeScript の設定ファイル
  • .babelrc: Babel の設定ファイル
  • .eslintrc.json: ESLint の設定ファイル
  • webpack.config.js: Webpack の設定ファイル
  • package.json: Node.js の設定ファイル

https://github.com/itok01/gas-zod-template/blob/main/tsconfig.json

https://github.com/itok01/gas-zod-template/blob/main/.babelrc

https://github.com/itok01/gas-zod-template/blob/main/.clasp.json

https://github.com/itok01/gas-zod-template/blob/main/.eslintrc.json

https://github.com/itok01/gas-zod-template/blob/main/webpack.config.js

package.jsonscripts を追加します。

package.json
  ...
+  "scripts": {
+    "build": "webpack",
+    "push": "yarn build && clasp push",
+    "push:watch": "yarn build --watch & clasp push --watch"
+  },
  ...

実行

src/main.tssrc/app/main.ts に移動して、main 関数の頭に export を追記します。

mkdir src/app
mv src/main.ts src/app/main.ts
src/app/main.ts
type User = {
  name: string;
  age: number;
};

-function main() {
+export function main() {
  const user: User = {
    name: 'John',
    age: 20,
  };

  console.log(`${user.name} is ${user.age}`);
}

src/index.ts を作成し、以下のように記述します。

このファイルは GAS でのエントリポイントとなり、ここで global.* に代入された関数は GAS で実行可能となります。

src/index.ts
import { main } from "./app/main";

declare const global: {
  [x: string]: unknown;
};

global.main = main;

appsscript.jsondist の下に移動します。

mkdir dist
mv ./appsscript.json ./dist

Webpack でビルドしたファイルを GAS に push するために .clasp.jsonrootDir"./dist" に変更します。

.clasp.json
{
  "scriptId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
-  "rootDir": "./src"
+  "rootDir": "./dist"
}

ソースコードをビルドして GAS にプッシュします。

yarn push

Web エディタから実行すると、ソースコードの処理部分に変更はないため、先程と同じ結果が表示されます。

以下のコマンドを実行すると、ファイルの変更が自動的に検知され、ビルドされて GAS にプッシュされて便利です。

yarn push:watch

Zod

本記事の本題の Zod をインストールします。

yarn add zod

バリデーションする値を入力するため、GAS で簡易的なウェブアプリを作成します。

app/src/main.ts を以下のように変更します。

app/src/main.ts
-type User = {
-  name: string;
-  age: number;
-};
-
-export function main() {
-  const user: User = {
-    name: 'John',
-    age: 20,
-  };
-
-  console.log(`${user.name} is ${user.age}`);
-}
+import { z } from "zod";
+
+export const UserSchema = z.object({
+  name: z.string(),
+  age: z.number(),
+});
+
+export function doGet(e: GoogleAppsScript.Events.DoGet): GoogleAppsScript.Content.TextOutput {
+  const user = UserSchema.parse(e.parameter);
+  Logger.log(user);
+  return ContentService.createTextOutput(`${user.name} is ${user.age}`);
+}

doGet(e) は GAS でウェブアプリを作成するために使う関数名です。

https://developers.google.com/apps-script/guides/web

実行したい関数の名前が変わったため、src/app/index.ts も以下のように変更します。

src/app/index.ts
-import { main } from "./app/main";
+import { doGet } from "./app/main";

declare const global: {
  [x: string]: unknown;
};

-global.main = main;
+global.doGet = doGet;

この時点でのディレクトリ構成は以下のようになっています。

$ tree -a
.
├── .babelrc
├── .clasp.json
├── .eslintrc.json
├── dist
│   ├── appsscript.json
│   └── index.js
├── node_modules
~~~
├── package.json
├── src
│   ├── app
│   │   └── main.ts
│   └── index.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock

ソースコードをビルドして GAS にプッシュします。

yarn push

以下のコマンドを実行して GAS のプロジェクトを開きます。

yarn clasp open

画面右上の「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。

「種類の選択」からウェブアプリを選択し、パラメータはそのままで「デプロイ」ボタンをクリックします。

以下のようなウェブアプリの URL が表示されます。

https://script.google.com/macros/s/XXXXXXXXXX/exec

URL の末尾にパラメータを追記してアクセスします。

https://script.google.com/macros/s/XXXXXXXXXX/exec?name=John&age=20

パラメータが User としてパースされてメッセージが表示されます。

John is 20

試しに agetwenty にします。

https://script.google.com/macros/s/XXXXXXXXXX/exec?name=John&age=twenty

すると、Zod で型のバリデーションが行われてエラーが表示されます。

[ { "code": "invalid_type", "expected": "number", "received": "string", "path": [ "age" ], "message": "Expected number, received string" } ] (line 291, file "index")

これで GAS に Zod を導入し、型のバリデーションをすることができました。

Zod は stringnumber などの単純な型のバリデーションを行うだけでなく、以下のように様々なデータを安全に扱うための機能を備えています。

  • 配列以外にも Union や Map、Set などのデータ構造をフィールドに指定する
  • 「〇〇以上 & 〇〇以下」などの条件を指定する
  • NULL を許容するかしないかを指定する
  • etc.

https://zenn.dev/uttk/articles/bd264fa884e026

おわりに

設定が少し面倒ですが、これでバリカタな GAS を書けるようになりました。

型のバリデーションを利用することで、Web アプリケーション以外にも、Google Sheets を使ったアプリケーションを型安全に開発することができます。

「こんな設定したくないよ~」という人のために、バリカタな GAS のテンプレートを用意しました。

https://github.com/cloud-ace/zenn-gas-zod-template

.clasp.json にプロジェクト ID を設定して、yarn installyarn deploy をするだけで簡単にデプロイをすることができます。

1 人でも多くの人が GAS プロジェクトをバリカタで開発してくれるようになってくれると嬉しいです。

参考

https://www.ykicchan.dev/posts/2020-07-12

https://zenn.dev/tacck/articles/20211218_gas

https://qiita.com/devaoyama/items/b8b9c17df548d490b566

https://qiita.com/Ryo-Nakano/items/50366600543c2888ada7

https://github.com/matcher-inc/gas-template

Discussion