1
2

More than 3 years have passed since last update.

Google Apps Script で公開したWebアプリケーション内で Web Woker や ES Modules を使う

Last updated at Posted at 2020-05-25

「CORSか SOP違反か」何とかしたいですよね。
※本記事は「xxx.gs」ではなく「xxx.html」に書く方のJavaScriptの話です。

外部スクリプトが使えない

GASで作ったWebアプリで下記のようなコードを書いてもエラーになります。

xxx.html
<script type="module">
import { module } from "https://xxx/module.js";
cosnt worker = new Worker("https://xxx/worker.js");
</script>

開発コンソールで確認してみると「CORS」だの「SOP」だのといった文言のエラーメッセージが出ていると思います。
CORSはオリジン間リソース共有、SOPは同一生成元ポリシーのことで、ざっくり言うと「他のWebサイトから持ってきてはいけない」というエラーです。

つまり、他所から持って来ていないように見せれば解決します。

失敗例 ContentServiceを使ってみる

GASにはHTMLを表示するHtmlServiceに対してプレーンテキストなどを表示するContentServiceというクラスがあります。

Class ContentService
https://developers.google.com/apps-script/reference/content/content-service

これを使ってdoGetメソッドをURLパラメーターを渡したときだけWorker用のJSファイルを返すようにカスタマイズします。

main.gs
function doGet(e) {
  const js = e.parameter.js;
  if (js != null) {
    const content = HtmlService.createHtmlOutputFromFile(js).getContent();
    return ContentService.createTextOutput(content).setMimeType(ContentService.MimeType.JAVASCRIPT);
  }
  return HtmlService.createTemplateFromFile("index").evaluate();
}
index.html
<!-- 前略 -->
<script>
const url = "<?=ScriptApp.getService().getUrl();?>?js=worker.js";
const worker = new Worker(url);
worker.onmessage = console.log;
worker.onerror = console.error;
worker.postMessage("Test Message.");
</script>
<!-- 後略 -->
worker.js.html
self.addEventListener("message", event => {
  self.postMessage(event.data);
});

しかし、これでも上手くいきません。
何故ならHTMLがインラインフレーム(iframe)で入れ子にされるからです。

Screenshot 2020-05-25 at 18.10.10.png

こちらで作ったHTMLは一番内側のiframe内に表示され、これが外側(アプリにアクセスするときに使うURL)と異なるドメインになっているため、結局また同一生成元ポリシーに違反します。

成功例 文字列からBlobを起こしてObjectURLを生成する

所謂インラインワーカーを使います。

ウェブ ワーカーの基本
https://www.html5rocks.com/ja/tutorials/workers/basics/#toc-inlineworkers

GAS側にWorkerのソースコード文字列を返すメソッド(getCode)を定義します。
このメソッドをindex.html側でgoogle.script.runを使って呼びます。

main.gs
function doGet(e) {
  return HtmlService.createTemplateFromFile("index").evaluate();
}

function getCode() {
  return HtmlService.createHtmlOutputFromFile("worker.js").getContent();
}
index.html
<!-- 前略 -->
<script>
google.script.run
.withSuccessHandler( code => {
  const url = URL.createObjectURL( new Blob([code], {type:"text/javascript"}) );
  const worker = new Worker(url);
  worker.onmessage = console.log;
  worker.onerror = console.error;
  worker.postMessage("Test Message.");
})
.withFailureHandler(console.error)
.getCode();
</script>
<!-- 後略 -->
worker.js.html
self.addEventListener("message", event => {
  self.postMessage(event.data);
});

以上。
コードはWeb Workerで説明しましたが、ES Modulesの場合はObjectURLを生成するところまでは一緒で、Dynamic Importを使えば同様に実現できます。

import - MDN - Mozilla
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Import

余談

本当はgoogle.script.runはPromiseで処理するようにしたいところですが、本題から外れるのでこの記事では触れません。
気が向いたら書きます。 書きました。

1
2
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
2