TypeScript+clasp+esbuildでGASのローカル開発をもっと便利に
はじめに
本記事では、TypeScript+clasp+esbuildを用いたより便利なGoogleAppScriptのローカル開発環境の構築方法を紹介しています。
claspについて
GoogleAppScriptで開発しようとすると、規模によってはgitを使って複数人でのバージョン管理やTypeScriptを利用した開発環境を準備したくなります。
そこで、@google/claspという開発ツールを用いることで、TypeScriptの利用やCLIでGoogleAppScript上にdeployが可能になりローカルでのコード管理が容易になります。また、必要に応じてprettierやeslintなど開発を便利にしてくれる機能なども導入可能になります。
しかし、clasp単体を利用したやり方ではTypeScriptのコンパイルはよしなにやってもらえるものの、ESmoduleの機能が使えずファイルをまたいでも全ての変数がグローバルになりimportやexportが使えず普段のような開発体験を得ることができません。
esbuildの利用について
上記の通り、このままではimportやexportの機能が使えないので利用したければ、claspのtsのコンパイル機能を使うのではなく、自前でtsのimportを解決してバンドル・コンパイルの必要があります。
そこで、esbuildとesbuildのGAS用のプラグインを使ってGAS用のjsへのバンドル・コンパイルを行うことでimportやexportの機能を使いつつGAS用のスクリプトを書けるようになります。
プロジェクト構成
最終的なスクリプトや設定ファイルの構成は以下のようになっています。
src配下にTypeScriptのコードを書き、GASにはdist配下のファイルをデプロイします。
.
├── dist/
│    ├── appsscript.json
│    └── main.js
├── src/
│    ├── main.ts
│    └── sample.ts
├── .clasp.json
├── esbuild.js
├── package.json
└── tsconfig.json
また、今回試したソースコードもこちらに置いているため必要があればご確認ください。
プロジェクトのセットアップ
以下では、実際にこの環境を再現する手順について記述していきます。
- package.jsonファイルの作成
 
npm init -y
- TypeScriptやclaspなどの必要なパッケージのインストール
 
npm install -D clasp typescript @google/clasp @types/google-apps-script
- tsconfigの作成
 
npx tsc --init
基本的に好みの設定で良いとは思いますが、今回はclaspのドキュメントにもある通り、以下の設定は追加しておきました。
{
  "compilerOptions": {
    "lib": ["esnext"],
    "experimentalDecorators": true
  }
}
- esbuildのインストールとbuildスクリプトの作成
 
npm install -D esbuild esbuild-gas-plugin
import esbuild from "esbuild";
import { GasPlugin } from "esbuild-gas-plugin";
esbuild
  .build({
    entryPoints: ["./src/main.ts"],
    bundle: true,
    minify: true,
    outfile: "./dist/main.js",
    plugins: [GasPlugin],
  })
  .catch((e) => {
    console.error(e);
    process.exit(1);
  });
- claspでのプロジェクトのセットアップ
 
npx clasp create --type standalone
{
  "scriptId": "--自動生成されたスクリプトID--",
  "rootDir": "./dist"
}
ここでappsscript.jsonを dist/appsscript.jsonに移行しておきます。
- 検証用のTypeScriptコードを作成
 
import { sample } from "./sample";
// GASから参照したい変数はglobalオブジェクトに渡してあげる必要がある
(global as any).sample = sample;
export function sample() {
  console.log("---sample---");
}
- buildやdeploy用のコマンドの追加
 
  "scripts": {
    "build": "node esbuild.js",
    "push": "clasp push",
    "open": "clasp open", # GAS側のスクリプト画面をブラウザで開く
    "deploy": "npm run build && npm run push"
  },
- 最後に実行確認
 
npm run deploy
npm run open

実際にimportしたsampleという関数を動かせていることを確認できました!
おわりに
ちょっとした社内ツールを開発したりする際などに私自身よくこのやり方をしています。
この記事がローカルでのGAS開発を検討している方のお役に立てると幸いです。
ユーザーファーストなサービスを伴に考えながらつくる、デザインとエンジニアリングの会社です。エンジニア積極採用中です!hrmos.co/pages/funteractive/jobs
Discussion