💭

Google Apps Script で複数ファイル開発をうまくやるために

2021/12/18に公開

この記事は コープさっぽろ オープン社内報  Advent Calendar 2021 18日目の記事です。

コープさっぽろ オープン社内報  Advent Calendar 2021

Google Apps Script を使いたい僕ら

コープさっぽろではいわゆる "DX" に取り組んでいます。
ざっくりと言えば、事業部門の課題に対して、事業部門のメンバーとわれわれエンジニアが一緒にタッグを組んでいる、という進め方です。

そんなわれわれの強い味方が Google Apps Script (以降GASと表記) です。
それなりの制限はありますが、気軽にサーバレスな実行環境を得られますし、われわれが導入している Google Workspace の各種リソースとの親和性も高いので、色々と活用しています。

最近は GAS の実行エンジンの改善によりほぼ JavaScript (ES2015 相当) のコードを書くことができますし、コンソール上のエディタの改善によりコードサジェストなどの機能が利用できるようにもなっています。

なのですが、不満点もあったりするわけです。

  1. GAS のソースコードをコンソールから取り出せない
  2. import / export の構文に対応していない
  3. 複数ファイルに分割する場合コンソール上の並び順に読み込まれるので順番を開発者が考えなければいけない
  4. NPM でパッケージを気軽に追加できない

1.は、コードの履歴管理ができない、という問題です。
これについては Clasp というツールが提供されているので、CLIからであれば取り出し可能です。取り出した後のコードをローカルで編集して GAS コンソールへアップロードすることもできます。

2.、3.については、 Clasp だけでは解決できない問題になります。
現代に生きるエンジニアとして、 JavaScript (TypeScript) を書くなら import / export を使いたいですし、ファイルの並び順を気にせずに開発を行ないたいものです。

4.も同様で、使い慣れたパッケージ・ライブラリを活用して、開発効率を高めていきたいですよね。

ということで、 Webpack も使ってなんとかしましょう、というのがこの記事です。

今後コープさっぽろで GAS を書く方は、この課題を共有した同志としてこの手順を取り入れてみてください。

解決策

ファイル分割するとうまくいかない、手間がかかる、でも開発中は分割したい、というわけなので、 Clasp でアップロードする前にファイルを一つに結合してしまおう、というのが解決策になります。

JavaScript / TypeScript の結合を行なってくれるツールは世の中にいくつか存在しています。この辺は Web フロントエンド界隈でよく使われているものなので、情報も見つけやすいです。
(Webページでは、細かいJavaScriptファイルを複数読み込むよりも、多少大きくても一つのファイルだけを読み込む方がページの表示速度をあげてくれます。そのため、 Web フロントエンドではこういったツールやテクニックが発達しています。)

今回は、私が比較的使い慣れていることと、情報の検索しやすさから Webpack を選択しました。

環境

手元の環境としては、 Node.js が必須です。

Clasp の実行には v10.13 以上となっていますが、できれば v16 系を使うことをお勧めします。

やり方

Clasp インストール

npm install -g @google/clasp

利用する Google アカウントで Clasp を利用できるように設定の変更を行ないます。
詳細は、 Clasp の README を確認してください。

設定ができたら、 Clasp と Google アカウントの連携を取るためにログインを実行します。

clasp login

アカウントを選択し、各種権限を許可するとこの後の作業が続けられる様になります。

Spreadsheet 作成

実際に連携する GAS のリソースを Google Workspace 上に作成します。
これは Clasp を使ってコマンドから作成することも可能です。

今回はブラウザで Spreadsheet を新規作成して、そこに連携するスクリプトを生成してからの手順で進めます。

  1. Google ドライブから Spreadsheet 作成
  2. 拡張機能 > App Script 選択
  3. サイドメニューのプロジェクトの設定 > スクリプトID をコピー

手元へ clone

下記のコマンドで、 Google Workspace 上のスクリプトと連携を取れるようになります。
[YOUR_SCRIPT_ID] の部分に、上記でコピーしたスクリプトIDを当てはめてください。

$ clasp clone [YOUR_SCRIPT_ID]

...

Cloned 2 files.
└─ /[YOUR_PATH]/sample/appsscript.json
└─ /[YOUR_PATH]/sample/コード.js
Not ignored files:
└─ /[YOUR_PATH]/sample/appsscript.json
└─ /[YOUR_PATH]/sample/コード.js

Ignored files:
└─ /[YOUR_PATH]/sample/.clasp.json
$

初期構成を変更

いくつかファイルができていますが、一部構成を変更します。
下記に従って作業を行なってください。

$ mkdir src dist
$ mv appsscript.json dist
$ rm コード.js 

.clasp.jsonrootDir を下記の様に修正:

{
  "scriptId": "[YOUR_SCRIPT_ID]",
  "rootDir": "./dist"
}

Webpack 設定

徐々に、本題に迫ってきました。
Webpack 関連の設定を行なうことで、ソースコードを良い感じに結合できるようにします。

webpack.config.js を下記内容で作成:

const path = require("path");
const GasPlugin = require("gas-webpack-plugin");

module.exports = {
  mode: "development",
  devtool: false,
  context: __dirname,
  entry: "./src/index.ts",
  output: {
    path: path.join(__dirname, "dist"),
    filename: "index.js",
  },
  resolve: {
    extensions: [".ts"],
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: "ts-loader",
      },
    ],
  },
  plugins: [new GasPlugin()],
};

tsconfig.json を下記内容で作成:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
  }
}

パッケージ類のインストール

TypeScript や Webpack 関連のパッケージをインストールします。

$ npm i -D typescript @types/node @types/google-apps-script # TypeScript と GAS の型
$ npm i -D webpack webpack-cli ts-loader gas-webpack-plugin # Webpack 関連

実行時のコマンド短縮のために、下記の内容を package.json に追記してください。

  "scripts": {
    "build": "webpack",
    "push": "npm run build && clasp push"
  }

コードを書く

ここまで来れば、あとは自分の作りたいコードを書くだけです。
ただ、少しだけ事前準備とルールが必要になります。

GAS はトップレベルに定義された関数に対して、イベント呼び出しの連携をしたり、 Spreadsheet の関数として呼び出したり、という連携を行なっています。
src/index.ts はそのトップレベルの関数を何にするか、という設定を行なっています。

そのため、src/index.ts から実体の関数を呼び出すような実装を行なえば良いです。
今回は、 src/main.ts の中の main() 関数を連携するようにしています。
呼ばれると、コンソールログとして hello と表示されることになります。

以下のように、ファイルを作成しましょう。

src/index.ts :

import { main } from "./main";

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

global.main = main;

src/main.ts :

export function main(): void {
  console.log("hello");
}

動作確認

実際に動作を見ていきましょう。

まずは、 src ディレクトリ以下にあるファイルを Webpack を使って一つにするところからです。

$ npm run build
npm run build                                                       

> build
> webpack

asset index.js 3.61 KiB [emitted] (name: main)
runtime modules 891 bytes 4 modules
cacheable modules 104 bytes
  ./src/index.ts 51 bytes [built] [code generated]
  ./src/main.ts 53 bytes [built] [code generated]
webpack 5.64.4 compiled successfully in 1692 ms
$

特に問題なく成功しました。
ちなみに元のソースコードに文法エラーなどあると、ここでビルド失敗できるので、早めにミスに気づくことができます。

続いて、ビルドしたソースコードを Google Workspace 上にアップロードしましょう。

$ npm run push

...

└─ dist/appsscript.json
└─ dist/index.js
Pushed 2 files.
$

こちらも、無事にアップロードできました。

では、実際にブラウザでコンソールを確認しましょう。

コンソール

このように、ビルド済みの index.gs というファイルが想定通り表示されています。
(コンソール内で、ソースコードは .gs ファイルとして扱われる。)

こちらを実行してみましょう。

ログ

コンソール上のログとして、実装した通りに hello と表示されました。

想定通り、手元で実装した複数ファイルのコードを単一のファイルに結合し、 Google Workspace 上で実行できるようになりました。

まとめ

結局これで何が楽になるかというと、「エンジニア間でソースコードの管理と引継ぎを行なう場合のコスト」が抑えられる、ということだと考えています。

特に GAS を使う場合の業務改善は、「小さく」初めて「長く」使うことが想定されます。
この場合、最初の担当者から別の担当者にプロジェクトとソースコードが引き継がれていくことも、容易に想定されます。

ソースコードが外部管理(Gitで履歴管理)できること、一般的なクラス型のオブジェクト指向のコーディングスタイルを選択することで実装者以外も読みやすくなること、といった状況を作っておくことはエンジニアとして重要ではないかな、と考えています。

つまり、エンジニアが楽になることで、その先の人たちの仕事も楽にする、という意識を持ちたいですよね、というお話でした。

GitHubで編集を提案

Discussion