Google Apps Script でAngularJSを使った Single Page Application を構築・公開する方法(基礎編)

#前置き

ペラのHTMLだけのサイトなら、ブログサービスだったりwikiだったりそれこそ我らがQiitaを活用すれば、速攻で公開出来ますね。
Analyticsとの連携だったり、独自の解析ツールが入っていたりと非常に使い勝手も良いです。

ですが動的な処理を組み込んだWebサービスを作ろうと思ったら、サーバ用意して、LAMP環境を整えて、ドメインも用意して…うおぉぉぉめんどくさい！！！
かといってPaaS使うとお金かかるし…

お金かけたくないけどお手軽にサービス作りたいお(￣￢￣*)
そんな怠惰かつケチくさい願いを叶えてくれるGoogle Apps Scriptさん(以下GAS)のお話をしたいと思います。

最終的にはAngularJSを導入したSingle Page Application(以下SPA)を作り、Analyticsの設定を行い、Adsenseの広告を貼っつけて、データの取得とログの記録はスプレッドシートで行う、というそれらしいところまで出来たので、その構築手法をご紹介したいと思います。
とはいえ休日にゲームの片手間でやったレベルなので、システムとしての完成度はそんなに高くない(爆
あとは応用編に書きますが、Search Consoleは導入出来ませんでした。
そのため**googlebotにクローリング要求を行う事が出来ません。**(結構痛い…)
また、GASで公開したサイトに対して負荷テストをしたわけではなく、**一般の商用向けとしての活用には耐えられない可能性が非常に高い**という認識なので、そこらへんはご注意ください。


この基礎編では、一般公開から、Webアプリ構築時にGASでつまづきそうなあたりをやります。

#一般公開のやり方

結構簡単です。

```Code.gs
// Getリクエストの受け取り口、起動時はここが呼ばれる模様
function doGet(request) {
    return HtmlService.createTemplateFromFile("index").evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
```

```index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    testですよ
  </body>
</html>
```

index.htmlは、GASエディタのメニューの「File」 > 「New」 > 「Html file」から作成したものです。
骨組みが出来あがっているHTMLファイルが出来るので、上記の例だとbodyの中身以外はデフォルトのものです。

コードが用意出来たら、「Publish」 > 「Deploy as web app」をクリックです。
![スクリーンショット 2016-12-13 15.20.56.png](https://qiita-image-store.s3.amazonaws.com/0/106608/bb6e95f5-3838-e63d-4fc3-3ab66ef7f14a.png)

**Project version**
公開するときに都度作成が必要なので、「New」を選択します。

**Execute the app as**
GASの実行ユーザです。
1.Me(誰がアクセスしても実行ユーザは作成者)
2.User accessing the web app(アクセスユーザが実行ユーザ)
今回は一般公開したいので、「Me」を選択します。
(2の場合、ログインしているユーザでないとアクセス出来ません)

**Who has access to the app**
アクセス許可する対象です。
1.Only myself(自分だけ)
2.Anyone(誰でもOK、ただしログイン者に限る)
3.Anyone,even anonymous(本当に誰でもOK、匿名=非ログイン者でも可)
※3は「Execute the app as」で「Me」を選択している場合だけ表示
一般公開なので「Anyone,even anonymous」を選択します。


そして「Deploy」をクリックします。
※更新する際も手順は同じになりますが、「Deploy」が「Update」に変わります

![スクリーンショット 2016-12-13 15.21.33.png](https://qiita-image-store.s3.amazonaws.com/0/106608/1928e687-3497-fdb4-e626-b2d2304d6439.png)

こんな感じのダイアログが出るので、Current web app URLと表示されている「https://〜」をコピペしてアクセスしてみると、ばっちり先ほどのindex.htmlの内容が表示されているはずです。

なお、先ほどのダイアログ中にあった「latest code」リンクをクリックすると、変更を新バージョンとしてアップデートせずともサイトの確認を行えます。
「latest code」でサイトを確認して公開したつもりになり、実際に公開されているのは前のバージョンのまま、という事がよくあるので注意です。

#簡単なルーティング方法
公開されたサイトのURLを見てみると、下記のような構成になっています。
https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxx/exec
※「xxxxxxxxx・・・」はサイト毎にユニークなIDが割り当てられています
※「latest code」で確認した際には末尾のexecがdevになっておりURLが異なるので注意

このURLは変更が出来ませんが、クエリストリングに関しては設定が出来ます。

doGetの引数をログに出してみると、下記のようになっています。
{parameter={}, contextPath=, contentLength=-1, queryString=null, parameters={}}

試しに下記のURLでアクセスしてみると・・・
https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxx/exec/path?hoge=1

こうなります。
{parameter={hoge=1}, contextPath=, contentLength=-1, queryString=hoge=1, parameters={hoge=[Ljava.lang.Object;xxxxxxx}, pathInfo=path}

つまりdoGet内で
パス情報は request["pathInfo"]
クエリストリングは request["parameter"]["hoge"]
みたいな感じで取得出来るのです。

これを利用すれば、例えばクエリストリングから簡単にルーティングを行う事も出来ます。

```Code.gs
// Getリクエストの受け取り口、起動時はここが呼ばれる模様
function doGet(request) {
  // pageに指定無しならindex.htmlを表示、
  if (typeof request["parameter"]["page"] === "undefined") {
    return HtmlService.createTemplateFromFile("index").evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
  }else{
    return HtmlService.createTemplateFromFile("index"+request["parameter"]["page"]).evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
  }
}
```
https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxx/exec?page=2
へアクセスすると、index2.htmlの内容が表示されます。

#JavaScript・CSSの読み込み方
HTTPSで公開されているライブラリに関しては問題無く読み込めます。
が、HTTPでしか公開されていなかったり、サーバ配置型になると少しだけハードルが上がります。
そもそもGASエディタには作れるファイルの種類がScriptとHTMLの2種類しかなく、Scriptの方はサーバーサイドスクリプトなため、単純にフロント側で使用するJavaScriptとCSSの配置には一工夫必要なのです。

参考：
[HTML Service: Best Practices](https://developers.google.com/apps-script/guides/html/best-practices#separate_html_css_and_javascript)

やや力業な感はありますが、HTMLファイルとしてJavaScriptもCSSも読み込む感じです。

```Code.gs
// Getリクエストの受け取り口、起動時はここが呼ばれる模様
function doGet(request) {
    return HtmlService.createTemplateFromFile('index').evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
  .getContent();
}
```

```index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    testですよ
    <?!=include("index.js")?>
  </body>
</html>
```

```index.js.html
<script>
console.log("OK");
</script>
```

このやり方を利用し、
https://code.angularjs.org/1.5.0/angular.min.js
のソースをコピペしたファイルを読み込んだところ、正常に動作しました。
おそらく他のライブラリも、同様の方法で動作するはずです。
※上記angular.min.jsはそのままHTTPSで読み込んでも動作します

#画像の読み込み
GASエディタ上で画像を配置する方法がありません。(たぶん)
そのため、
1.別のファイルサーバからリンクで読み込む
2.Base64化したデータを使用する
の2パターンが考えられます。

2の方は、先ほどJavaScriptやCSSを読み込むために使った方法を使います。

```Code.gs
// Getリクエストの受け取り口、起動時はここが呼ばれる模様
function doGet(request) {
    return HtmlService.createTemplateFromFile('index').evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
  .getContent();
}
```

```index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    testですよ
    <img src="<?!= include('test.img'); ?>">
  </body>
</html>
```

```test.img.html
data:image/jpeg;base64,XXXXX…(ものすごく長いので以下略)
```

みたいな感じです。
上の例だとtest.img.htmlにはbase64化した内容だけ記述していますが、imgタグごと書いてあげると読み込み側の記述もスッキリするかもしれません。
Base64化すると元の画像よりサイズが増え、GASのエディタも大量の文字列のせいか動作が重くなってしまいますが…
ひとまずファイルサーバが無いときにはこれでどうにかなるため、覚えておいて損はないかと思います。

#まとめ
基礎編はここまでです。
静的なページだけのサイトなら、ここまでの内容で構築出来ると思います。
次回応用編では、AngularJS導入やサーバーサイドスクリプトとの連携、AnalyticsやAdsenseの設定に関する内容を紹介します。
ではm(_ _)m