cover

GSuiteをプロのように使いこなす

sorcererxw

最近、Google オフィススイートをベースにした開発ニーズがあり、ビジネスバックエンドを通じて Google Suite のドキュメントを読み書きすることが目的です。通常は Google Suite SDK を直接使用するのが良いですが、多くの機能制限があり、Google Form を作成することもできません。Google はその多くの機能を Google App Script(Google はずるいですね)にパッケージ化しているため、開発に GAS を導入する必要があります。

GAS(Google App Script)の紹介

GAS(Google App Script)を使用する理由

しかし、Googleが開発者向けに複数のSDKを提供しているにも関わらず、なぜGoogle App Scriptを使用するのでしょうか:

  • ローカルでJavascriptまたはTypescriptを使用して開発することができます。

    Google App Scriptは本質的にはJavascriptであり、文法に違いはありません。ただし、gsはGASの環境下で実行され、特定のインターフェースを呼び出すことができます。また、Typescriptを使用してローカルで記述し、アップロード時に自動的にAppsScriptに解釈されます。

  • インターフェースがより豊富です

    GSuiteの直系の子として、SDK内にないいくつかのインターフェースを呼び出すことができます。たとえば、Google Formを作成することができます。

    ただし、環境の制限により、他のプラットフォームのSDKほどインターフェースの使用柔軟性に欠ける

  • Webサービスとしてデプロイ可能です

    GASをWebサービスとしてデプロイし、他のプログラムから呼び出すことができます。

  • 省略された呼び出し側の面倒な認証操作

    自分のビジネスサービスがGASのWebインターフェースを直接呼び出す場合(デプロイ時に匿名アクセス可能に設定されていることが前提)、追加の認証操作を行う必要はありません。

    もしセキュリティが心配ならば、クエリに認証パラメータを含めて、GAS のビジネスロジック内で改めて認証を行うことができます。

GASの制限

  • 非エンジニアリング化可能

    モジュールの概念がなく、単一のファイル内の関数は相互に呼び出すことができますが、ファイル間での呼び出しはできません。

    TypeScriptのファイルを一つのJavaScriptファイルにコンパイルすることは可能ですが、この方法ではdoGetやdoPostなどのフレームワーク固有のメソッドをトップレベルで露出させることができません。そのため、コンパイルされたコードがGASフレームワークに認識されないままです。

  • Webフレームワークが利用できません

    GASフレームワークが呼び出すために、doGet と doPost の二つのインターフェースのみを定義することができます。リモート呼び出し側は、これら二つのインターフェースを操作することしかできません。

    • GET https://script.google.com/macros/s/{scriptID}/exec
    • POST https://script.google.com/macros/s/{scriptID}/exec

    具体のロジックは呼び出されたクエリに依存して判断されます。また、すべての返却データは手動でMimeTypeを指定する必要があります。そうしないと、HTML形式で返されることになります。

  • ローカルでデバッグできない

    Google App Scriptではモジュールの概念がなく、外部ライブラリを呼び出すことができません。そのため、Google APIを呼び出すにはGASの実行環境にデプロイする必要があります。ローカル開発については、Googleは自動補完を容易にするために <code>@types/google-apps-script</code> を提供しています。

    また、デプロイに関しても、ローカルでプッシュした後、ウェブページを手動で開いてデプロイする必要があり、開発効率を大幅に低下させています。

  • 同期ブロッキング

    すべての操作、文書の操作であれ、ネットワークリクエストであれ、ブロッキングされており、結果が返ってくるまで次のステップを実行できません。この特徴は欠点でありながらも利点でもあります。

スキルアップ

クロスファイル呼び出しを解決する

複数のコードファイルを分けて管理することは、エンジニアリングの基礎です。そうでなければ、メンテナンスコストが非常に高くなります。したがって、AppsScriptを使用して正式なプロジェクトを開発したい場合は、ファイル間の呼び出し問題を解決する必要があります。

Typescriptでは、ES6のimport moduleを使用してファイル間で呼び出しを行います。各ファイル内で使用するimportをすべて上部に宣言し、ファイル間に循環参照がないことを保証すれば、モジュール呼び出しのトポロジー図を計算出すことができます。importを置き換える形でモジュールコードをコピーすることにより、このトポロジー図をファイル内に圧縮することが可能です。

// ------------
// 原始文件
// ------------

// index.ts
import * as A from './A'

function sayHello(){
	A.helloWorld()
}

// A.ts
import {foo} from './B'

export function helloWorld(){
	foo()
	console.log('Hello World!')
}

// B.ts

export function foo(){
}
// ------------
// 处理之后
// ------------

const B = (function () {
	function foo(){}
	B.foo = foo
})()

const foo = B.foo

const A = (function () {
  function helloWorld() {
		foo()
    console.log('Hello World!')
  }
  A.helloWorld = helloWorld
})();

function sayHello() {
  A.helloWorld()
}

これは単純な目標効果のデモンストレーションです。この方法を使って、複数のファイルのコードを統合することができます。しかし、実際の状況では、考慮すべきいくつかの点があります。主要な点をいくつか挙げてみましょう:

  • モジュールの循環参照を防ぐためには、解析する前に事前チェックが必要です。madgeを参考にしてください。
  • 使用を防ぐ必要がある <code>require</code>

Google AppsScript には、認証やコードのアップロード・デプロイを迅速に行うためのツール clasp が用意されています。このツールを使ってコードをアップロードすると、JavaScript や TypeScript が自動的に gs ファイルに変換されます。TypeScript ファイルについては、変換後のファイルの先頭に Compiled using ts2gas と表示され、clasp が ts2gas を使用して TypeScript をコンパイルしていることがわかります。

ご覧ください、ts2gas の実装を。ヘッダーコメントには以下のように記載されています:

/**
 * Transpiles a TypeScript file into a valid Apps Script file.
 * @param {string} source The TypeScript source code as a string.
 * @param {ts.TranspileOptions} transpileOptions custom transpile options.
 * @see https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
 */

ts2gas は実はそれほど不思議なものではなく、Compiler-API を提供する typescript を使用して ts のコンパイルを実現しています。コンパイルされた結果に少しカスタマイズされたコードを加えることで、gs ファイルを生成できます(先に述べたように、gs は実際には js です)。

では、ts2gas を基にして、コンパイルされた gs が私たちの期待する効果を達成できるのでしょうか?まずは、ts2gas が具体的にどのように実現されているのかを見てみましょう。

const ts2gas = (source: string, transpileOptions: ts.TranspileOptions = {}) => {
	transpileOptions = deepAssign({},  // safe to mutate
    defaults,  // default (overridable)
    transpileOptions,  // user override
    statics,  // statics
  );
	
	// Transpile (cf. https://www.typescriptlang.org/docs/handbook/compiler-options.html)
  const result = ts.transpileModule(source, transpileOptions);

  // # Clean up output (multiline string)
  let output = result.outputText;
  
	const pjson = require('../package.json');  // ugly hack

  // Include an exports object in all files.
  output = `// Compiled using ${pjson.name} ${pjson.version} (TypeScript ${ts.version})
var exports = exports || {};
var module = module || { exports: exports };
${output}`;

	return output;
}

上記はts2gasのコアコードであり、tsのtranspileModuleを使用して元のコードをコンパイルします。この関数は呼び出し元が自分のtranspileOptionsを渡してdefaultOptionを上書きすることを許可していますが、staticsOptionを使用して特定の機能の正確性を強制的に保証しています。staticsOptionを調整することで、例えばrequireの使用を防ぐ要求に対する制限を行い、コンパイル時にエラーを投げることができます。