include() で HTML・CSS・JS を3ファイルに分ける
GAS の Web アプリは、1つの HTML に全部書いていくと、すぐに何百行にもなります。 CSS と JS を別のファイルに出す定番の形と、分けた直後に引っかかりやすいところをまとめます。
Apps Script のエディタで作れるファイルは、スクリプト(.gs)と HTML の2種類だけです。CSS や JS のファイルは作れません。HTML から同じプロジェクトの別ファイルを、<link> や <script src> で読み込む仕組みもありません。
そのかわり、HTML をテンプレートとして読むと、中から自分で書いた関数を呼べます。これを使って、ほかのファイルの中身を差し込む関数を用意するのが定番の形です。公式のベストプラクティスでも紹介されています。
3つに分ける
| ファイル | 中身 |
|---|---|
index.html |
画面の構造だけ |
style.html |
<style> で囲んだ CSS |
script.html |
<script> で囲んだ、画面側の JS |
style.html と script.html は、中身が CSS や JS でも拡張子は .html になります。そのぶん、中身をタグで囲んでおく必要があります。<style> を書き忘れると、CSS がただの文字として画面に出てしまうので気をつけてください。
include() は自分で書く
コード.gs に次の関数を足します。
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
ファイルの中身を文字列にして返すだけの関数です。名前は include でなくても動きますが、公式の例やほかの記事と揃うので、このままにしておくと楽です。
呼び出す側の index.html は、こう書きます。
<!doctype html>
<html>
<head>
<base target="_top" />
<?!= include('style'); ?>
</head>
<body>
<h1>週次フォーム集計</h1>
<?!= include('script'); ?>
</body>
</html>
include() には、拡張子を付けずにファイル名を渡します。
doGet() はテンプレートとして読む
doGet() の側も、テンプレートとして読む形に変えます。
function doGet() {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setTitle('週次フォーム集計');
}
setTitle() の文字列は、ブラウザのタブに出るタイトルです。ツールの名前に置き換えてください。
createHtmlOutputFromFile() で読んだままだと、<?!= ... ?> が実行されません。CSS も JS も読み込まれないので、分けた直後にスタイルが当たらず、ボタンも反応しなくなったら、まずここを見てください。
createTemplateFromFile() で読むことと、evaluate() を呼ぶことは、セットで覚えておくと安心です。
<?= ?> と <?!= ?> は別物
テンプレートの中で使う書き方は3つあります。
<? /* 何も出力しない。ループや条件分岐に使う */ ?>
<?= '出力する。エスケープされる' ?>
<?!= '出力する。エスケープされない' ?>
include() が返すのは HTML そのものなので、エスケープされると困ります。<?= で読み込むと <style> までエスケープされて、タグとして働きません。include() には <?!= を使う、とだけ覚えておけば大丈夫です。
逆に、シートから読んだ値や、ユーザーが入力した文字列を出すときは <?= を使います。公式でも、<?= のエスケープはクロスサイトスクリプティングを防ぐためのものと説明されています。<?!= で出すと、中身が HTML としてそのまま解釈されてしまいます。
meta タグは addMetaTag() で足す
index.html の <head> に viewport の meta タグを書いても、効きません。Apps Script の HTML ファイルに直接書いた meta タグは無視される、という仕様です。
スマートフォンでの表示を整えたいときは、doGet() の側で足します。
function doGet() {
return HtmlService.createTemplateFromFile('index')
.evaluate()
.setTitle('週次フォーム集計')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
addMetaTag() で足せるのは、次の4つだけです。OGP のタグなどは足せません。
viewportgoogle-site-verificationmobile-web-app-capableapple-mobile-web-app-capable
読み込む位置と、リンクの target
style は <head> の中、script は </body> の直前で読み込みます。
<script> を最後に置くと、先に画面が表示されてから JS が動きます。公式のベストプラクティスでも、この順番が勧められています。データを取ってくるあいだ、「読み込んでいます」のような表示を先に出しておけるからです。
この位置なら、JS からすぐに画面の要素を触っても大丈夫です。<head> で読み込むと、まだ作られていない要素を探しにいくことになり、getElementById() が null を返します。
<head> の <base target="_top" /> も、忘れずに入れておいてください。Web アプリの画面では、リンクの target を _top か _blank にする必要があります。<base> に書いておけば、ページの中のリンクすべてにまとめて効きます。
分けたあとに動かないとき
| 画面の様子 | 見るところ |
|---|---|
| スタイルが当たらず、ボタンも反応しない | doGet() が createTemplateFromFile() と evaluate() になっているか |
| CSS がそのまま文字で並んでいる | style.html が <style> で囲まれているか。<?= で読み込んでいないか |
| スマートフォンで文字が小さすぎる | addMetaTag() で viewport を足したか |
3つに分けておくと、style.html を差し替えるだけで見た目を変えられるようになります。