グローバル変数したい(globalThis)

1(globalThis as Record<string, unknown>)["myFn1"] = myFn1;
2(globalThis as Record<string, unknown>)["myFn2"] = myFn2;
3(globalThis as Record<string, unknown>)["myFn3"] = myFn3;
4(globalThis as Record<string, unknown>)["myFn4"] = myFn4;
5
6// もしくは
7const g = globalThis as Record<string, unknown>;
8g["myFn1"] = myFn1;
9g["myFn2"] = myFn2;

globalThisは、ECMAScript標準の環境に依存しないグローバル変数の置き場所です。 src/index.tsのようなエントリーポイントとなるファイルで、 ほかのモジュールからexportした関数をglobalThisに登録しておくとよいです。

とくにGASでは、rollupでバンドルすると、関数は内部スコープに閉じ込められてしまいます。 そのままではGASから参照できませんが、globalThisに登録することで、外から呼べるようになります。

注釈

JavaScriptでは、ブラウザ環境だとwindow、Node環境だとglobal、 のように、環境によってグローバル変数名が違います。 globalThisを使えば、環境を気にせずに同じ書き方で利用できます。

型安全に登録したい

 1import { myFn1, myFn2, myFn3 } from "./module";
 2
 3interface GASGlobalThis {
 4  myFn1?: typeof myFn1;
 5  myFn2?: typeof myFn2;
 6  myFn3?: typeof myFn3;
 7}
 8
 9if (typeof globalThis !== "undefined") {
10  const gas = globalThis as unknown as GASGlobalThis;
11  gas.myFn1 = myFn1;
12  gas.myFn2 = myFn2;
13  gas.myFn3 = myFn3;
14}

Record<string, unknown>+文字列キーの書き方は手軽ですが、 キー名をタイプミスしても型チェックでは検出できません。 公開する関数・クラスの一覧をinterfaceとして定義しておくと、 存在しないプロパティへの代入がコンパイルエラーになるため、より安全です。

typeof globalThis !== "undefined"のガードは、 globalThisが存在しない環境(テスト実行時など)でエラーにならないようにするためのものです。

トリガー関数を登録したい

1(globalThis as Record<string, unknown>)["onFormSubmit"] = onFormSubmit;
2(globalThis as Record<string, unknown>)["onEdit"] = onEdit;
3
4// 手動実行する関数は、先頭にアンダースコアを付けて登録する
5(globalThis as Record<string, unknown>)["_setupTriggers"] = setupTriggers;

ScriptApp.newTrigger("関数名")でトリガーを作成する場合、 指定する文字列はglobalThisに登録したキー名と完全に一致している必要があります。 onFormSubmit・onEditのようなイベントハンドラー系の関数は、素の名前のまま登録します。

ヒント

Apps ScriptエディターのRunドロップダウン(実行ボタンの隣の関数選択)には、 トップレベルで宣言された関数しか表示されません。 rollupでバンドルしたcode.jsの中身はすべて内部スコープに隠れてしまうため、 このままではエディターから手動実行できません。

対策として、エディターから直接実行したい関数だけ、別ファイル(例:stubs.js)に 以下のような「橋渡し用」のトップレベル関数を用意しておく方法があります。

1// stubs.js(clasp pushの対象に含める)
2function setupTriggers() {
3  globalThis["_setupTriggers"]();
4}

このとき、バンドル本体(code.js)側では実装をアンダースコア付きの名前 (_setupTriggers)で登録しておく必要があります。 アンダースコアなしで登録してしまうと、GASがトップレベル宣言をglobalThisに 巻き上げる際にstubs.js側の宣言と衝突し、 スタブが自分自身を呼び出して無限ループ(スタックオーバーフロー)になります。

リファレンス