Google Apps Script で Web アプリケーションとして作成しようと思い、フロントエンドを Angular 7にして、初期設定、デプロイまで実施してみました。
Angular(+2) の デプロイ方法は Web を調べても見つからなかったのですが、Vue.js の デプロイの手順を参考にして実施したところうまく画面表示するところまではできました。
手順、留意点をを記載します。
前提
以下の環境で実施しています。
-
OS
sw_vers ProductName: Mac OS X ProductVersion: 10.14.3 BuildVersion: 18D109
-
Node.js のバージョン
node -v v11.8.0
-
Angular のバージョン
ng version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI: 7.3.1 Node: 11.8.0 OS: darwin x64 Angular: ... Package Version ------------------------------------------------------ @angular-devkit/architect 0.13.1 @angular-devkit/core 7.3.1 @angular-devkit/schematics 7.3.1 @schematics/angular 7.3.1 @schematics/update 0.13.1 rxjs 6.3.3 typescript 3.2.4
プロジェクトのディレクトリ構成について
Angular プロジェクトの frontend
、 clasp プロジェクトの backend
を分けて作成しました。
ここは 1つにもできそうに思いますが、Angular の自動生成したファイル書き換えていいものか迷ったので 2つにしました。
-
Treeコマンドの結果
tree -L 2 . ├── backend │ ├── LICENSE.txt │ ├── dist │ ├── node_modules │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── tsconfig.json │ ├── tslint.json │ └── webpack.config.js ├── build.sh └── frontend ├── README.md ├── angular.json ├── dist ├── e2e ├── extra-webpack.config.js ├── node_modules ├── package-lock.json ├── package.json ├── src ├── tsconfig.json └── tslint.json
-
build.sh の内容
プロジェクトルートのbuild.sh
の中身です。
frontend
の ビルド後に、backend
の clasp プロジェクトをデプロイしています。
もっとここはいい方法がありそうですが、これでも実現ができました。
#!/bin/sh cd ./frontend ng build --prod cd ../backend npm run deploy
Angular CLI をインストール、Angular プロジェクトフォルダの作成
フロンドエンドのプロジェクトを作成します。
-
Angular CLI
のインストール
Angular CLI
をインストールします。
npm install -g @angular/cli
-
プロジェクトフォルダの作成
プロジェクトルートに移動して、ng
コマンドで Angular プロジェクトを作成します。
ng new frontend
Angular プロジェクトを Google Apps Script にデプロイするために調整する
Angular の内部で動いている webpack は 設定が隠蔽化されています。
Angular 6 で使用できた ng eject
コマンドは Angular 7 では廃止されていて、ng eject
コマンドを実行すると以下のようなメッセージが出力されます。
-
ng eject
実行時のメッセージ
メッセージに記載されているng eject The 'eject' command has been disabled and will be removed completely in 8.0. The new configuration format provides increased flexibility to modify the configuration of your workspace without ejecting. There are several projects that can be used in conjuction with the new configuration format that provide the benefits of ejecting without the maintenance overhead. One such project is ngx-build-plus found here: https://github.com/manfredsteyer/ngx-build-plus
ngx-build-plus
のインストールだけではうまく動かず、Angular7でwebpack configを調整してバンドルサイズや挙動を自在に操る(ag-Grid入門付き!) - Qiita を参考に設定したところうまくいきました。
調整した内容について以下に記載します。 -
Angular プロジェクトルートに移動
ここからの作業は、Angular プロジェクトルート で行います。
cd frontend
-
webpack の調整に必要なパッケージのインストール
以下、パッケージのインストールを行います。
npm i -D @angular-builders/custom-webpack npm i -D ngx-build-plus npm i -D webpack
-
angular.json
の編集
angular.json
内の記述を以下のように編集します。
//builder を変更する //"builder": "@angular-devkit/build-angular:browser" "builder": "@angular-builders/custom-webpack:browser", "options": { "outputPath": "dist/frontend", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.app.json", //webpack の追加設定ファイルを指定する "customWebpackConfig": { "path": "./extra-webpack.config.js" },
-
extra-webpack.config.js
で使用するパッケージのインストール
extra-webpack.config.js
で使用するパッケージをインストールします。
html-webpack-inline-source-plugin は 出力ファイルをHTMLファイル1つにまとめる ため、
webpack-cdn-plugin は Angular 関連の JavaScript を CDN から取得するために使います。
npm i -D html-webpack-plugin npm i -D html-webpack-inline-source-plugin npm i -D webpack-cdn-plugin
-
extra-webpack.config.js
の作成
extra-webpack.config.js
の記述は以下になります。
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); const WebpackCdnPlugin = require('webpack-cdn-plugin'); module.exports = { // cdn から取得する対象のライブラリを、externals に指定する "externals": { "rxjs": "rxjs", "zone.js": "Zone", "@angular/core": "ng.core", "@angular/common": "ng.common", "@angular/platform-browser": "ng.platformBrowser", "@angular/router": "ng.router" }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: './src/index.html', // webpack-cdn-plugin で cdn から読み込む対象にしているjs,cssはインライン化の対象外にする inlineSource: '^(?!http).*.(js|css)$', // Google Apps Script で読み込む際に都合が悪いので、minify時の動作を変更する minify: { removeAttributeQuotes: false, removeScriptTypeAttributes: false } }), new HtmlWebpackInlineSourcePlugin(), // Angular 関連のライブラリはCDNから取得する new WebpackCdnPlugin({ modules: [ { name: 'rxjs', var: 'rxjs', path: 'bundles/rxjs.umd.min.js' }, { name: '@angular/core', var: 'ng.core', path: 'bundles/core.umd.min.js' }, { name: '@angular/common', var: 'ng.common', path: 'bundles/common.umd.min.js' }, { name: '@angular/platform-browser', var: 'ng.platformBrowser', path: 'bundles/platform-browser.umd.min.js' }, { name: '@angular/router', var: 'ng.router', path: 'bundles/router.umd.min.js' }, { name: 'zone.js', var: 'Zone', path: 'dist/zone.js' } ], publicPath: '/node_modules' }) ] }
-
app-routing.module.ts の調整
Webアプリケーションとして公開時、HTML は以下のような URL に割り当てられます。
https://xxxxxxxxxxxxxxxxxxxxxxxxxx-script.googleusercontent.com/userCodeAppPanel
この URL に対して Angular の Router のマッピングがないと以下のようなエラーが発生します。
このため自動生成されたcore.umd.min.js:563 ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'userCodeAppPanel' Error: Cannot match any routes. URL Segment: 'userCodeAppPanel'
app-routing.module.ts
の Routes の定義を以下のように変更しました。
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; const routes: Routes = [ { path: '', component: AppComponent, pathMatch: 'full' }, // root 以外の path を 空コンポーネントにマッピングしておく { path: '**', children: [] } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
Angular プロジェクト に対して実施したことを以上です。
claspプロジェクトの作成
clasp プロジェクトは、howdy39/gas-clasp-starter: A starter template for Google Apps Script by clasp をベースに作成しました。
追加で実施したことについて記載します。
-
パッケージのインストール
Angular プロジェクトで build した html を clasp の デプロイ時に含めたいので、copy-webpack-plugin
をインストールしました。
npm i -D copy-webpack-plugin
-
backend/webpack.config.js
の修正
copy-webpack-plugin
で対象のファイルをコピーする記述を追加しました。
設定は以上です。const path = require('path'); const GasPlugin = require("gas-webpack-plugin"); const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { mode: 'development', entry: './src/index.ts', devtool: false, output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /\.ts$/, use: 'ts-loader' } ] }, resolve: { extensions: [ '.ts', '.js' ] }, plugins: [ new GasPlugin(), // Angular プロジェクトでビルドした、index.html を、dist ディレクトリ配下にコピーする new CopyWebpackPlugin([ { from: "../frontend/dist/frontend/index.html", to: "./index.html", toType: 'file' } ]) ] };
これで、プロジェクトルートで、build.sh
を実行し、Google Apps Script で Web アプリケーションとして公開すると、ページが表示されるようになります。
その他試したこと、うまくいかないこと
上記作業中に試しはしたがうまくいかなかったので諦めたことになります。
-
dynamic-cdn-webpack-plugin
の利用
webpack-cdn-plugin での パスの指定が面倒なので、dynamic-cdn-webpack-plugin - npm を使おうとしましたが、以下、module-to-cdn で rxjs 6 以降の cdn のパスの解決ができない問題があり、webpack-cdn-plugin を使用しました。
support rxjs 6 and only support version 5 and above by juanferreira · Pull Request #13 · mastilver/module-to-cdn -
copy-webpack-plugin
を使わずに、html-webpack-plugin
を使っていた。
copy-webpack-plugin
の存在を知らず、html-webpack-plugin
を使って Angular プロジェクトのファイルコピーを行なっていました。
この方法でも、html-webpack-exclude-assets-plugin
と併用することで、ファイルコピーが実現可能ですが、あまりいいやり方ではないです。
以下、パッケージをインストールして、
以下、webpack.config.js でファイルコピーは実現できました。npm i -D html-loader npm i -D html-webpack-exclude-assets-plugin npm i -D html-webpack-plugin
const path = require('path'); const GasPlugin = require("gas-webpack-plugin"); const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackExcludeAssetsPlugin = require('html-webpack-exclude-assets-plugin'); module.exports = { mode: 'development', entry: './src/index.ts', devtool: false, output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /\.ts$/, use: 'ts-loader' }, { test: /\.html$/, loader: "html-loader" } ] }, resolve: { extensions: [ '.ts', '.js' ] }, plugins: [ new GasPlugin(), // Angularプロジェクトの new HtmlWebpackPlugin({ excludeAssets: [/\.js$/] , filename: "./index.html", template: "../frontend/dist/frontend/index.html", }), new HtmlWebpackExcludeAssetsPlugin() ] };
-
favicon.icoのbase64エンコード
index.html 内に、favicon.ico の記載がありますが、webpack で favicon.ico を base64 エンコードする方法がわからず、一旦放置しました。
Google Apps Script から直接 URL を指定するか、gulp ですが favicon を base64 エンコードする plugin があったので、後日実施しようかと思います。 - GASで作成したWebページにファビコンを設定する方法
- gulp-base64-favicon - npm
参考
以下、作業実施時に参考にした記事になります。
- Angular7でwebpack configを調整してバンドルサイズや挙動を自在に操る(ag-Grid入門付き!) - Qiita
-
clomie/gas-vue-typescript: Google Apps Script with Vue.js, TypeScript, …
-
Angular7でwebpack configを調整してバンドルサイズや挙動を自在に操る(ag-Grid入門付き!) - Qiita
-
Angular
<router-outlet>
displays template twice - Stack Overflow -
Google Apps Script でAngularJSを使った Single Page Application を構築・公開する方法(基礎編) - Qiita
以上です。
コメント