0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GAS HtmlServices + Vue3 + Vite + Clasp でのデプロイ後エラー対策

Posted at

こんにちは、luthです

ノンプログラマーですが、社内ツール開発でワンオフ物としてWebAppをGASで公開していたりします
今回は、Vue@3.4.0にバージョンアップした際に数時間引っかかったエラーの対応についてです

*なおReactやバニラなHTMLでも発生しうる問題です

経緯はこちら
https://zenn.dev/luth/scraps/90e0c8d8db82a2

後述のnpmパッケージはこちら
https://www.npmjs.com/package/vite-plugin-vue-google-apps-script

環境情報

  • GoogleAppsScript [HtmlServices]
  • Node.js
  • @google/clasp
  • vue@^3.4.0
    ※@3.3.x以下では発生なし
  • vite
  • vite-plugin-singlefile

*上記構成については下記記事を参照させていただきました、ありがとうございました…!
https://engineer.retty.me/entry/2022/12/22/150035

課題点

運用中のVueアプリのメンテナンスで、Vue@3.4.0へのバージョンアップを検証していました
ローカル開発ではブラウザで問題なく動いたため、claspでGASにアップロードし、デプロイテストをしたところ、何もマウントされない状態になってしまいました

コンソールを見ると、下記エラーで止まってマウントまで至らなかったようです

開発者ツール内コンソール
Uncaught SyntaxError: Invalid destructing assignment target

このエラー自体はデフォルト引数に関するエラーです
ただ、ローカルでのデフォルト引数の設定自体は問題ありませんでしたし、
何よりビルド後のローカルのhtmlファイルをブラウザで開いても、このエラーは出ずに正常処理されます

宇宙猫🐈になりかけながらも、デプロイ後のソースを追ったところ、ローカルとの差分は以下の通りでした

ローカルファイル内 vue@3.4.0以降
i = `https://vuejs.com/errors/#runtime-${n}`
デプロイ後のブラウザで開いた際
i = `https://            /* 謎の改行 */
${T}`, message: "alert", // 本来はかなり先のコード

GASはWebアプリをブラウザで描画する際、生のHTML+JSを利用するわけではなく、2重のiframeを使って埋め込むことで、アプリ利用者向けのセキュリティ担保を目指しています
おそらくは、そのデプロイ後のソースを読み込んでiframe内で展開する時に、ソースがめちゃ削られてる感じです

Vue@3.3.xの時のビルドソースでは以下のようになっていましたので、GASがソース内のURLを、セキュリティ上の理由だかで削る正規表現がめちゃ広めなものなんでしょう…

vue@3.3.x でのbuild版
i = n;

対策plugin

URLが置換されるのが問題なら事前にURLを潰そう、ということで、
Viteプラグイン用に下記のように記述してみました

plugins/vite-plugin-vueOnGoogleAppsScript.ts
import type { Plugin } from 'vite';
export interface ReplaceRule {
  from: string | RegExp;
  to: string;
}
export const PRESET_REPLACE_MASTER: Array<ReplaceRule> = [
  {
    from: /\=`https:\/\/vuejs\.org\/errors\/#runtime-\$\{(.+?)\}`/g,
    to: '=$1',
  },
  {
    from: /\=`https:\/\/vuejs\.org\/errors{0,1}-reference\/#runtime-\$\{(.+?)\}`/g,
    to: '=$1',
  },
  // for scriptlet of apps script
  {
    from: /"(\<\?\!{0,1}\={0,1}.+?\?\>)"/g,
    to: "'$1'",
  },
];

export const vueOnGas = (
  replaceMaster: Array<ReplaceRule> = PRESET_REPLACE_MASTER,
): Plugin => ({
  name: 'vue-on-gas',
  generateBundle(_outputOptions, outputBundle) {
    const chunkNames = Object.keys(outputBundle);
    chunkNames.forEach((chunkName) => {
      const chunk = outputBundle[chunkName];
      replaceMaster.forEach(({ from, to }) => {
        const isMatch =
          typeof from === 'string'
            ? chunk.code.indexOf(from) !== -1
            : from.test(chunk.code);
        if (isMatch) {
          console.info(
            `[Vue on GoogleAppsScript plugin] match with pattern: ${from.toString()}`,
          );
          chunk.code = chunk.code.replace(from, to);
        }
      });
      outputBundle[chunkName] = chunk;
    });
  },
});

viteの設定に組み込むのはこんな感じ
置換設定を変えたい場合は引数にマスタを入れてあげれば置換処理に使えます

vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { viteSingleFile } from 'vite-plugin-singlefile';
import { vueOnGas } from './plugins/vite-plugin-vueOnGoogleAppsScript';

export default defineConfig({
  plugins: [
    vue(),
    viteSingleFile(),
    vueOnGas(),
  ],
  build: {
    outDir: 'dist',
  },
});

というnpmパッケージ

そんな内容のnpmパッケージを公開しましたので、
よろしければご利用くださいませ。

https://github.com/luthpg/vite-plugin-google-apps-script
https://www.npmjs.com/package/vite-plugin-google-apps-script


単純な置換処理ですが、プラグインにできたことでビルド設定さえすれば以後は気にせずに開発~デプロイを実行できるようになりました
GAS+Vue+Viteの組み合わせはニッチな気がしますが、同構成をご利用の方、よろしければご利用ください…!

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?