1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GAS上で動かすCDN版 vue3.xでもSFC(.vueファイル)を使いたい!

Last updated at Posted at 2022-05-08

コトの発端

この記事を読んで「GASでもSFCが使えるんだー!」って知ったのですが、こちらはvue2.x系の記事です

Google Apps Scriptでちょっとしたアプリを作る場合はvue2.6系のCDN版を使ってましたが、nuxtもvuetifyもvue3.x系の正式リリースが迫ってきたので、そろそろ刷新時期かなぁと思い調べてみました

CDN版ですので例によってスクリプトエディタにコピペで動きます

まぁ、SFC使う規模のGAS webappならclasp使う方がラクだと思いますよ?
ってのは言わない約束で…

ソース

スクリプトエディタに配置するファイル的には3つです

  • code.gs
  • index.html
  • sample.vue.html

GAS側 - code.gs

code.gs
function doGet(request) {
  return HtmlService.createTemplateFromFile('index')
      .evaluate();
}

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

doGetとincludeはgoogleのページにある、いつものヤツです
ここは説明不要かと

フロント側 - index.html

index.html
<!DOCTYPE html>
<html>
  <body>
    <div id="app">
      <sample></sample>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@next"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader"></script>
    
    <script>
      // GAS上にある.vueファイルを文字列として事前に読み込んでしまう
      // nameに拡張子.vue 必須
      const vuefiles = [
        {
          name: 'sample.vue',
          tag: 'sample',
          template:<?=include('sample.vue')?>
        }
      ];
      const options = {
        moduleCache: {
          vue: Vue
        },
        async getFile(url) {
          // GASで読み込み済の.vueファイルがあったらそれを渡す
          const vue = vuefiles.find(v=>v.name===url);
          if ( vue ) return Promise.resolve(vue.template)

          // 外部のurlであればそれをfetchする
          const res = await fetch(url);
          if ( !res.ok )
            throw Object.assign(new Error(res.statusText + ' ' + url), { res });
          return await res.text();
        },
        addStyle(textContent) {
          const style = Object.assign(document.createElement('style'), { textContent });
          const ref = document.head.getElementsByTagName('style')[0] || null;
          document.head.insertBefore(style, ref);
        },
      };
      const app = Vue.createApp();

      const { loadModule } = window['vue3-sfc-loader'];
      vuefiles.forEach(v=>{
        // ルートで全てのコンポーネントをグローバル登録する
        app.component(v.tag, Vue.defineAsyncComponent( () => loadModule(v.name, options) ) );
      })

      app.mount('#app');
    </script>
  </body>
</html>

CDN版vue2.6でSFCを使うには、http-vue-loaderを使う感じでしたが、この後継がvue3-sfc-loaderです
上記のソースはここのオススメに、GAS特有の改造を施した感じです

さて、Vue.createAppされてコンポーネント登録をするのですが、入口である loadModule にはファイル名を渡す必要があります
しかし、GASにおいては './sample.vue' とかやっても、ファイルをアクセスできるわけではありません
また、vue3-sfc-loaderの内部的には .vue / .mjs / .js を判別しているっぽいです
https://github.com/FranckFreiburger/vue3-sfc-loader/blob/80f10f9fd82d7dde6c8681d23d36e9ac3ab9a654/src/tools.ts#L370
ですので、拡張子の.vueは必須です

ここは一旦 'sample.vue' として渡してしまいますが一工夫が必要となります

まず、Scriptエディタ上に配置された sample.vue.htmlを以下の部分で読み込んでいます

      // GAS上にある.vueファイルを文字列として事前に読み込んでしまう
      // nameに拡張子.vue 必須
      const vuefiles = [
        {
          name: 'sample.vue',
          tag: 'sample',
          template: <?=include('sample.vue')?>
        }
      ];

この時使うのがGASのscriptlet構文です

ここではsample.vue.htmlを文字列として取得したいだけなので、この文脈で良く使われるForce-printing scriptlets構文ではなくprinting scriptletsとして使い、配列に格納しておきます

<?!=include('sample.vue')?> /* サーバー上に読み込んだjavascriptも実行されてしまう*/
<?=include('sample.vue')?> /* サーバー上で読み込んで文字列をエスケープする*/

その後中で何やかんやした後でここに入ってきます

        async getFile(url) {
          // GASで読み込み済の.vueファイルがあったらそれを渡す
          const vue = vuefiles.find(v=>v.name===url);
          if ( vue ) return Promise.resolve(vue.template)

          // 外部のurlであればそれをfetchする
          const res = await fetch(url);
          if ( !res.ok )
            throw Object.assign(new Error(res.statusText + ' ' + url), { res });
          return await res.text();
        },

通常であればurlからfetchすればいい話ですが、GASにおいては前述の通りスクリプトエディタ上のファイルは取得できません

ですので、予め読んで登録済しておいた.vueがあればそれを返し
無ければ通常のurlとしてfetch処理をしてあげます

一応これで上手くいっているようですが、複雑な.vueを読み込む場合、include('sample.vue') の時に、読み込んだ文字列をパースする必要が出てくるかもしれません

SFC側 - sample.vue.html

sample.vue.html
<template>
  <span class="btn">{{ msg }}</span>
</template>

<script setup>
  import { ref } from 'vue'
  const msg = ref("GAS Webappでも CDN版vue3.xのSFC(.vueファイル)が動いちゃった!!");
</script>

<style scoped>
.btn {
  color: white;
  background-color: teal;
  border-radius: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 3em;
  font-size: large;
  font-weight: 600;
}  
</style>

はい
特に普通です
script setupはラクで良いですね

ちなみに、.vue内でさらに別のCDNからライブラリとかを読み込むと上手く行きません
index.htmlで読み込むようお願いします

まとめ

なんか間違いあったらご指摘オナシャス!

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?