はじめに
とあるWebアプリを開発するにあたって、一つのプロジェクトに複数のAngularアプリを構成したくなった。
したくなったので、やってみた。
「もっといい方法あるよー」とか「これ間違ってるよー」とかあったら教えてください。
2018/3/20 更新
アプリでassetsを使っているときにpathが合わなくなることが判明。
assets内のリソースへのpathを相対pathで指定することと、
.angular-cli.jsonの設定に以下を追加することで対応できた。
2018/12/30 追記
タグにもありますが、angular v5の時代の記事です。
angular v7.1.4現在、.angular-cli.json
はangular.json
に置き換わっており、中身を見る限りマルチプロジェクトに対応しているようです。
angular.json
の解説は2018 Angular advent calender内の記事などあるので、そちらを参照ください。
"apps": [
{
"baseHref": "./",
...
フォルダ構成
書いてみたけどスーパー見づらい。
.
|-- coverage // テストカバレッジ
| |-- first-app
| |-- second-app
| `-- server
|-- dist // ビルド結果
| |-- first-app
| |-- second-app
| `-- server
|-- e2e // e2eテストは一旦パス
|-- server // バックエンド側ソース
| |-- index.spec.ts
| |-- index.ts
| `-- tsconfig.json
|-- src // フロント(Angular)アプリ側ソース
| |-- first-app
| | |-- app
| | | |-- app.component.css
| | | |-- app.component.html
| | | |-- app.component.spec.ts
| | | |-- app.component.ts
| | | `-- app.module.ts
| | |-- assets
| | |-- environments
| | | |-- environment.prod.ts
| | | `-- environment.ts
| | |-- favicon.ico
| | |-- index.html
| | |-- main.ts
| | |-- polyfills.ts
| | |-- styles.css
| | |-- test.ts
| | |-- tsconfig.app.json
| | |-- tsconfig.spec.json
| | `-- typings.d.ts
| |-- second-app
| | |-- app
| | | |-- app.component.css
| | | |-- app.component.html
| | | |-- app.component.spec.ts
| | | |-- app.component.ts
| | | `-- app.module.ts
| | |-- assets
| | |-- environments
| | | |-- environment.prod.ts
| | | `-- environment.ts
| | |-- favicon.ico
| | |-- index.html
| | |-- main.ts
| | |-- polyfills.ts
| | |-- styles.css
| | |-- test.ts
| | |-- tsconfig.app.json
| | |-- tsconfig.spec.json
| | `-- typings.d.ts
| |-- polyfills.ts // アプリ一括テスト用polyfill
| |-- test.ts // アプリ一括テスト用ブートストラップ
| `-- tsconfig.spec.json // アプリ一括テスト用コンフィグ
|-- README.md
|-- karma.conf.js
|-- package-lock.json
|-- package.json
|-- protractor.conf.js
|-- tsconfig.json
`-- tslint.json
複数アプリ構成になるまで
それぞれのアプリのソースを置く
ng new
で生成されたソースだと、./src
以下にアプリのソースが集まっている。
複数アプリに対応できるように、ソースの移動&コピーを行う。
# './src' copy to './src/first-app' & './src/second-app'
mv ./src ./first-app
mkdir ./src
mv ./first-app ./src
cp -r ./src/first-app ./src/second-app
それぞれのtsconfig.xxx.jsonのパスがずれるので直す
{
"extends": "../../tsconfig.json", // <-- ここと
"compilerOptions": {
"outDir": "../../out-tsc/app", // <-- ここ
"baseUrl": "./",
...
アプリ側はこれだけ。
.angular-cli.jsonを直す
Angular CLIが複数アプリを正しく扱えるように設定してやる。
...
"apps": [
{
"name": "first-app", // (Optional) 名前を付けてやるとあとでうれしいかも。
"root": "src/first-app", // ソースコードのルートディレクトリ
"outDir": "dist/first", // 出力先ディレクトリ
"deployUrl": "/first-app", // デプロイするurl (expressのルート設定と合わせる)
...
},
{
"name": "second-app", // 二つ目のアプリも同様に
"root": "src/second-app",
"outDir": "dist/second",
"deployUrl": "/second-app",
...
}
],
...
パスによってExpressが返すページを変える
サーバのフレームワークはExpressを使ってます。
執筆時点ではexpress@4.16.3
でした。
また、AngularがTypescriptなのでサーバサイドもTypescriptで書いちゃいました。
tsconfig.jsonもフロント側のtsconfig.app.jsonあたりから持ってきてサクッと書いちゃいます。
import * as path from "path";
import * as express from "express";
const app = express();
// ...
// .angular-cli.jsonで指定したdeployUrlとパスを合わせること
app.use("/first-app", express.static(path.resolve(`${__dirname}/../first-app`));
app.use("/second-app", express.static(path.resolve(`${__dirname}/../second-app`));
// ...
{
"extends": "../tsconfig.json", // Anguler CLIでルートディレクトリに吐き出される設定を使っとく
"compilerOptions": {
"outDir": "../dist/server", // ビルド出力先を設定
"baseUrl": "./",
"module": "commonjs", // イマイチ把握してない。誰か教えてください。
"target": "es2015", // 使っているNode.jsのバージョンに合わせて出力フォーマットを決める。(node v8系なのでes2015は動く)
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
複数アプリをそれぞれビルドする
今の所、.angular-cli.jsonのappsに複数アプリを書いても一括で全部ビルドしてくれたりserveしてくれたりする機能はないようです。
なので、個別にビルドします。
サーバサイドもついでにビルドします。
# .angular-cli.jsonにnameプロパティをちゃんと付けてたらアプリ名で指定できる
ng build --prod -a first-app
ng build --prod -a second-app
tsc -p server/
# nameプロパティ付けてない場合は配列のインデックスで。
# あとの工程でインデックスをずらすので注意。
ng build --prod -a 0
ng build --prod -a 1
tsc -p server/
こんだけ打つの面倒なので、npm scriptに書いちゃいましょう。
開発に使ってるPCがWindowsで、デプロイ先がDebian系だったりするので、npm-run-allのお世話になってます。
Linux系OS使わせて。
"scripts": {
"build": "npm-run-all -s build:**:*",
"build:first": "ng build --prod -a first",
"build:second": "ng build --prod -a second",
"build:server": "tsc -p server/",
}
実行する!
node dist/server/index.js
http://<deploy-domain>/first-app
とhttp://<deploy-domain>/second-app
にアクセスするとそれぞれのアプリが表示されるはずです。
実行まではできたけど。
もうちょっとやりたいことがある。
- ng serveで画面を確認しながらコード書きたい。
- ユニットテストはどうやるの?
ng serveしたい
これは、ng serve
にオプションを付けるだけで出来ました。
ng serve --app first-app
ng serve --app second-app
ng build
と同じですね。
ユニットテストしたい
アプリをそれぞれ別個にテストするだけなら簡単。
ng test --app first-app
ng test --app second-app
これもng build
と同じくオプション付けるだけでした。
ここで疑問。
ユニットテストのカバレッジってどう取る…?
それぞれ別個にカバレッジを取るだけならまぁ良い。
ng test --app first-app --code-coverage
ng test --app second-app --code-coverage
不満があるとすれば、片方実行するたびにもう一方のカバレッジ結果に上書きされてしまう点。
この方法だとこのプロジェクト全体のカバレッジを見ることができないんですね。
プロジェクト全体のテストカバレッジを俯瞰したい!
というわけで構成をもう少しいじります。
src以下に全体ユニットテスト用ファイルを置く
全体ユニットテストって良く分かんない言葉ですね。でも他に思いつかないからそのまま書きます。
src以下に以下の3ファイルを置きましょう。
import "./first-app/polyfills";
import "./second-app/polyfills";
(デフォルトのtest.tsをそのままコピーしてくるだけ)
(デフォルトのtsconfig.spec.jsonをそのままコピーしてくるだけ)
.angular-cli.jsonに全体ユニットテスト用構成を追加する
apps
以下にこんなのを追加します。
"apps": [
{
"name": "testAll", // 分かりやすい名前にしましょう。
"root": "src/", // テスト用ファイルを置いたディレクトリをルートにする。
"main": "test.ts", // 実際使わないのでなんでもいい。"省略する"ことに対しては怒られるのでなんか書く。
"test": "test.ts", // さっき置いたファイル。
"testTsconfig": "tsconfig.spec.json", // さっき置いたファイル。
"polyfills": "polyfills.ts" // さっき置いたファイル。ブラウザ使ってテストするので、各アプリで必要なpolyfillを適用してやる必要がある。
},
...
僕はapps
以下の先頭にこいつを追加しました。
先頭にある場合は、ng test
(appオプションを省略)したときの動作がデフォルトでこの構成になります。
アプリ名を指定しなかった場合は全体テスト、指定した場合はアプリ個別のテスト、というように使い分けています。
あくまでこの構成はテストしか考えていないので、ng serve
やng build
(appオプションを省略)はどちらもエラーで通らなくなっています。
serve
やbuild
をしたいときは--app 1
や--app 2
を付けてあげましょう。(さっき書いてたインデックスとはずれてるので注意)
テストしてみる
おもむろにng test
してみましょう。全てのアプリに対してテストが実行されたと思います。
ng test --code-coverage
してやると、全てのアプリのテストカバレッジが取得できるようになりました。
めでたし。
おわりに
あ、サーバサイドのテスト入れるの忘れてた…。