GoogleAppScriptでVue.jsの単一ファイルコンポーネントをつかう

GoogleAppScriptでVue.jsが使えると知ったので使ってみた。
しかし、そのままではVue.jsの単一ファイルコンポーネント(single-file component)は使えず、一つのhtml内にすべて記述する必要がある。
Vue.jsを使っておきながら単一ファイルコンポーネントが使えないとなると少しもったいない。そこで、なんとかgasで単一ファイルコンポーネントが使えるよう模索したのでここに示す。

※ ある程度のgasとvueの知識が必要

## プロジェクト構成

gasプロジェクトは以下の構成とする
```
.
├── main.gs
├── index.html
└── app.vue
```

## htmlを取得できるようにする

ページにアクセスできるようにdoGetメソッドを作る。
今回はデモなので細かい設定は省略しindex.htmlをそのまま返すようにする。
``` js:main.gs
function doGet() {
  return htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
}
```

AppScriptエディタ右上の"デプロイ"から、ウェブアプリとしてデプロイしておく。
ここまでは普通の gas + html

# GoogleAppScriptにVue.jsを導入する

index.htmlにvue導入処理を記載する。
cdn版Vue.jsは一行で導入できる！すごい！
``` html:index.html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <h1>{{message}}</h1>
    </div>
  </body>
  <script>
    new Vue({
      el: '#app',
      data () { return { message: 'Hello GAS + Vue.js' } }
    });
  </script>
</html>
```
この状態でデプロイ先リンクを開くと"Hello GAS + Vue.js"と出力される。

ここまでは普通の gas + vue


# 単一ファイルコンポーネントを読み込む
さて本題の単一ファイルコンポーネントの読み込みだ。

事前にapp.vue.htmlを単一ファイルコンポーネントの形式にしておく。
ここでは引数messageを受け取って表示するだけのコンポーネントとする。
``` html:app.vue.html
<template>
  <h1>
    {{message}}
  </h1>
</template>
<script>
module.exports = {
  props: ['message']
}
</script>
```

index.htmlから単一ファイルコンポーネントを読み込むためにhttp-vue-loaderを導入する。
http-vue-loaderは単一ファイルコンポーネントのURIを受け取ってコンポーネントを生成するため、これを使って別ファイルに書き出したコンポーネントを読み込む。

``` html:index.html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
    <script src="https://unpkg.com/http-vue-loader"></script> <!-- 追加 -->
  </head>
  <body>
    <div id="app">
      <App :message = "message"></App>
    </div>
  </body>
  <script>
    // 追加
    const App = httpVueLoader(
      'data:text/plain,' + 
      '<?!= encodeURIComponent(HtmlService.createTemplateFromFile('app.vue').evaluate().getContent()).replace(/[\'']/g,"\\'") ?>'
      );
    new Vue({
      el: '#app',
      data () { return { message: 'Hello GAS + Vue.js + single-file component' } },
      components:{ // 追加
        App
      }
    });
  </script>
</html>
```

ミソは以下の部分
```js
const App = httpVueLoader(
  'data:text/plain' + 
  '<?!= encodeURIComponent(HtmlService.createTemplateFromFile('app.vue').evaluate().getContent()).replace(/[\'']/g,"\\'") ?>'
      );
```

app.vue.htmlの中身を文字列として取得
```js
HtmlService.createTemplateFromFile('app.vue').evaluate().getContent()
```

取得した文字列をURIにエンコード
```js
encodeURIComponent(...)
```

エンコードした文字列の中のシングルクオートをエスケープ
```js
....replace(/[\'']/g,"\\'")
```

```<?!= ~ ?>```の中の文字列はgasのスクリプトとして処理されたのち埋め込まれるため、
```js
const App = httpVueLoader('data:text/plain,{app.vue.htmlの中身}')
```
のようになり、URIをhttpVueLoaderで読み込んでコンポーネントオブジェクトが生成できる。

読み込んだコンポーネントはvueのコンポーネントとして普通に使うことができる。

この状態でデプロイページにアクセスすると、"Hello GAS + Vue.js + single-file component"と表示されるはずだ。
これで、単一ファイルコンポーネントをgasで扱うことができるようになった。


# Gasに変換処理を任せる
```js
const SomeComponent = httpVueLoader(
  'data:text/plain' + 
  '<?!= encodeURIComponent(HtmlService.createTemplateFromFile('some_component.vue').evaluate().getContent()).replace(/[\'']/g,"\\'") ?>'
      );
```
単一ファイルコンポーネントが利用できるようになったのはいいが、コンポーネントを読み込むたびにこんな長文を書いてられないので、Gas側でできるところまでメソッド化する。


```js:main.gs
function doGet() {
  return htmlOutput = HtmlService.createTemplateFromFile("index").evaluate();
}

// 追加
/** @type { (name:string) => string } */
function getVueURI(name) {
  return "'data:text/plain," + encodeURIComponent(HtmlService.createTemplateFromFile(name).evaluate().getContent()).replace(/[\'']/g,"\\'") + "'"
}
```
ファイル名からURIを構成して返却するメソッドgetVueURIを作る。やっていることは先ほどと同じ。
それに合わせてindex.htmlを書き換えると以下のようになる。
httpVueLoaderだけはクライアント側で実行しないといけないため、このような構成となった。

``` html:index.html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
    <script src="https://unpkg.com/http-vue-loader"></script>
  </head>
  <body>
    <div id="app">
      <App :message = "message"></App>
    </div>
  </body>
  <script>
    // 変更
    const App = httpVueLoader(<?!=getVueURI('app.vue')?>)
    new Vue({
      el: '#app',
      data () { return { message: 'Hello GAS + Vue.js' } },
      components:{
        App
      }
    });
  </script>
</html>
```

# おわりに
これでGoogleAppScriptでvueの単一ファイルコンポーネントが使えるようになった。
あとはvuexでもvuetifyでも使って好きに開発できる！

追記：著者はgas/vueともにあまり詳しくはないので、ミスやほかの方法があればコメントにて教えてください。