エクスポートしたい(export / import)

1// src/sheets.ts
2export function highlightRow(
3  sheet: GoogleAppsScript.Spreadsheet.Sheet,
4  rowIndex: number,
5  color: string,
6): void {
7  sheet.getRange(rowIndex, 1, 1, sheet.getLastColumn()).setBackground(color);
8}

exportで、モジュールから関数やクラスを外部に公開できます。 これはtsc + rollup + claspのワークフローで ローカル開発する場合の標準的な書き方で、TypeScript・JavaScriptの言語機能そのものです。

注釈

rollupがexport/importを解決してすべてのモジュールを1つのファイルにバンドルするため、 GAS本体がexport/importに対応している必要はありません。 バンドル後のコードにはexport文自体は残りません。

GASエディターに直接コードを貼り付けて使う場合は事情が異なります。 その場合の書き方は、後述の「GASエディターに直接書きたい場合は」を参照してください。

まとめてエクスポートしたい(export *)

1// src/index.ts(エントリーポイント)
2export * from "./sheets";

プロジェクトルートにエントリーポイントを作成し、 外向けに公開するAPIをまとめてエクスポートできます。 エントリーポイントにはsrc/index.tsのようなファイル名を使うことが多いです。

注意

export * from "./sheets.ts"のように拡張子(.ts)を含めて書くと、 tsconfig.jsonの設定によってはAn import path can only end with a '.ts' extension when... というエラーになります。 拡張子なし("./sheets")で書くのが基本です。

インポートしたい(import)

1// src/main.ts
2import { highlightRow } from "./sheets";
3// もしくは、エントリーポイント経由でまとめて
4import { highlightRow } from "./index";

importで、他のモジュールがexportした関数やクラスを取り込めます。 rollupがバンドル時にこれらを解決するので、 実行時にrequireのような読み込み処理が発生するわけではありません。

GASエディターに直接書きたい場合は

ここまでのexport/importは、ローカルでTypeScriptを書いてrollupでバンドルする前提の書き方です。 GASエディターで直接.gs(.js)ファイルを編集するスタイルでは事情が異なります。 GASのスクリプトファイルには、標準のexport/importもCommonJSのrequireもありません。 ファイル同士は単に同じグローバルスコープを共有しているだけです。

 1// Node環境用のエクスポート(GAS環境では無視される)
 2if (typeof module !== 'undefined' && module.exports) {
 3    module.exports = {
 4        ModuleName,
 5        anotherModuleName,
 6        someFunction,
 7    };
 8}
 9// GAS環境用のエクスポート(Node環境では無視される)
10else {
11    this.ModuleName = ModuleName;
12    this.anotherModuleName = anotherModuleName;
13    this.someFunction = someFunction;
14}

module.exportsの有無を確認することで、 Node環境(jestなどでのテスト実行時)かGAS環境かを区別し、 両方の環境で動くコードを書く、という古典的なテクニックです。 rollupを使わずに、ローカルでNode向けにテストしつつ、 同じファイルをそのままGASエディターにも貼り付けたい場合に使われます。

 1function importClassName() {
 2    // ClassName が定義されている場合
 3    if (typeof ClassName !== 'undefined') {
 4        return new ClassName();
 5    }
 6
 7    // ライブラリで定義されている場合
 8    if (typeof LibraryName !== 'undefined' && LibraryName.ClassName) {
 9        return new LibraryName.ClassName();
10    }
11
12    // requireが使える場合(=Node環境)
13    if (typeof require === 'function') {
14        try {
15            const { ClassName } = require('./ModuleName');
16            return new ClassName();
17        } catch (e) {
18            throw new Error(`ClassName not available via require()`);
19        }
20    }
21    throw new Error(`ClassName is not available in this environment`);
22}
23
24const _cn = importClassName();
25_cn.someFunction();

インポート側も同様に、typeofでグローバルに定義されているか、 requireが使える環境かを実行時に確認してから読み込みます。

ヒント

tsc + rollup + claspのワークフローが組める場合は、 このセクションの書き方をあえて使う必要はありません。 標準のexport/importのほうがシンプルで、型チェックの恩恵も受けられます。

リファレンス