Angular CLI で作ったアプリを Heroku にデプロイして動くようにした
Angular CLI で生成したアプリを Heroku にデプロイして動作するようにするには、いくつか設定変更が必要だったので紹介。
目次
- 環境情報
- Angular プロジェクトを作る
package.json
のdevDependencies
をdependencies
に移動するpackage.json
にengines
を記述するpostinstall
時にng build
を実行させるangular.json
を書き換えてファイルがdist/
直下に生成されるようにする- 多分任意 : HashLocationStrategy を使っておく
- Express サーバを用意する
npm start
で Express サーバを起動してもらうようpackage.json
を修正する- ココまでの対応内容まとめ
- Heroku にデプロイする
- 以上
環境情報
- Angular CLI : v7.0.4
- Node.js : v10.7.0
- npm : v6.1.0
- OS : macOS Mojave・Windows10 で検証
以下を前提条件とする。
- Angular CLI に関するひととおりの知識があること (コマンド等は詳しく説明しない)
- Heroku CLI がインストールしてあり、Angular プロジェクトと Heroku アプリの紐付けなどは自分でできること (細かいところまで説明しない)
Angular プロジェクトを作る
まずは Angular CLI で Angular プロジェクトを作る。ココは普通に $ ng new
。
package.json
の devDependencies
を dependencies
に移動する
Heroku でのデプロイは、Heroku 上で git clone
相当の資材取得を行い、npm install --production
相当のコマンドで npm install
が行われる。
すなわち、devDependencies
に記載されたパッケージはインストールされないのだ。この仕様により、Angular CLI で生成したプロジェクトは、typescript
パッケージなどが devDependencies
に記載されているため、このままではビルドが上手く行われないのだ。
そこで、package.json
の devDependencies
のうち、最低でも以下のパッケージを dependencies
に移動しておく必要がある。
- @angular-devkit/build-angular
- @angular/cli
- @angular/compiler-cli
- @angular/language-service
- typescript
考え方としては、ng build
コマンドの動作に必要なパッケージを移動しておけば良い、ということだが、心配なら全ての devDependencies
の内容を dependencies
に移植しても問題はない (デプロイ時の npm install
に時間がかかるようになるだけ)。
自分の場合は Karma・Jasmine・Protractor など、テスト関連ツールを省き、それ以外は全て dependencies
に書くことにした。@types
はトランスパイル時に必須ではないだろうし、tslint
も別に要らない、ts-node
はよく分からない、という感じだが、この辺はとりあえず dependencies
に移植しておいた。
package.json
に engines
を記述する
Heroku 上で npm install
等を動作させるために、使用する Node.js および npm のバージョンを指定する必要があるので、package.json
に engines
プロパティを記述しておく。バージョン番号は x
を使用して曖昧指定もできるので、現行の最新メジャーバージョンである Node.js v10、npm v6 系を指定しておこう。
"engines": {
"node": "10.x",
"npm": "6.1.x"
}
postinstall
時に ng build
を実行させる
先程も書いたように、Heroku へのデプロイ時に、自動的に npm install
コマンドが動作するので、この挙動を利用して、postinstall
時に ng build
を実行するように package.json
に npm-run-scripts を書いておこう。
"scripts": {
"postinstall": "npm run build",
"build": "ng build --prod"
}
build
コマンドは Angular CLI が最初から用意していると思うので、それを呼び出すだけの postinstall
コマンドにしておくと良いだろう。
angular.json
を書き換えてファイルが dist/
直下に生成されるようにする
Angular v6 以降、一つの Angular プロジェクト内に複数のアプリやライブラリを持てるようになった関係で、ng build
時の成果物ファイルは dist/
直下ではなく、dist/【アプリ名】/
ディレクトリに出力されるようになった。
今回は複数アプリを作る要件はないので、Angular v5 までと同様、dist/
直下に index.html
やら bundle.js
やらが生成されるように設定を変更しようと思う。
ビルド時の出力先ディレクトリを決めているのは、angular.json
の次の部分。
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/【アプリ名】", // ← ココ!
この outputPath
部分を、dist/【アプリ名】
から dist
のみに修正する。
"options": {
"outputPath": "dist",
コレで ng build
による成果物の生成先ディレクトリを1階層上げることができた。
多分任意 : HashLocationStrategy を使っておく
このあと、この Angular アプリを Express にて配信するのだが、余計なルーティングの設定を避けるために、Angular アプリ内の URL 表現を、通常の PathLocationStrategy ではなく、HashLocationStrategy に変更しておこうと思う。
何を言っているかというと、アプリ内のルーティングによる URL 変更を、http://localhost/my-page/hoge
といった見た目にするのではなく、http://localhost/#/my-page/hoge
と、ハッシュ #
付きの URL にしよう、というワケ。この URL なら、必ずルートパスからのハッシュリンクの形で遷移できるので、Express 側でのリダイレクト処理などが省けるかな、という狙い。
- 参考 : Angular Docs
HashLocationStrategy を使うには、RouterModule.forRoot()
を imports
に設定する箇所で、useHash: true
オプションを渡してやるだけ。通常 --routing
オプションによって app-routing.module.ts
を生成していれば、このファイルに追記して設定すれば良い。
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })], // ← useHash を追加
exports: [RouterModule]
})
export class AppRoutingModule { }
ココらへんはどれだけ Express 上の設定をするかに応じて調整できるかと。今回は余計な問題を避けるためハッシュ利用にしておく。
Express サーバを用意する
最後に、これら Angular アプリを配信するための Express サーバを用意する。ng build
で生成された dist/
ディレクトリ配下の静的なファイルを配信するだけなので、コードとしては簡素なモノで済ませられる。
# Express をインストールして dependencies に追加する
$ npm install --save express
# Express サーバの設定を記述するファイルを作る
$ touch server.js
server.js
の中身は以下のとおり。
const path = require('path');
const express = require('express');
// サーバをインスタンス化する
const app = express();
// 以下の設定だけで dist/index.html も返せてはいる
app.use(express.static(`${__dirname}/dist`));
// ルートへのアクセス時は念のため dist/index.html を確実に返すようにしておく
app.get('/', (req, res) => {
res.sendFile(path.join(`${__dirname}/dist/index.html`));
});
// サーバ起動
const server = app.listen(process.env.PORT || 8080, () => {
const host = server.address().address;
const port = server.address().port;
console.log(`Listening at http://${host}:${port}`);
});
一番のキモはこの部分。
app.use(express.static(`${__dirname}/dist`));
コレは
app.use('/', express.static(`${__dirname}/dist`));
とほぼ同義で、ルート配下のパスを指定されたら、そのパスに合致するファイルを静的に返すという指定。で、参照先を dist/
配下にしているので、ng build
コマンドで dist/
配下に生成された、bundle.js
やら favicon.ico
やらがこの記述で取れる、というワケ。
この記述によって、http://localhost:8080/index.html
はも返せるのだが、http://localhost:8080/
とアクセスした時に確実に index.html
を返すために、その下で res.sendFile()
を使ったルーティングを定義している。
__dirname
という Node.js 組み込みの変数を使っているのは、この server.js
のパスに依存した相対パスを利用せず、フルパスで指定するため。
npm start
で Express サーバを起動してもらうよう package.json
を修正する
あとはこの Express サーバを npm start
で起動できるよう、package.json
に start
コマンドを記述しておく。
"scripts": {
"postinstall": "npm run build",
"build": "ng build --prod",
"start": "node server.js",
"dev": "ng serve"
}
Angular CLI で生成したとおりの package.json
だと、ng serve
コマンドを実行するために start
コマンドが割り当てられているが、コレは外しておかないといけない。適当に dev
コマンドでも割り当てておけば開発中にも使えるだろう。
ココまでの対応内容まとめ
ココまでの対応内容をおさらいする。
angular.json
outputPath
をdist/
に変更
package.json
- ビルド・デプロイ時に必要なパッケージを
devDependencies
からdependencies
に移植する engines
プロパティを記述するpostinstall
時にng build --prod
を実行するように設定するstart
コマンドでnode server.js
を実行するように設定する
- ビルド・デプロイ時に必要なパッケージを
server.js
ng build
で生成されたdist/
ディレクトリ配下のファイルを静的に返すルーティングを実装しておく
app-routing.module.ts
- HashLocationStrategy を使用するように変更 (任意)
コレで準備完了。
Heroku にデプロイする
ココまでできたら、いよいよ Heroku へのデプロイだ。デプロイの仕方は通常の Node.js プロジェクトと同様、$ git push heroku master
コマンドで Heroku に git push
すれば良い。
$ git push heroku master
Heroku に向けて git push
すると、git clone
と npm install
が行われ、postinstall
コマンドが呼ばれて ng build --prod
処理が実行される。成果物ファイルは angular.json
の設定どおり、dist/
ディレクトリ直下に出力される。
その後 npm start
コマンドが呼ばれ、server.js
に書いた Express サーバが起動する。
ココまでできたら、https://【アプリ名】.herokuapp.com/
にアクセスしてみよう。ビルドされた Angular アプリが動作しているはずだ。
以上
以上の作業で、とりあえず Angular アプリを Heroku 上で動作させることはできたと思う。
クライアントサイドで完結する Angular アプリなら、別に GitHub Pages に Push しても同じだが、今回は PostgreSQL も付いてくる Heroku にデプロイしたので、この後はサーバサイドも実装してみて、Angular アプリと Express サーバがうまいことやり取りして、DB も絡んで、セッション管理とかもするような Web アプリケーションを作っていきたいと思う。