この記事のWebpack設定はVue CLI v2向けのものです。
Vue CLI v3のvue.config.jsに関しては下記の記事を参照してください。
GoogleAppsScriptのWebAppsでVue.jsするためのvue.config.js
https://qiita.com/clomie/items/ef10db03b9ecef29ca5d
はじめに
vue-cliのwebpackテンプレートで作成したVue.jsのアプリケーションを、GoogleAppsScript(以下GAS)のHTMLServiceで動作可能な形にビルドする手順をまとめます。
最終的にはnpm run build
でビルドしたファイルを、clasp push
でそのままアップロード可能にすることが目的です。
Vueのプロジェクトを作成するところからスタートします。
プロジェクト名は仮に"vue-deploy-gas-sample"として進めます。
$ vue init webpack vue-deploy-gas-sample
$ cd vue-deploy-gas-sample
対応手順
- 出力ファイルをHTMLファイル1つにまとめる
- GAS+claspのセットアップ
- vue-routerとgoogle.script.historyの同期
セットアップ完了後のリポジトリです。
https://github.com/clomie/vue-deploy-gas-sample
手順通りにコミットを作ってあるので、追っていく感じで説明します。
出力ファイルをHTMLファイル1つにまとめる
GASでは、JSファイルやCSSファイルを分割して配信することはできません。
公式のガイドでは、styleタグやscriptタグとして分離したファイルを用意して、GAS内でインライン化してからレスポンスを返す方法が推奨されています。
https://developers.google.com/apps-script/guides/html/best-practices
今回はwebpackを使っているので、ビルドの段階ですべてHTMLファイルにインライン化します。
HtmlWebpackInlineSourcePluginの導入
vue-cliのwebpackテンプレートではHtmlWebpackPluginを使ってindex.htmlを生成していますが、サブプラグインであるHtmlWebpackInlineSourcePluginでインライン化できるようなので、これを導入します。
$ npm install -D html-webpack-inline-source-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
+const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
プロダクションビルド用のwebpack設定ファイルに、HtmlWebpackInlineSourcePluginを適用します。デフォルトでは何もインライン化されない設定になっているので、inlineSource
プロパティの設定を忘れないように。
また、minify
の設定でremoveAttributeQuotes
がtrueだとGAS上でエラーになってしまうのでOFFにします。
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
+ // embed inline all javascript and css
+ inlineSource: '.(js|css)$',
minify: {
removeComments: true,
collapseWhitespace: true,
- removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
+ new HtmlWebpackInlineSourcePlugin(),
SourceMapのインライン化
GAS上でSourceMapを見たい場合は、SourceMapもインライン化する必要があります。
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
- ? { safe: true, map: { inline: false } }
+ ? { safe: true, map: { inline: true } }
: { safe: true }
}),
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
- devtool: '#source-map',
+ devtool: '#inline-source-map',
静的ファイルのインライン化
フォントファイル、画像ファイルなどもインライン化する必要があるため、url-loaderのlimit設定を取り除きます。
また、webpackテンプレートではstaticディレクトリが用意されていますが、これもなかったことにします。
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
- limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
- limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
- limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
- // copy custom static assets
- new CopyWebpackPlugin([
- {
- from: path.resolve(__dirname, '../static'),
- to: config.dev.assetsSubDirectory,
- ignore: ['.*']
- }
- ])
GAS+claspのセットアップ
GASプロジェクト作成
作成したプロジェクトのディレクトリでclasp create
コマンドを実行します。
$ clasp create vue-deploy-gas-sample
GASのプロジェクトが作成されて、.clasp.json
とappsscript.json
が生成されます。
デフォルトの設定では、claspはプロジェクトフォルダ内のファイルをすべてGASプロジェクトにアップロードするため、ファイルの配置と内容を変更していきます。
.clasp.json
claspでGASプロジェクトへファイルをアップロードするための設定ファイルです。デフォルトではGASプロジェクトのIDだけが書かれています。
crasp push
コマンドを実行する際のカレントディレクトリに配置されている必要があるため、これはプロジェクトフォルダルートにこのまま置いておきます。
コマンド実行時にnpm run build
で生成したファイルがアップロードされるように、rootDirにdist
を指定しておきます。
-{"scriptId":"<Your GAS Project ID>"}
+{
+ "scriptId": "<Your GAS Project ID>",
+ "rootDir": "dist"
+}
gas/appsscript.json
.clasp.json
のrootDirで指定したディレクトリに配置されている必要があります。
ただし、dist
ディレクトリにビルド成果物以外を置いておきたくないので、プロジェクトフォルダ内にgas
ディレクトリを作ってそこに置くことにします。
webpack実行時にgas
ディレクトリからdist
ディレクトリにコピーする設定を追加します。
gas/Code.js
GASプロジェクトでのエントリポイントとなるファイルです。
実際にGAS上でページを開いたときに呼び出される処理です。これもgas
ディレクトリに配置します。
function doGet() {
return HtmlService.createHtmlOutputFromFile("index");
}
.claspignore
claspでアップロードする際の無視ファイルリストです。
claspリポジトリのREADMEにも例がありますが、アップロードしたいファイルのほうが少ないので、ホワイトリスト方式で書いたほうが楽です。
これはプロジェクトフォルダルートに配置します。
**/**
!dist/appsscript.json
!dist/Code.js
!dist/index.html
webpack設定
gas
ディレクトリ内のファイルをビルド時にdist
ディレクトリにコピーする設定を、webpackの設定ファイルに追記します。
children: true,
minChunks: 3
}),
+ // copy google apps script files
+ new CopyWebpackPlugin([
+ {
+ from: path.resolve(__dirname, '../gas'),
+ ignore: ['.*']
+ }
+ ]),
+
// copy custom static assets
new CopyWebpackPlugin([
{
ここまででとりあえず動く
ひとまず、ここまでで最低限の設定は完了です。
ビルドすればGASプロジェクトにアップロードするファイルが生成されます。
$ npm run build
> vue-deploy-gas-sample@1.0.0 build /path/to/vue-deploy-gas-sample
> node build/build.js
Hash: d0dd6dffd9a3d968a929
Version: webpack 3.11.0
Time: 11674ms
Asset Size Chunks Chunk Names
static/js/vendor.7fed9fa7b7ba482410b7.js 850 kB 0 [emitted] [big] vendor
static/js/app.b22ce679862c47a75225.js 41.2 kB 1 [emitted] app
static/js/manifest.2ae2e69a05c33dfc65f8.js 7.5 kB 2 [emitted] manifest
static/css/app.30790115300ab27614ce176899523b62.css 1.52 kB 1 [emitted] app
index.html 900 kB [emitted] [big]
appsscript.json 98 bytes [emitted]
Code.js 77 bytes [emitted]
Build complete.
Tip: built files are meant to be served over an HTTP server.
Opening index.html over file:// won't work.
ビルド結果にはJSファイル、CSSファイルも含まれていますが、index.htmlを見るとすべてインライン化されています。
GASプロジェクトにアップロードしてみましょう。
$ clasp push
└─ dist/Code.js
└─ dist/appsscript.json
└─ dist/index.html
Pushed 3 files.
あとはGASプロジェクトからWebアプリケーションとしてデプロイすれば動くはずです。
https://developers.google.com/apps-script/guides/web#deploying_a_script_as_a_web_app
vue-routerとgoogle.script.historyの同期
GASでデプロイしたアプリはiframe内で動作するため、vue-routerを使った場合、画面遷移とブラウザのURLが同期しません。
routerのナビゲーションを拾って、GASのClient-side APIからブラウザのURLを更新することで、これを解決できます。
export const sync = router => {
if (!window.google) {
return
}
window.google.script.url.getLocation(location => {
const path = location.hash
const query = location.parameter
router.replace({ path, query })
})
router.afterEach(route => {
window.google.script.history.replace(null, route.query, route.path)
})
}
google.script.history
, google.script.url
はGAS上のWebアプリでグローバルにアクセス可能なオブジェクトです。
その他
この記事ではvue-cliのwebpackテンプレートをベースに説明しましたが、
出力ファイルが1つで良いのであれば、webpackの設定はよりシンプルにできるはずで、主にExtractTextPluginや、CommonsChunkPluginなどは不要になるかと思います。
このあたりもう少し詰めればビルド時間が短くなったりするかもしれません。
参考リンク
google/clasp
Web Apps | Apps Script | Google Developers
HTML Service | Apps Script | Google Developers
Google Apps Scriptの新しい3つの機能 その③ CLI Tool Clasp
Google Apps Script をローカル環境で快適に開発するためのテンプレートを作りました
Google Apps Script でAngularJSを使った Single Page Application を構築・公開する方法(基礎編)