Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save kimiyuki/89d995d77e38e143400461bed2b07ff4 to your computer and use it in GitHub Desktop.

Select an option

Save kimiyuki/89d995d77e38e143400461bed2b07ff4 to your computer and use it in GitHub Desktop.

JSDocを活用した型付きGAS開発とClaspデプロイのベストプラクティス

JSDocを活用した型付きJavaScript開発を行い、clasp を使用してGoogle Apps Scriptにデプロイする形ですね。ローカルとGASのコード構成を統一しつつ、GASのウェブエディターでも編集可能な状態を目指します。

ディレクトリ構成の最適化

  • 単一のソースディレクトリ: ローカルではsrc/フォルダなどにソースコード(.jsファイル)をまとめます。各ファイルがGAS上ではスクリプトファイル(.gsまたは.js)に対応し、同名で配置されます。
  • フラットな構造: GASのプロジェクトはフォルダを持てずフラット構成になるため、ローカルでも深いネストは避けます (Google Apps Script - flat project clasp structure - Stack Overflow)。必要に応じてファイル名に「フォルダ名/ファイル名」の形式を含めると、clasp経由で仮想フォルダとして管理可能ですが、GASエディタ上ではファイル名にスラッシュが付いた形式で一覧表示されるだけです (Google Apps Script - flat project clasp structure - Stack Overflow)。そのため、可能ならフラットなディレクトリ構成で十分です。
  • ファイル分割と命名: 機能ごとにスクリプトファイルを分割します(例:「Spreadsheet関連処理.gs」「API呼び出し.gs」等)。一つのファイルに一つのクラスや論理的モジュールを定義すると見通しが良くなります (Modular pattern in Google Apps Scripting - Stack Overflow)。GASでは全てのスクリプトファイルが同一グローバル空間を共有するため、別ファイルの関数や変数も直接参照可能です (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。この特性を活かし、import/exportを使わずにファイル間で関数を共有できます(GAS環境ではES6のimport/export構文はサポートされません (GASで良い感じの開発体験をしたい #JavaScript - Qiita))。
  • マニフェスト: 必要に応じてappsscript.json(プロジェクトのマニフェスト)を用意し、src/内に置きます。これはGASの設定(例えばV8ランタイムの指定など)を保持します。Claspはマニフェストも一緒にプッシュします (clasp/README.md at master · google/clasp · GitHub) (clasp/README.md at master · google/clasp · GitHub)。

JSDocによる型定義と型チェック

  • JSDocコメントの活用: 全ての関数やグローバル定数に対してJSDocコメントを記述し、引数や戻り値の型、説明を書くようにします (GoogleAppsScript チーム開発のためのアイデア集)。例えば:
    /**
     * 与えられたユーザー名で挨拶メッセージを返します。
     * @param {string} name ユーザー名
     * @return {string} 挨拶メッセージ
     */
    function greet(name) {
      return `Hello, ${name}!`;
    }
    これによりエディタ上で補完が効き、関数使用時に型情報や説明が表示されます。
  • カスタム型の定義: オブジェクト構造など複雑な型は、JSDocの@typedefで定義して再利用します (GoogleAppsScript チーム開発のためのアイデア集)。例えばユーザー情報オブジェクトを定義し、関数の戻り値型に利用できます:
    /**
     * ユーザー情報型定義
     * @typedef {Object} UserInfo
     * @property {string} id ユーザーID
     * @property {string} name 名前
     * @property {number} age 年齢
     * @property {UserInfo[]} friends 友達リスト(再帰的にUserInfo型)
     */
    /**
     * 全ユーザー情報を取得する
     * @return {UserInfo[]} ユーザー情報配列
     */
    function getAllUsers() { ... }
    こうしておけば、例えば const users = getAllUsers(); と書いた際に、users[0].nameusers[0].friendsと入力すると名前や型情報が補完されます (GoogleAppsScript チーム開発のためのアイデア集)。
  • GAS組み込みオブジェクトの型: SpreadsheetAppDocumentAppなどGASの組み込みオブジェクトも型として扱えます。JSDoc内で@param {SpreadsheetApp.Sheet} sheetのように書けば、対象がスプレッドシートのSheetオブジェクトであることを明示できます (GoogleAppsScript チーム開発のためのアイデア集)。ローカル環境でそれらを補完・チェックするには、型定義をプロジェクトに導入します。
    • 型定義の導入: npm経由で@types/google-apps-scriptdevDependenciesにインストールし(npm i -D @types/google-apps-script)、VSCodeやTypeScriptにGASの型情報を認識させます (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。これでSpreadsheetApp等が未定義扱いされず補完が効くようになります。
    • 型定義の参照: 上記をインストール後、VSCodeは自動で型を参照しますが、必要に応じて/// <reference types="google-apps-script" />をファイル先頭に追加して明示的に参照させても構いません。
  • 静的型チェックの有効化: JSDocを書くだけではエディタ上に型エラーが表示されないため、TypeScriptのチェック機能を使います (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。方法はシンプルで、各ファイルの先頭// @ts-check コメントを記載するか、プロジェクトにjsconfig.json/tsconfig.jsonを配置してcheckJs: trueを有効にします (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。例えば:
    // @ts-check
    /** @type {number} */
    let count = "10";  // 文字列を代入しているのでエラーを検出
    このように書くと、VSCode上で型不一致のエラーが即座に表示され、リファクタリング時の安心感が高まります (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。
  • VSCodeでの補完とリファクタ: 上記設定により、VSCodeはJavaScriptでもTypeScript同等の型チェックと補完を行います。変数や関数のリネームも型情報に基づいて安全に行えるため、ローカルで大規模なリファクタリングが容易になります。

clasp設定(clasp.json の例)

  • clasp.jsonの配置: プロジェクトルート(例ではsrc/の一つ上のディレクトリ)に.clasp.jsonを配置します。clasp createclasp cloneコマンドを使うと自動生成されます (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。内容は以下のようになります:
    {
      "scriptId": "<スクリプトID>",
      "rootDir": "src"
    }
    • scriptId: デプロイ先となるGASプロジェクトのIDを指定します。既存プロジェクトをクローンした場合は自動で入力されます (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。
    • rootDir: ローカルプロジェクト内でソースコードが入っているディレクトリを指定します。例えば"src"にすれば、src以下のファイルだけがプッシュ対象になります (clasp/README.md at master · google/clasp · GitHub)。
    • (必要に応じて)fileExtension: ローカルファイルの拡張子とGAS上の拡張子の関連付けを指定できます。デフォルトではローカルの.jsファイルはプッシュ時にGAS上.gsファイルになります (Google Apps Script - flat project clasp structure - Stack Overflow)。ローカルも.gsに統一したい場合は"fileExtension": "gs"と設定できます。VSCodeで.gsをJavaScriptとして認識させる設定をする手間を考えると、ローカルでは.jsのままにしておくのがおすすめです。
  • .claspignoreの活用: .claspignoreファイルを作成すると、プッシュ時に無視するファイルを指定できます (clasp/README.md at master · google/clasp · GitHub)。例えばテストコードやnode_modules、ビルド成果物などGASに不要なものは除外可能です。デフォルトでもnode_modulesや.git配下は無視されます (clasp/README.md at master · google/clasp · GitHub)。必要に応じてパターンを追加してください。

ローカル開発ワークフロー(リンター/フォーマッター/エディタ設定)

  • エディタ: VSCodeなどモダンなエディタを利用し、上記の型チェック設定を有効にします。エディタ拡張でESLintやPrettierを導入すれば、保存時にコード整形や静的解析が走るように設定可能です。VSCodeの場合、workspaceに.vscode/settings.jsonを作成し、"editor.formatOnSave": true"eslint.validate": ["javascript"]等を設定すると良いでしょう。
  • ESLintの設定: コード品質を保つためESLintを導入します。GASのグローバル(例えばSpreadsheetApp)を未定義扱いしないよう、ESLint設定で環境とグローバル変数を指定します。例えば、.eslintrc.jsonに以下を追加します:
    {
      "env": { "es6": true },
      "globals": {
        "SpreadsheetApp": "readonly",
        "DocumentApp": "readonly",
        "DriveApp": "readonly",
        "...": "readonly"
      },
      "rules": {
        "no-undef": "off" // または必要なグローバルを上で定義
      }
    }
    こうすることで、ESLintがこれらを未定義と誤認して警告を出すのを防げます (GASで良い感じの開発体験をしたい #JavaScript - Qiita)(※TypeScriptの型定義を入れていても、ESLintは別途対応が必要です)。
  • コードフォーマッター: Prettierなどを使ってコード整形ルールを統一します。特にGASウェブエディタでの自動整形と衝突しないよう、セミコロンの有無やインデントなど基本的なスタイルを決めておくと安心です。可能であればチーム内でPrettierを標準にし、GASエディタではフォーマットしない運用にするか、逆にGASエディタの整形スタイル(2スペースインデント等)に合わせてPrettier設定を調整します。
  • ビルド/デプロイ手順: 今回はimport/exportを使わない方針のためビルド工程は不要です。その代わり、Claspでのプッシュを開発サイクルに組み込みます。開発中は:
    1. VSCode上でコード編集(適宜JSDoc追記、型チェックしつつ)。
    2. 保存してESLint/Prettier適用。
    3. ターミナルからclasp pushでGASにデプロイ。 (最初はclasp loginで認証が必要)
    4. ブラウザのGASエディタやスプレッドシート上でスクリプトを実行して動作確認。必要ならLogger.logやデバッグ機能で検証。
    5. 不具合があれば修正し、再度clasp push
      このループで進めます。clasp pushは差分のみアップロードするため高速です。
  • ソース管理: ローカルで開発する利点としてGit等のバージョン管理も活用できます。コードをコミット&プッシュして履歴を残し、必要なら過去バージョンを参照できます。チーム開発であればPull Requestによるレビューも可能です。Claspを使うことでGASプロジェクトをGit管理できる点は大きなメリットです (Use the command line interface with clasp | Apps Script)。

GASウェブエディターとの互換性維持

  • 直接編集可能なコード: トランスパイルやバンドルを行わないので、GAS上のコードは常に人間が読める状態(=ローカルで書いたそのままのコード)です。関数や変数名も保たれるため、必要であればGASのウェブエディタ上で直接コードを確認・微修正できます。
  • TypeScriptの直接利用を避ける理由: TypeScriptで書いてトランスパイルする方法もありますが、一度トランスパイルを挟むとスクリプトエディタ上では編集しづらくなるため本フローでは採用していません。 (clasp/docs/typescript.md at master · google/clasp · GitHub)にあるように、TypeScript導入後は生成されたコード(transpiled code)を編集しない運用が前提になります。JSDoc+チェック機能で型安全性を確保するのは、この制約を避けるためです。
  • V8ランタイムの利用: プロジェクトの設定(appsscript.json)でランタイムをV8に設定しておきます。現在のデフォルトではありますが、古いGAS環境から移行した場合は確認が必要です。V8により**クラス構文やアロー関数、const/let**等のモダンなJS構文が利用可能になります (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。ローカルで書いたES2015+のコードも問題なく実行できるため、互換性の心配が減ります。
  • エディタ間の変更同期: 万一GASエディタ上で直接修正を加えた場合でも、clasp pullコマンドでローカルに取り込めます。ローカルとリモート(GAS)のコードベースは単一のソースを共有しているため、どちらでの変更もclasp経由で同期可能です (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。チームメンバーがGAS上で緊急修正した場合などは、忘れずにローカルに取り込みましょう。
  • JSDocコメントの扱い: JSDocはコメントなので、GAS上で実行時に無視されます。エディタ上では多少見慣れないコメントが増えますが、ドキュメントとしても価値があるためそのまま残すことを推奨します (GoogleAppsScript チーム開発のためのアイデア集)。ライブラリ化して他プロジェクトから利用する場合も、JSDocコメントがある程度反映され自動ドキュメント生成にも使えます(完全ではないものの補完に役立ちます) (GoogleAppsScript チーム開発のためのアイデア集)。

サンプルプロジェクト例

以下に、上記ベストプラクティスを踏まえた簡単なサンプル構成とコード例を示します。

my-gas-project/
├── src/
│   ├── Code.js            -- メイン処理(エントリーポイントとなる関数定義)
│   ├── UserService.js     -- ユーザー関連処理(モジュール的に分割)
│   └── appsscript.json    -- マニフェスト(V8ランタイム指定など)
├── package.json           -- devDependenciesに@types/google-apps-script等
├── .clasp.json            -- clasp設定(scriptIdとrootDir指定)
├── .eslintrc.json         -- リンター設定
└── jsconfig.json          -- VSCode用設定(checkJs有効など)

Code.js – エントリーポイント用のコード。グローバル関数myFunctionから他のモジュールを利用しています。VSCodeで@ts-checkを有効にしているため、存在しない関数を呼ぶとエラーになります。

// @ts-check
/**
 * スプレッドシートのアクティブなシートに、全ユーザーの名前を書き出すサンプル関数
 */
function myFunction() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const users = getAllUsers();            // UserService.jsで定義された関数を呼び出し
  users.forEach(user => {
    sheet.appendRow([user.name]);         // JSDoc型定義によりuser.nameが補完される ([GoogleAppsScript チーム開発のためのアイデア集](https://zenn.dev/luth/articles/gas-team-coding#:~:text=function%20noticeUsers%28%29%20,0%5D.mySheet.%20%2F%2F%20getRange%2C%20setName%2C%20getLastRow%E3%81%AA%E3%81%A9%E2%80%A6))
  });
  Logger.log(`Appended ${users.length} users.`);
}

UserService.js – ユーザー情報を管理するモジュールのコード例です。JSDocでオブジェクト型Userを定義し、その型を使って関数の戻り値を注釈しています。これによりgetAllUsers()の結果(配列)の各要素がUser型として補完されます。

// @ts-check
/**
 * ユーザー情報オブジェクト
 * @typedef {Object} User
 * @property {string} name 名前
 * @property {string} email メールアドレス
 * @property {number} age 年齢
 */
 
/**
 * 全ユーザー情報を取得する(サンプルデータ)
 * @return {User[]} ユーザー情報の配列
 */
function getAllUsers() {
  return [
    { name: "山田太郎", email: "taro@example.com", age: 30 },
    { name: "Jane Doe", email: "jane@example.com", age: 25 }
  ];
}

上記の構成では、ローカル開発GAS実行環境のコードは常に同期され、JSDocによる型情報のおかげでIDE上で高度な補完・検証が効きます。例えばgetAllUsersの実装でageを文字列にすると、VSCode上ですぐにエラーが表示されるでしょう (GASで良い感じの開発体験をしたい #JavaScript - Qiita)。一方、デプロイされたコードはそのままGAS上で実行可能であり、GASエディタからmyFunctionを実行して正しく動作することを確認できます。

以上のように、JSDocを活用した型付きJavaScript開発claspによるデプロイを組み合わせることで、型安全性と開発体験を向上させつつ、GAS独自環境との互換性も保った開発フローを実現できます。これらのベストプラクティスを参考にプロジェクトを構成すれば、ローカルとGAS双方で快適に開発・運用できるでしょう。 (GASで良い感じの開発体験をしたい #JavaScript - Qiita) (clasp/docs/typescript.md at master · google/clasp · GitHub)

リネーム機能とコード構成の工夫について

Google Apps Script(GAS)でグローバル関数を直接定義している場合、IDE(例:VS Code)のリネーム機能(F2)がシンボル間の関係を正確に把握できず、プロジェクト全体でのリネームがうまく機能しないという問題があります。この問題に対する対策としては、内部ロジックや共通処理をグローバル関数として定義するのではなく、ひとつの名前空間(例:MyApp などのオブジェクト)にまとめる方法が有効です。

【具体例】

  • 内部処理のまとめ
    内部のロジックや共通の処理は、グローバルオブジェクト(例:MyApp)に属する関数として定義し、IDE上でのリネームやリファクタリングを容易にする。たとえば、MyApp.processData として関数をまとめることで、関数名の変更が各ファイル間で正しく反映されやすくなる。

  • エントリーポイントのグローバル定義
    一方、GAS の実行トリガーやウェブエディタ上で直接実行する必要がある「エントリーポイント」や、個別に実行させたい関数は、グローバル関数として定義する。これらのグローバル関数は、内部処理を呼び出す薄いラッパーとして機能させることで、GAS の実行環境とローカル開発環境の双方の要件を満たす。

【実装例】

// 内部処理:名前空間にまとめる
var MyApp = MyApp || {};

/**
 * 入力された文字列を大文字に変換する処理
 * @param {string} input
 * @return {string}
 */
MyApp.processData = function(input) {
  return input.toUpperCase();
};

// エントリーポイント:グローバル関数として定義
function myEntryPoint() {
  var result = MyApp.processData("hello world");
  Logger.log(result);
}

このような構成にすることで、内部ロジック部分は IDE の補完やリファクタリング機能を十分に活用でき、エントリーポイントは GAS の実行要件を満たすというメリットがあります。開発効率と保守性の両面で、非常にバランスのとれた設計と言えるでしょう。

types.d.ts の利用

declare namespace MyUtils {
  function deleteEmptyRows(sheet: GoogleAppsScript.Spreadsheet.Sheet): void;
}

のような形で型定義ファイルを作成する

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment