「CORSか SOP違反か」何とかしたいですよね。
※本記事は「xxx.gs」ではなく「xxx.html」に書く方のJavaScriptの話です。
#外部スクリプトが使えない
GASで作ったWebアプリで下記のようなコードを書いてもエラーになります。
<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ファイルを返すようにカスタマイズします。
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();
}
<!-- 前略 -->
<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>
<!-- 後略 -->
self.addEventListener("message", event => {
self.postMessage(event.data);
});
しかし、これでも上手くいきません。
何故ならHTMLがインラインフレーム(iframe)で入れ子にされるからです。
こちらで作ったHTMLは一番内側のiframe内に表示され、これが外側(アプリにアクセスするときに使うURL)と異なるドメインになっているため、結局また同一生成元ポリシーに違反します。
#成功例 文字列からBlobを起こしてObjectURLを生成する
所謂インラインワーカーを使います。
ウェブ ワーカーの基本
https://www.html5rocks.com/ja/tutorials/workers/basics/#toc-inlineworkers
GAS側にWorkerのソースコード文字列を返すメソッド(getCode)を定義します。
このメソッドをindex.html側でgoogle.script.runを使って呼びます。
function doGet(e) {
return HtmlService.createTemplateFromFile("index").evaluate();
}
function getCode() {
return HtmlService.createHtmlOutputFromFile("worker.js").getContent();
}
<!-- 前略 -->
<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>
<!-- 後略 -->
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で処理するようにしたいところですが、本題から外れるのでこの記事では触れません。
気が向いたら書きます。 書きました。