Google Apps Script の暗黙オブジェクト

　あまり知られていない Google Apps Script の暗黙オブジェクトについて、調べたのでまとめておく。

## GAS の「サーブレット」と暗黙オブジェクト
　GAS はサーバーレスで気軽に Web アプリケーションを作れることでお馴染みだが、Html テンプレートの仕様についての情報が PHP や Java に比べて乏しい。
　`HtmlTemplate.getCode()` で [Java でいうサーブレット](https://www.javadrive.jp/servlet/)のようなものを取得できるので、Logger なりなんなりで見てみると、冒頭に以下のような宣言が行われていることに気づく。

```javascript:from-getCode()
(function() { 
  var output = HtmlService.initTemplate(); 
  . . .
```
　これこそが GAS における唯一の暗黙オブジェクト `output` の宣言に他ならない。

## 暗黙オブジェクト output の詳細
　GAS の入門書には（何冊か眺めた限りでは）暗黙オブジェクトについては記載がない（もっとも、GAS には暗黙オブジェクトは `output` しかないので、別にサーブレットのようなものを入手しても何の面白みもないし、格別にコードを書くのが快適になったりすることはない）。`output` は次のように、明示的に代入を行うのに使うことができる。

||専用スクリプトレット|別の書き方|
|--:|:--|:--|
|PHP|`<?=$hoge?>`|`<?php echo $hoge; ?>`|
|JAVA|`<%= hoge %>`|`<% out.println(hoge) %>`|
|GAS|`<?=$hoge?>`|`<? output._ = hoge ?>`|

　よく見ると、[公式サイトにちょびっと解説があった](https://developers.google.com/apps-script/guides/html/templates#walking_through_evaluated_code)。ざっくりと概要を追っていくと、

> `output` は `HtmlService.initTemplate()` というメソッドにより生成される暗黙オブジェクトである。基本的に template が利用するものなのでドキュメントには記載していない。

　この `initTemplate()` というメソッドの詳細自体、公式ドキュメントに記載されていないし、GAS のスクリプトエディタでも補完対象になっていないが、明示的に打ち込んでもきちんと動く（サンプルコードは後述する）。

>これは 特殊な `HtmlOutput` 型オブジェクトで、`append()` と `appendUntrusted()` に対応する略記法としてプロパティ `_` と `_$` を持っている。`output` はさらに `$out` というプロパティを持っていて、これによりふつうの `HtmlOutput` 型オブジェクトに変換される。

　略記法とは言うが、`output.append(sth)` に対して略記法は `output._ = sth` だから、構文の違いには注意する必要がある。
　また、この特殊な `HtmlOutput` 型オブジェクトは、単に略記法が増えただけの `HtmlOutput` 型オブジェクトなのではない。そのまま doGet() から return してもエラーが出てブラウザで表示されない。もしこの特殊なオブジェクトをプログラマが明示的に扱いたい場合は、最終的に `$out` を呼んで普通の `HtmlOutput` 型に変換する必要がある。V8 ランタイムではさらに `HtmlOutput` 型との差異が大きくなり、もはや全く別物と考えてよい。型の性質については、この記事の下部で見る。

> `evaluate` の過程で、スクリプトレット外部の HTML は `output._ = sth` という形に変換され、スクリプトレット内部はそのまま Javascript コードとして扱われる。行番号は保存されるので、エラーが出たときに原因箇所を特定するのは楽。

　これはまあ、JSP がサーブレットに変換される過程と同じか。

### 基本的な記法
　たとえば1番から100 番まで列挙するアホスクリプトを書くことを考える。以下のように代入スクリプトレットを駆使して書く手法がよく挙げられる。

```html:outputを使わない書き方
<div>
  <? for (var i=1;i<100;i++) { ?>
    <?= i.toString() + "番" ?>
  <? } ?>
</div>
```

　これは下のように暗黙オブジェクトを用いて書いてもよい。個人的にはちびちびと `<? ?>` と `<?= ?>` を切り替えて打つのは面倒くさくて嫌なので、暗黙オブジェクトの存在を知れたのはありがたい。

```html:outputを使った書き方
<div>
  <? 
  for (var i=1;i<100;i++) {
    output._ = i.toString() + "番";
  } 
  ?>
</div>
```

　スクリプトレットを使わず、GAS ですべて完結させるなら以下のような書き方になる。明示的に作成する場合は変数名を自由に付けられる。最終的に `.$out` を呼んで[^1]普通の `HtmlOutput` 型に戻さないとエラーが出るので注意。

[^1]: 絶対に `.$out` じゃないとダメかというとそうではなくて、後述するように Rhino ランタイムなら `.asTemplate().evaluate()` とかでも普通の `HtmlOutput` 型に変換すること自体は可能です。

```javascript:code.gs
function doGet(){
  var op = HtmlService.initTemplate(); // 特殊 HtmlOutput 型をつくる
  op._ = "<html><body><div>";
  for (var i=1;i<100;i++) {
    op._ = i.toString() + "番";
  } 
  op._ = "</div></body></html>";
  return op.$out;  // ここで $out を呼んでふつうの HtmlOutput 型に戻す
}
```

参考：[Google Apps Scriptプログラミング [中級編] - Webアプリケーション開発とHtml Service (6/7)](https://www.tuyano.com/index3?id=926001&page=6)

### 型の相互関係とランタイムバージョン
　HtmlService には多くのオブジェクト型が存在するが、この「特殊な `HtmlOutput` 型」（以降とりあえず `HtmlOutputBeta` とする）と関連する型はほかの型とどういう関係になっているのだろうか。公式ドキュメントには "output is a special HtmlOutput object" とあるから、基本的に `HtmlOutput` 型の性質を継承しているものと思われるが、実はランタイムバージョンによって性質は大きく異なる。
　まず `RuntimeVersion` を Rhino にした状態では、以下のようになっており、基本的に `HtmlOutputBeta` 型は `HtmlOutput` とほとんど同じ機能を有していることがわかる。

![object_relationship.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/5978f3f9-a1c7-1b38-c6e5-f6ab0242d5bb.png)

　注意点としては、`HtmlOutputBeta `型のままでは return しても表示できないこと、`HtmlOutputBeta `型に `.clear()` メソッドをあてると普通の `HtmlOutput` 型になること、`HtmlOutputBeta` 型は新規作成するほかなく、別のデータから変換する手段はないということだろうか。
　ところが、この `output` オブジェクトは、先日 GAS に導入された V8 ランタイムでは性質が変更されているようで、[teratail にも質問が投稿されている](https://teratail.com/questions/241930)。たとえば、略記法の `output._ = sth;` は問題なく機能するが、略記元であるはずの `output.append(sth)` を使おうとすると、`output.append is not a function.` というエラーが表示されてしまう。それどころか、`Rhino` 版では使えた `.setContent()`、`.clear()` などといった `HtmlOutput` 型に存在するメソッドが軒並み使えなくなっているようだ。

![object_relationship_V8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526487/7d67f6f4-ef52-1d48-c6ce-cce7f9abecc2.png)

　V8 ランタイムが導入されて間もないが、この点は[公式の V8 移行マニュアル](https://developers.google.com/apps-script/guides/v8-runtime/migration)にも記載されていないので、もし暗黙オブジェクトを使いこなして技巧的な Html を作成している方がいるならば、以降に際してあらためてコードをチェックする必要がある。

## まとめ
- GAS には暗黙オブジェクト `output` が存在するが、`output._ = sth` という `.append(sth)` の略記法以外はたいして活用法がない。
- V8 ランタイムでは `output` の機能が大幅に制限されているので、これまで暗黙オブジェクト `output` を利用して技巧的なコードを書いていた人は注意すべし。