
最近、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の使用を防ぐ要求に対する制限を行い、コンパイル時にエラーを投げることができます。