Angular プロジェクトを新規作成したとき 生成されるファイルの役割を説明していく

サーバーサイドアプリケーションの動作確認やデモのために Angular を触っています。Angular は、 anglar-cli を用いることで、雛形の生成や開発に必要なファイルの追加といったことがコマンドラインから行えます。

「雛形を作ったときに一緒に生成されるファイルのことをよく知らないな」と思い、調べて記録することにしました。

バージョン情報

$ npm install -g angular-cli@latest
$ ng version

   _                      _                 ____ _     ___
  / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
 / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
/ ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
              |___/
@angular/cli: 1.2.0
node: 8.1.0
os: darwin x64
@angular/animations: 4.2.5
@angular/common: 4.2.5
@angular/compiler: 4.2.5
@angular/core: 4.2.5
@angular/forms: 4.2.5
@angular/http: 4.2.5
@angular/platform-browser: 4.2.5
@angular/platform-browser-dynamic: 4.2.5
@angular/router: 4.2.5
@angular/cli: 1.2.0
@angular/compiler-cli: 4.2.5
@angular/language-service: 4.2.5

対象とするプロジェクト

angular-gstream-app というプロジェクトを作ることにします。

$ ng new angular-gstream-app
$ tree -a -L 1
.
├── .angular-cli.json
├── .editorconfig
├── .gitignore
├── README.md
├── e2e
├── karma.conf.js
├── node_modules
├── package-lock.json
├── package.json
├── protractor.conf.js
├── src
├── tsconfig.json
└── tslint.json

e2e, protractor.conf.js

$ tree -a -L 1
.
├── .angular-cli.json
├── .editorconfig
├── .gitignore
├── README.md
├── e2e                 # directory
├── karma.conf.js
├── node_modules
├── package-lock.json
├── package.json
├── protractor.conf.js  # file
├── src
├── tsconfig.json
└── tslint.json

これらのファイルは Protractor という Angular 向け E2E Test のためのツールで利用します。 E2E Test とはエンドツーエンドテストのことで、ウェブアプリケーションのコンテキストでは「ブラウザを起動し、実際にユーザーが利用する状況と同等の操作を行った結果をテストする」を指すようです。Angular ではプロジェクトを作成した段階ですでに実行可能なようになっています。

  • e2e ディレクトリ
  • protractor.conf.js

e2eディレクトリ

まずは e2e ディレクトリの中をみます。

tree -a -L 1 e2e/
e2e/
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json

app.e2e-sepc.ts

  • app: app Component の
  • e2e-spec: E2E Test を実行する
  • ts: TypeScript ファイル

ということで、テストを実行したときに OK, NG 判定が行われるのはこのファイルです。新しいパターンのテストを追加しようと思ったら、このファイルを修正することになります。

app.po.ts

po とはどうやら Page Object を指すようです。つまりこのファイルは、ユーザーの操作をコードで表現することが役割です。ページ遷移やテキストの抽出、フォームへの入力など、ページでテストしたいアクションがあったら、まずはこのファイルを修正し、そのあと app.e2e-spec.ts ファイルを修正します。Page Object の考え方は Selenium ブラウザテストツールを使っている方は馴染みがあるかもしれません。

tsconfig.e2e.json

tsconfig.xxx.json が存在するディレクトリは、そのディレクトリが TypeScript プロジェクトであることを示します。ということは、この e2e ディレクトリを、アプリケーションとは別の TypeScript プロジェクトとして実行している人がどこかにいるということになります。

protractor.conf.js

e2e ディレクトリを TypeScript のプロジェクトとして実行するための設定が書かれていました。ファイルの一部を抜粋すると、

onPrepare() {
  require('ts-node').register({
    project: 'e2e/tsconfig.e2e.json'
  });
  jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}

このような記述があり、「 e2e/tsconfig.e2e.json をルートとする TypeScript プロジェクトを ts-node を使って実行せよ」という指示になります。後述しますが、 .angular-cli.json に 「E2E Test 実行のときに protractor.conf.js を使え」という記述があるため、ng e2e とコマンドを打つことで E2E Test が実行できるという理屈です。

ユースケース

E2E Test を実行する

$ ng e2e

...(中略)...

[15:03:46] I/launcher - Running 1 instances of WebDriver
[15:03:46] I/direct - Using ChromeDriver directly...
Jasmine started

 angular-gstream-app App
   ✓ should display welcome message

Executed 1 of 1 spec SUCCESS in 1 sec.
[15:03:50] I/launcher - 0 instance(s) of WebDriver still running
[15:03:50] I/launcher - chrome #01 passed

参考サイト

.angular-cli.json

.angular-cli.json は、 Angular のコマンドラインツール ng コマンドを実行する際に利用する設定ファイルです。スキーマ情報はこちらにあります。主な記述を抜粋すると以下です。

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "angular-gstream-app"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.css"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json"
    },
    {
      "project": "src/tsconfig.spec.json"
    },
    {
      "project": "e2e/tsconfig.e2e.json"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }
}
  • "e2e": E2E Test のために利用する設定ファイルを指定します。プロジェクト生成時には "./protractor.conf.js" が指定されています。
  • "test": Unit Test のために利用する設定ファイルを指定します。プロジェクト生成時には ./karma.conf.js" が指定されています。

参考サイト

.editorconfig

EditorConfig は、異なるIDE・エディタで共通して利用できる設定ファイルです。例えばインデントサイズや末尾空白を除去するといった設定を書けます。この設定ファイルを編集しリポジトリへ一緒にPushすることで、チームメンバー間のコードスタイルを維持することができます。私が普段利用している IntelliJ でも対応していました。

参考サイト

.karma.conf.js

Karma は、 Unit Test を実行するためのツールです。実行結果のレポートなど、たいていのツールはコンソールへ出力するところ、Karma はブラウザ上にレポートしてくれます。 ふつうは karma コマンドで Unit Test を実行しますが、 Angular プロジェクトでは、 .angular-cli.json に設定を書くことで ng コマンドから Karma を起動できます。

ユースケース

Unit Test を実行してブラウザ上でレポートを確認する

$ ng test
 10% building modules 1/1 modules 0 active05 07 2017 10:19:35.023:WARN [karma]: No captured browser, open http://localhost:9876/
05 07 2017 10:19:35.040:INFO [karma]: Karma v1.7.0 server started at http://0.0.0.0:9876/
05 07 2017 10:19:35.041:INFO [launcher]: Launching browser Chrome with unlimited concurrency
05 07 2017 10:19:35.049:INFO [launcher]: Starting browser Chrome
05 07 2017 10:19:44.638:WARN [karma]: No captured browser, open http://localhost:9876/
05 07 2017 10:19:44.871:INFO [Chrome 59.0.3071 (Mac OS X 10.11.6)]: Connected on socket BC8Gn7N2JRb61-WoAAAA with id 56513096
Chrome 59.0.3071 (Mac OS X 10.11.6): Executed 4 of 4 SUCCESS (0.308 secs / 0.276 secs)

これでブラウザが起動します。

karma

参考サイト

package.json, package-lock.json

node.js の package 管理ツールである npm が使います。package.json がある ディレクトリで npm install を実行すると package をインストールできます。

package-lock.json は、npm5 から導入されました。

$ npm -v
5.0.3

package-lock.json は、pakcage.json だけでは対処できない、開発時と本番リリース時でpackageのバージョンが異なる可能性がある という問題に対処するためのファイルです。package.json では、「パッチバージョンは最新のものをダウンロードする」「メジャーバージョンのみ固定し、あとは最新のものを使う」といった書き方が可能です(^2.0.0, ~2.1.4等)。わざわざ package.json を手で修正することなくコマンドひとつでアップデートできる一方、「開発時は v2.1.4 でテストしていたのに、リリース時になって v2.2.0 になり、これまで行ったテストが一部やりなおしになりそうだ…」といったシーンに遭遇します。package-lock.json は、依存 package 含め node_modules 以下にあるすべての package のバージョンを記録しています。開発者が明示的に npm update を実行しない限りは、package-lock.json も変更されず、どの環境でも npm install で導入される package は決まったバージョンになるというわけです。

package.json と同じように、package-lock.json もプロジェクトのリポジトリへ一緒に Push すると良いでしょう。

参考サイト

tsconfig.json

このプロジェクトが TypeScript プロジェクトであること示すと同時に、コンパイルターゲットやコンパイル時の設定を記載します。

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "src",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2016",
      "dom"
    ]
  }
}
  • compilerOptions.baseUrl: コンパイルターゲットのベースとなるディレクトリを指定します。ここではsrcです。
  • compilerOptions.target: コンパイル先の ECMAScript バージョンを指定します。ここではES5を指定しています。
  • compilerOptions.typeRoots: コンパイル時に利用する型情報として使うファイルのありかを指定します。 "node_modules/@types" なので、各 package に含まれる型情報を使う、ということになりそうですね。

参考

tslint.json

TypeScriptのための静的解析ツール TSLint の設定ファイルです。 ng コマンドでプロジェクトを生成した場合 tslint.json も一緒に生成してくれます。そして、.angular-cli.json によると、 ng lint で tslint.json を使った静的解析をかけてくれるようです。コミット時に静的解析がかけられるよう、git の pre-commit hook で ng lint を実行すると良いかもしれません。

$ ng lint

Warning: The 'no-use-before-declare' rule requires type checking

ERROR: /Users/wada.yusuke/.ghq/github.com/cm-wada-yusuke/developers-io-2017/angular-gstream-app/src/app/app.component.ts[9, 1]: Exceeds maximum line length of 140

Lint errors found in the listed files.

参考

おわりに

Angular だけでなく、多くのフレームワークでは、開発者が苦心することなくプロジェクトの雛形を作れるようになりました。効率が上がって便利な反面、自動生成されるファイルの意味を知っておかないと、いざ使うとなったときに正しい使い方ができなかったり、せっかく用意されているのに有効活用できずに開発を進めてしまうこともあります。今後もただ漫然と使っていくだけでなく、用意されたファイルや設定の意味を少しずつでも理解しながら開発していきたいと思います。