Angular2の開発環境、俺ならこうやる 2016年7月25日版(2.0)

  • 71
    いいね
  • 0
    コメント

(追記)2.0に対応しました。(2016/09/15)

ここに書いていることは全て自己流のオレオレ環境なのであまり鵜呑みにしないでください。

今回のGitHubリポジトリ→ovrmrw/ovrmrw-ng2-env-20160725

まずは僕のマシン環境から。

  • Windows 10 64bit or 7 32bit
  • Visual Studio Code (stable) 1.3.1
  • Node.js 6.3.1
  • Git 2.8.1
  • GitHub Desktop

(Node.jsはnvm-windowsでインストールしています)

Angular2やるならこれぐらいは最低限インストールしておく必要があります。

WindowsにおけるNode.jsの環境構築はWindowsでnpm installの赤いエラーに悩まされているアナタへも合わせてどうぞ。

Node.jsの6系はnode-sassインストールのときにコケるので使わないというのが周辺の総意のようです。最新のnode-sassはv6に対応してるみたいです。

GitHubにリポジトリを作ってローカルにclone

ローカルで作ったものをGitHubにアップする方法は知らないので(!)、最初にGitHub上でリポジトリを作ります。

.gitignoreはNodeにして、licenseはMITにしておきます。

GitHubで作ったリポジトリを自分のPCにcloneしましょう。GitHub Desktopを使うと簡単です。

この段階で躓く方はGoogleで色々調べてください。

npm initとかnpm installとか

cloneしたフォルダに移動して、コマンドプロンプトを開きます。
まずはグローバルにいくつかインストールしておきましょう。

$ npm i -g typescript@rc firebase-tools tslint
  • typescript 2.0がインストールされます。
  • firebase-toolsはFirebase Hostingにアップロードするのに使います。
  • tslintはTypeScriptの構文が正しいかどうかをチェックしてくれます。

Firebase Hostingって何?→Firebase Hosting
要するにこのリポジトリをデプロイしてWebサイトとして動くようにする、ということです。

次にプロジェクトフォルダに色々インストールしていきます。

とりあえず最初は

$ npm init -y

そして

$ npm i -S @angular/core @angular/common @angular/compiler .....

入力しててめんどくさくなってきたのでpackage.jsonの"dependencies"と"devDependencies"を見てください。

本当にこんなに使うのかよ?!→使います。

$ npm i -D typescript@rc

typescriptはバージョン指定してインストールしています。typescript@rcのように@rcを付けないと1.8.1がインストールされます。

$ npm i -D @types/chai @types/empower .....

@typesで始まる彼らはtypescript 2.0時代の型定義ファイルです。typingsですか?彼の名前は忘れましょう。

tsconfig.jsonとtslint.jsonでコードを守る

typescriptやるならtsconfig.jsonの設定をまともに書けるようになることは必須スキルです。

$ tsc --init

これでルートディレクトリにtsconfig.jsonが生成されますね。これを僕ならこのように書き換えます。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2015",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": false,
    "sourceMap": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "strictNullChecks": true,
    "skipLibCheck": true,
    "outDir": ".dest-tsc",
    "lib": [
      "es2017",
      "dom"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}

これが自分のコードを守る砦です。いずれわかるでしょう。
tsconfig.jsonに限らずtypescript情報なら国内ではvvakameさんが一番詳しいかと思います。

$ tslint --init

これでtslint.jsonが生成されます。これも書き換えますが、mgechev/codelyzerを参考にしてtslint.jsonのようにします。
コードがちょっと長いのでリンクの掲載に留めました。

ここまで用意すると使えるようになる、package.jsonの"scripts"にある tscheck 、こいつは強力です。

$ npm run tscheck

これを走らせるとtscとtslintを続けて実行します。これで何のエラーも出なければとりあえずは(文法的には)問題なし、ここでエラーが出るなら早い段階で要修正!
こういうチェック機構のあるなしが沼に落ちるかどうかの命運を分けるのです。大げさですね。

.gitignoreの編集を忘れずに

Gitに含めたくない一時フォルダの指定を追加しておきましょう。忘れると悲惨です。

.gitignore
(前略)
# my settings
.dest*
.awcache*

.vscode/settings.jsonが必要

これはVSCodeに限った話かもしれませんが、現状はまだエディタがそのままだとtypescript 2.0に対応していません。
これを対応させるためにこういうファイルを作ります。

.vscode/settings.json
{
  "typescript.tsdk": "./node_modules/typescript/lib"
}

VSCode用語で言えばワークスペース設定というやつですね。
これでプロジェクトにインストールしたtypescriptをコンパイラとして使うようになります。

publicフォルダを作りindex.htmlとか色々置く

こんな感じです。→public

ビルド時にはこの中のファイルは.destフォルダにコピーされます。
それを制御しているのが

build/copy-files.js
const fs = require('fs-extra');

// 一時フォルダを削除する。
fs.removeSync('./.dest');

// publicフォルダにあるファイルを.destフォルダにコピーする。
fs.copy('./public', './.dest');

// srcフォルダにある『末尾が'.ts'か'.tsx'ではない』ファイルを.destフォルダにコピーする。
fs.copy('./src', './.dest/src', { filter: /^(?!.*\.ts(x|)$)/ }); // systemjs用

// polyfillを.destフォルダにコピーする。
fs.copy('./node_modules/babel-polyfill/dist/polyfill.min.js', './.dest/polyfill.min.js');

要らないフォルダを消したり、いくつかファイルをコピーしたりします。

srcフォルダを作りソースコードをまとめて置く

こんな風に。→src

これはAngular2公式のチュートリアルTour of Heroesの内容そのものです。手抜きです。

ここはもう各自で好きにやればいいところで、中身をどう書くかについてはチュートリアルをご覧ください。あるいはソースコードをお読みください。Angular2ハンズオンに足を運んでみるのもいいと思いますよ!

webpackの設定を書く

みんな大好きwebpack。バンドルしちゃえばいいじゃん。まだsystemjsで消耗しているの?(僕ほど消耗してる人もなかなかいないと思いますが)

設定ファイルは僕ならこう書きます。Angular2公式のWEBPACK: AN INTRODUCTION
が参考になるでしょう。

webpack.config.js
'use strict';

const webpack = require('webpack');


module.exports = {
  entry: {
    vendor: './config/bundle.vendor.ts',
    main: './src/main.ts',
  },
  output: {
    path: '.dest',
    filename: 'webpack.bundle.[name].js'
  },
  resolve: {
    extensions: ['', '.ts', '.js']
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' })
  ],
  module: {
    loaders: [
      {
        test: /\.ts$/,
        exclude: [/node_modules/],
        loaders: [
          'awesome-typescript-loader?tsconfig=config/tsconfig.bundle.json',
          'angular2-template-loader'
        ],
      },
      {
        test: /\.json$/,
        loader: "json-loader"
      },
      {
        test: /\.html$/,
        loader: "raw-loader"
      },
      {
        test: /\.css$/,
        loader: 'raw-loader'
      },
      {
        test: /\.scss$/,
        loaders: ['raw-loader', 'sass-loader']
      }
    ]
  },
  devtool: 'source-map',
};
config/tsconfig.bundle.json
{
  "compilerOptions": {
    "target": "es2015",
    "noImplicitAny": false,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictNullChecks": true,
    "skipLibCheck": true
  },
  "exclude": [
    "node_modules"
  ],
  "awesomeTypescriptLoaderOptions": {
    "useBabel": true,
    "babelOptions": {
      "presets": [
        "latest"
      ],
      "plugins": []
    },
    "useCache": true,
    "useWebpackText": true,
    "doTypeCheck": false
  }
}

全てを理解するには調べないといけないことがたくさんあると思います。
そこはまあがんばりましょう。
webpackの学習コストは少ないようで思ったより多い、というのが僕の感想です。

awesome-typescript-loader というローダーを使って、target: es2015 でts→jsコンパイルをした後にBabelでさらにコンパイルしています。
これのメリットはasync/awaitが書けるということと、Babelプラグインが使えるということですね。僕の場合はユニットテストでPower-assertを使うために babel-plugin-espower を使ったりしています。

configフォルダに設定ファイルを置く

フロントエンドに限らず色々と細かい設定ファイルが必要になります。それらをどこに置くかは難しい問題ですが、僕ならある程度はconfigフォルダを作ってそこに放り込みます。

こんな感じで。→config

ここにあるものは大体webpackから呼ばれるか、package.jsonのscriptsから呼ばれてると思います。

設定ファイルは職人芸ですのでがんばって身に付けましょう。

この辺でわからないことがあればそれぞれのライブラリのGitHubやnpmを調べてみると使い方とか書き方とか大体わかります。わかるまで調べよう。

※ルートにあった方がわかりやすいものはルートに置きます。

  • karma.conf.js
  • gulpfile.js
  • webpack.config.js
  • tsconfig.json
  • tslint.json

これらはルートにあると「あぁそれ使ってるのねフムフム」って見た人がぱっとわかるのであまり隠さない方がいいと思います。

npm start !!

ここまで出来たら

$ npm start

ですね。やっと動かせます。package.jsonの"scripts"を読んで npm start が連鎖的に何をやっているのか追ってみてください。
これぐらい読めないとガチ勢のscriptsは読めませんよ!

Firebase Hostingにデプロイ

下記の設定ファイルを自分の環境用に書き換えると簡単にFirebase Hostingにアップロードできるようになっています。

https://my-ng2-env-20160725.firebaseapp.com/は実際にこのリポジトリをデプロイしています。

.firebaserc
{
  "projects": {
    "default": "my-ng2-env-20160725"
  }
}

"my-ng2-env-20160725" のところを書き換える必要があるでしょう。

デプロイするコマンドは

$ npm run deploy

後はご自身で調べてみてください。「firebase hosting」でググると情報には困らないと思います。

Unit Testを書く

Angular2の花形、ユニットテストです。

(注意)rc.6ではkarmaのフレームワークをmochaにするとエラーになります。jasmineを使いましょう。

$ npm run karma
or
$ npm run karma:w

でテストが走ります。(:wはwatchモード)

Angular2のユニットテストは現状はとても大変な道のりなので少数の物好きだけがこの分野に特攻しているという状態ですね。ほんとにドキュメントが少ないです。というか無い。
juliemr/ng2-test-seedのコードを参考にしたりStackOverflowで地道に調べたりするのが近道です。

この辺りに必要なものは揃っています。
ちなみにテストコードは僕が書くとこんな感じです。

src-specs/app/app.component.spec.ts
/* >>> boilerplate */
import assert from 'power-assert';
import lodash from 'lodash';
import { inject, async, fakeAsync, tick, TestBed, ComponentFixture } from '@angular/core/testing';
import { setTimeoutPromise, elements, elementText, elementValue } from '../../test-ng2/testing.helper';
/* <<< boilerplate */

////////////////////////////////////////////////////////////////////////
// modules
import { AppComponent } from '../../src/app/app.component';
import { Observable } from 'rxjs/Rx';

////////////////////////////////////////////////////////////////////////
// mocks
const mockTemplate = `
  <h1>{{title}}</h1>
  <nav>
    <a>Dashboard</a>
    <a>Heroes</a>
  </nav>
`;

////////////////////////////////////////////////////////////////////////
// tests
describe('TEST: App Component', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent],
      providers: []
    });
  });


  it('can create, should have title', async(async () => { // 2つ目のasyncは async/await のasync。
    await TestBed
      .overrideComponent(AppComponent, { set: { template: mockTemplate } })
      .compileComponents();
    const fixture = TestBed.createComponent(AppComponent);
    assert(!!fixture);

    const el = fixture.debugElement.nativeElement as HTMLElement;
    const component = fixture.componentRef.instance;
    assert(elementText(el, 'nav a', 0) === 'Dashboard');
    assert(elementText(el, 'nav a', 1) === 'Heroes');
    assert(component.title === 'Tour of Heroes');
    assert(elementText(el, 'h1') === '');
    fixture.detectChanges();
    assert(elementText(el, 'h1') === 'Tour of Heroes');
  }));


  it('can create, should have title (fakeAsync ver.)', fakeAsync(() => {
    TestBed
      .overrideComponent(AppComponent, { set: { template: mockTemplate } })
      .compileComponents();
    tick();
    const fixture = TestBed.createComponent(AppComponent);
    assert(!!fixture);

    const el = fixture.debugElement.nativeElement as HTMLElement;
    const component = fixture.componentRef.instance;
    assert(elementText(el, 'nav a', 0) === 'Dashboard');
    assert(elementText(el, 'nav a', 1) === 'Heroes');
    assert(component.title === 'Tour of Heroes');
    assert(elementText(el, 'h1') === '');
    fixture.detectChanges();
    assert(elementText(el, 'h1') === 'Tour of Heroes');
  }));
});

rc.6以降はJasmine環境が前提になってしまったので、Mocha派は諦めてJasmineに転向しましょう。

RxJS Marble Testを書く

RxJSの花形(?)、マーブルテストです。

$ npm run testrxjs
or
$ npm run testrxjs:w

マーブルテストって何?こんな感じです。↓

test-rxjs/spec/sample1.ts
/* >>> boilerplate */
import { Observable, Subject, TestScheduler } from 'rxjs/Rx';
import assert from 'assert';
/* <<< boilerplate */


describe('TEST: rxjs5 basics', () => {
  /* >>> boilerplate */
  let ts: TestScheduler;
  let hot: typeof TestScheduler.prototype.createHotObservable;
  let cold: typeof TestScheduler.prototype.createColdObservable;

  beforeEach(() => {
    ts = new TestScheduler(assert.deepEqual);
    hot = ts.createHotObservable.bind(ts);
    cold = ts.createColdObservable.bind(ts);
  });
  /* <<< boilerplate */


  it('should return correct observable', () => {
    const source$ = cold<number>('-a-b-c', { a: 1, b: 2, c: 3 });
    const marbles = '---B-C';
    const values = { A: 10, B: 20, C: 30 };
    const test$ = mapFilterTest(source$);
    ts.expectObservable(test$).toBe(marbles, values);
    ts.flush();
  });

});


function mapFilterTest(observable: Observable<number>): Observable<number> {
  return observable
    .map(value => value * 10)
    .filter(value => value > 10);
}

何をやっているのかよくわからないかもしれません。
rxjsの式がちゃんと意図通りに動くかどうかをテストするのですが、これもまともなドキュメントは存在しません。あるのはWriting Marble TestsとかRxJS(5.x)で行うテストファーストな機能開発ぐらいです。

rxjsは独特の概念なので、それ単体で一つテストの仕組みがあった方がいい、というわけなんですね。

まとめ

ここまで駆け足でリポジトリの解説でした。

細かい説明はほぼ全て飛ばしています。StackOverflowとかで色々調べましょう。

いずれにしても自力でトラブルシュートしていくには現状ではそれなりの英語力が要求されます。フロントエンドエンジニアやるならまず英語からですよね。がんばりましょう。