CentOS 7 系で Angular 2 開発環境を構築してみた

## はじめに
### AngularJS と Angular 2 について
AngularJS は Google 主導で開発されているフロントエンド用の JavaScript フレームワークです。  

GitHub の履歴を見ると [v1.0.0](https://github.com/angular/angular.js/releases/tag/v1.0.0) がリリースされたのは2012年で、
そこから改良が加えられ、アップデートされていたのですが、  
2015年に新しく Angular 2 の [2.0.0-beta.0](https://github.com/angular/angular/releases/tag/2.0.0-beta.0) が公開されました。

どちらのフレームワークも開発が進んでおり、
現行バージョンは AngularJS が [v1.5.8](https://github.com/angular/angular.js/releases/tag/v1.5.8) で、
Angular 2 が [2.0.0-rc.5](https://github.com/angular/angular/releases/tag/2.0.0-rc.5) です。

AngularJS と Angular 2 は、互換性 ( ほぼ ) 無しの別物フレームワークになってます。  
MVC からコンポーネント指向になったり、Syntax まるっきり変わってたり・・・

導入方法もかなり変わり、  
AngularJS では JavaScript を読み込むだけで使えましたが、  
Angular 2 では TypeScript が推奨になることでコンパイルが必要になり、  
開発環境を整えるのに一手間かかるようになりました。

ローカルの Windows や Mac で Angular 2 を動かす情報はすぐ見つかるのですが、  
CentOS などを使ったリモート環境で Angular 2 を動かす情報はあまりなかったので調べて構築してみました。

## 1. 準備したもの
- ssh コマンドが使える Windows 7 環境 ( mintty )
    - サーバー接続とサーバー上での作業に利用
- ブラウザ ( Firefox )
    - アプリの動作確認に利用
- CentOS 7 系の VM
    - Angular 2 の開発環境構築に利用
    - クラウドサービス側のファイアウォールにて特定の IP からしかアクセスできないように設定済み

### VM 情報

| 項目             | 内容                                                                                             |
|------------------|--------------------------------------------------------------------------------------------------|
| 利用したサービス | [GMOクラウドALTUS](https://www.gmocloud.com/) [Basicシリーズ](https://www.gmocloud.com/basic/)    |
| 仮想サーバー     | Mini Server                                                                                      |
| テンプレート     | CentOS 7.1.1503 64bit_20160223                                                                   |
| vCPU 数          | 1                                                                                                |
| CPU ( MHz )      | 2000                                                                                             |
| メモリ ( MB )     | 512                                                                                              |

### パッケージ情報

| 項目           | 内容                           |
|----------------|--------------------------------|
| gcc-c++.x86_64 | 4.8.5-4.el7                    |
| make.x86_64    | 1:3.82-21.el7                  |
| nodejs.x86_64  | 1:4.5.0-1nodesource.el7.centos |

### その他利用したツール情報

| 項目    | 内容    |
|---------|---------|
| mintty  | 2.3.5   |
| Firefox | 47.0.1  |

## 2. Angular 2 を利用するため、Node.js をインストールする
AngularJS では JavaScript ファイルを読みこめばそのまま利用できましたが、  
Angular 2 では Node.js が必要になるため、 CentOS にインストールします。

### 開発用のユーザーを追加
sudo を使えるユーザー追加を自分の環境にあった方法で適宜実行してください。  
この記事では、`clione` という開発用のユーザーを追加しています。 

### パッケージを更新
```
$ sudo yum update -y
```

### Node.js をインストール
```
$ curl --silent --location https://rpm.nodesource.com/setup_4.x | sudo bash -
$ sudo yum -y install nodejs gcc-c++ make
```

#### Node.js インストールでの躓きポイント
- Node.js を epel でインストールするとバージョンの関係で Angular 2 を動かせません ( 1敗 )
    - 2016年8月23日現在、 Angular 2 を動かすためには Node.js 4.x.x 系が必要です。
- Node.js 公式のインストール方法でインストールしたら上手く動きました
    - https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora

## 3. Angular 2 QuickStart でプロジェクトを用意
[Angular 2 公式の QuickStart](https://angular.io/docs/ts/latest/quickstart.html) を参考に、サーバー上に直接プロジェクトを作成し、アプリを動かします。

事前準備として作業用のディレクトリを作成しておきます。

```
$ mkdir ~/workspace
$ cd    ~/workspace
```

### Step 1 : プロジェクトの作成とパッケージ依存関係の定義
#### a. プロジェクトディレクトリの作成
```
$ mkdir angular2-quickstart
$ cd    angular2-quickstart
```

#### b. パッケージ定義ファイルと各種設定ファイルの追加
プロジェクトディレクトリにパッケージ定義ファイルと設定ファイルを追加します。

- package.json
    - QuickStart app で利用する npm パッケージを定義するファイル
- tsconfig.json
    - TypeScript コンパイラの設定ファイル
- typing.json
    - TypeScript 定義ファイル
- systemjs.config.js
    - SystemJS の設定ファイル

`package.json` 作成

```
$ vi package.json
```

`package.json` の内容

```package.json
{
  "name": "angular2-quickstart",
  "version": "1.0.0",
  "scripts": {
    "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
    "lite": "lite-server",
    "postinstall": "typings install",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "typings": "typings"
  },
  "license": "ISC",
  "dependencies": {
    "@angular/common": "2.0.0-rc.5",
    "@angular/compiler": "2.0.0-rc.5",
    "@angular/core": "2.0.0-rc.5",
    "@angular/forms": "0.3.0",
    "@angular/http": "2.0.0-rc.5",
    "@angular/platform-browser": "2.0.0-rc.5",
    "@angular/platform-browser-dynamic": "2.0.0-rc.5",
    "@angular/router": "3.0.0-rc.1",
    "@angular/router-deprecated": "2.0.0-rc.2",
    "@angular/upgrade": "2.0.0-rc.5",
    "systemjs": "0.19.27",
    "core-js": "^2.4.0",
    "reflect-metadata": "^0.1.3",
    "rxjs": "5.0.0-beta.6",
    "zone.js": "^0.6.12",
    "angular2-in-memory-web-api": "0.0.15",
    "bootstrap": "^3.3.6"
  },
  "devDependencies": {
    "concurrently": "^2.0.0",
    "lite-server": "^2.2.0",
    "typescript": "^1.8.10",
    "typings":"^1.0.4"
  }
}
```

`tsconfig.json` を作成

```
$ vi tsconfig.json
```

`tsconfig.json` の内容

```tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  }
}
```

`typings.json` を作成

```
$ vi typings.json
```

`typings.json` の内容

```typings.json
{
  "globalDependencies": {
    "core-js": "registry:dt/core-js#0.0.0+20160602141332",
    "jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
    "node": "registry:dt/node#6.0.0+20160807145350"
  }
}
```

`systemjs.config.js` を作成

```
$ vi systemjs.config.js
```

`systemjs.config.js` の内容

```systemjs.config.js
/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function(global) {
  // map tells the System loader where to look for things
  var map = {
    'app':                        'app', // 'dist',
    '@angular':                   'node_modules/@angular',
    'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
    'rxjs':                       'node_modules/rxjs'
  };
  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main: 'main.js',  defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' },
    'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
  };
  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'forms',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'router-deprecated',
    'upgrade',
  ];
  // Individual files (~300 requests):
  function packIndex(pkgName) {
    packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
  }
  // Bundled (~40 requests):
  function packUmd(pkgName) {
    packages['@angular/'+pkgName] = { main: 'bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
  }
  // Most environments should use UMD; some (Karma) need the individual index files
  var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
  // Add package entries for angular packages
  ngPackageNames.forEach(setPackageConfig);
  var config = {
    map: map,
    packages: packages
  };
  System.config(config);
})(this);
```

#### c. パッケージインストール
`package.json` に記述したパッケージをインストールします。

```
$ npm install
```

### Step 2 : 最初の Angular component 
`app` というサブディレクトリを作成します。

```
$ mkdir app
```

`app/app.component.ts` というコンポーネントファイルを作成します。

```
$ vi app/app.component.ts
```

`app/app.component.ts` の内容

```app/app.component.ts
import { Component } from '@angular/core';
@Component({
  selector: 'my-app',
  template: '<h1>My First Angular 2 App</h1>'
})
export class AppComponent { }
```


### Step 3 : Angular モジュールの作成
`app/app.module.ts` というモジュールファイルを追加します。

```
$ vi app/app.module.ts
```

`app/app.module.ts` の内容

```app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
```

### Step 4 : main.ts の追加、ルートコンポーネントの識別
`app/main.ts` というメインファイルを追加します。

```
$ vi app/main.ts
```

`app/main.ts` の中身

```app/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
```

### Step 5 : index.html の追加、アプリをホストする Web ページ
`index.html` を追加します。

```
$ vi index.html
```

`index.html` の中身

```index.html
<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="styles.css">
    <!-- 1. Load libraries -->
     <!-- Polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <!-- 2. Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>
  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>
```

`index.html` で呼び出される `styles.css` を作成

```
$ vi styles.css
```

`styles.css` の中身

```styles.css
/* Master Styles */
h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}
h2, h3 {
  color: #444;
  font-family: Arial, Helvetica, sans-serif;
  font-weight: lighter;
}
body {
  margin: 2em;
}
```



### Step 6 : アプリのビルドと起動
アプリのビルドと起動を行います。

```
$ npm start
```

出力例

```
> angular2-quickstart@1.0.0 start /root/angular2-quickstart
> tsc && concurrently "npm run tsc:w" "npm run lite"

[1]
[1] > angular2-quickstart@1.0.0 lite /root/angular2-quickstart
[1] > lite-server
[1]
[0]
[0] > angular2-quickstart@1.0.0 tsc:w /root/angular2-quickstart
[0] > tsc -w
[0]
[0] 3:23:37 PM - Compilation complete. Watching for file changes.
[1] Did not detect a `bs-config.json` or `bs-config.js` override file. Using lite-server defaults...
[1] ** browser-sync config **
[1] { injectChanges: false,
[1]   files: [ './**/*.{html,htm,css,js}' ],
[1]   watchOptions: { ignored: 'node_modules' },
[1]   server: { baseDir: './', middleware: [ [Function], [Function] ] } }
[1] [BS] Access URLs:
[1]  -------------------------------------
[1]        Local: http://localhost:3000
[1]     External: http://172.xxx.xxx.xxx:3000
[1]  -------------------------------------
[1]           UI: http://localhost:3001
[1]  UI External: http://172.xxx.xxx.xxx:3001
[1]  -------------------------------------
[1] [BS] Serving files from: ./
[1] [BS] Watching files...

```

3000 ポートで Angular 2 アプリ、 3001 ポートで Browsersync ( テスト、デバッグ用のツール ) が立ち上がります。

## 4. 動作確認
### Firewall の設定を確認
Firewall が動いているか確認します。

```
$ firewall-cmd --state
```

出力例

```
running
```

Firewall で有効になっているゾーンを確認します。

```
$ firewall-cmd --get-active-zones
```

出力例

```
public
  interfaces: eth0
```

### ポート開放
Angular 2 と Browsersync で利用する 3000, 3001 ポートを開放します。

zone は `firewall-cmd --get-active-zones` で確認したものを指定します。( 今回は `public` )

```
$ sudo firewall-cmd --zone=public --add-port=3000/tcp --add-port=3001/tcp
※ このポート開放設定を永続的にするには --parmanent オプションを追加する必要があります
```

出力例

```
[sudo] password for clione:
success
```

ポート開放できているかコマンドで確認します。

```
$ firewall-cmd --list-ports
```

出力例

```
3000/tcp 3001/tcp
```

**注意点**  
3001 ポートはテスト・デバッグ用ツールで利用するポートのため、アプリへのアクセスのみ利用する場合は 3000 ポートだけを開放してください。  
今回は特定の IP からしかアクセスできないようにクラウドサービス側のファイアウォールを別途設定し、 3001 ポートも開放していますが、自分の利用目的と環境に合わせて適宜対応してください。

#### アプリへのアクセスでの躓きポイント
- ファイアウォールの確認と、ポート開放を忘れずに。 ( 2敗 )
- クラウドサービスで作成した VM の場合、OS のファイアウォールとクラウドサービス側のファイアウォール ( セキュリティグループ ) の2つを確認しないと行けない場合があるので要注意です。
- アクセス失敗例
    - ![2016-08-26_17h22_56.png](https://qiita-image-store.s3.amazonaws.com/0/138637/f52f0ddd-c950-4da4-0952-934889a672c0.png)

### アクセスしてみる
- VM に付与しているグローバル IP のポート 3000 にアクセスしてみます。
    - ![2016-08-26_17h26_22.png](https://qiita-image-store.s3.amazonaws.com/0/138637/2b95ab00-9680-b6a9-d3e0-dd448aba4781.png)

- 同じようにポート 3001 にもアクセスしてみます。
    - ![2016-08-29_15h34_16.png](https://qiita-image-store.s3.amazonaws.com/0/138637/82099b3f-c8ac-1e94-a299-99901615c052.png)

無事、動作を確認できました！！

## おわりに
あまり手間取らず Angular 2 を動かすことができて良かったです。  

### クラウド上に開発環境を構築して使う便利さ
クラウドなどのリモート環境で開発は、
最初は不便さしか無いと思っていたのですが、
使っているうちに以下のようなメリットもあると感じてきました。

- ローカル環境がごちゃごちゃしない
- サーバーを複数人で共有できる
- ローカルマシンのリソースを節約できる
- 必要に応じてサーバーのスペックをコントロールできる

以上のメリットを考えるに、  
ローカル環境を汚したくない、ローカルマシンのスペックが低い・・・といった問題を抱えている方は  
今回のようなリモート環境での開発環境構築が一考の価値ありかと思います。

## 参考
- https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora
- https://www.angularjs.org/
- https://github.com/angular/angular.js/
- https://github.com/angular/angular/
- https://angular.io/docs/ts/latest/quickstart.html
- https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Using_Firewalls.html#sec-Configuring_the_Firewall
- https://www.browsersync.io/