この記事は CAMPHOR- Advent Calendar 2016 23日目の記事です。
JS知識ほぼ0は言い過ぎかもしれないが、いわゆるモダンJSというものには縁遠く、つい最近まで jQuery をブラウザからぽちぽちダウンロードして適当に ajax とか使う人生を送ってまいりました。(当然フレームワークとか使ったことない)
まさしくこの記事みたいな状況
最近 TypeScript を書く機会があって、開発環境は用意されてるのでなんとなく書けるけど、エコシステムとかいろいろ全くわかってなくてこのまま旧石器時代然としたJavaScriptを書いていてはまずい気がすると思って勉強することにしました。
目標は TypeScript を使ってこんなんを作る、テストも書こうね(こんなのに何をテストするんだ)
この記事では主に環境構築~DOM操作のテストまでをやっていてTypeScriptの文法みたいな話はしません
- npm とか node のバージョンを管理
- npm でパッケージ管理をする
- TypeScript
- tsconfig.json
- 外部モジュールをTypeScriptから使う
- 実際にTypeScriptプログラムを書く
- CommonJS
- browserify
- gulp
- テスト
- npm script からテストなどを呼び出す
- 感想
- 参考
最終的なプロジェクトのディレクトリ構成はこんな感じ
. ├── gulpfile.js ├── karma.conf.js ├── package.json ├── src │ └── ts │ ├── index.ts │ ├── piyo.ts │ └── test │ ├── index.ts │ ├── piyo.html │ └── piyo.ts ├── static │ ├── html │ │ └── index.html │ └── js │ └── app.js ├── tsconfig.json └── typings.json
npm とか node のバージョンを管理
バージョン切り替えの必要はあるのかわからんけど一応...
nodebrew を使って、node や npm のバージョンを切り替える。他にもいろいろ同様のツールはあるみたいですね
nodebrew ls-remote # バージョンを眺めたり nodebrew ls # インストールされたバージョンを見たり nodebrew install-binary <version> # 指定バージョンのnodeをバイナリインストールしたり nodebrew use <version> # バージョン切り替え
npm でパッケージ管理をする
bower はもう古い、フロントだろうかバックエンドだろうか npm でパッケージ管理をするらしい
最近は Yarn とか良いらしいけどよくわからないので npm で...
npm install -g <package> # グローバルな環境にパッケージをインストール npm install <package> # だとローカル(カレントディレクトリの `node_modules` 以下?)
モジュールがダウンロードされる node_modules
はgit管理から外しておく
プロジェクトごとにパッケージ管理
プロジェクトルートに package.json
を配置
npm init
で対話的にpackage.json
がつくられるnpm install --save <package>
だとpackage.json
のdependencies
に書き込まれnpm install --save-dev <package>
だとdevDependencies
に書き込まれる。
開発ツールとかは --save-dev
でインストールしていく感じで便利
package.json
に書かれたモジュールたちは npm install
でインストール
TypeScript
TypeScriptはAltJSのひとつで、ES5(多分僕が昔なんとなしに書いていたもの)のsupersetとして設計されたもので、ES5+αな言語。
class
とかの ES2015 な要素もあるっぽい。
TypeScriptは全くの新言語、というわけではありません。ECMAScript 5のsupersetとして設計されているため、皆さんが慣れ親しんだJavaScriptの構文に少しの+αを加えた言語がTypeScriptです。ECMAScript 5のsupersetなのですが、設計当初よりクラスなどのECMAScript 2015の要素も含んでいました。ゆくゆくはECMAScript 2015のsupersetにもなっていくことでしょう。
TypeScriptを使ってECMAScript 2015時代のJavaScriptを先取りしよう! | HTML5Experts.jp
(babel
はES2015やES7をES5にコンパイルするツールなのね)
開発環境
npm install --save-dev typescript
$(npm bin)/tsc -v Version 2.0.10
tsconfig.json
TypeScriptをコンパイルするときにコンパイル対象のtsコードの指定とかコンパイルオブションを書く。
tsconfig.json
は tsc --init
で簡易的なものが得られる。上記のバージョンでは以下の tsconfig.json
ができた
{ "compilerOptions": { "module": "commonjs", "target": "es5", "noImplicitAny": false, "sourceMap": false } }
module: commonjs
: モジュールシステムとしてCommonJSを使うtarget: es5
: ES5をターゲット言語としてトランスパイルnoImplicitAny
: 暗黙的なAny型宣言をエラーにしてくれるsourceMap
: 生成したjsと元のtsを結びつけてくれる君、デバッグ時に便利
TypeScript の開発環境構築と周辺ツールの紹介 | HTML5Experts.jp
外部モジュールをTypeScriptから使う
npm install
した外部モジュールを使おうとするとトランスパイル時に怒られる!
型定義ファイルというものをインストールします。
nodeなどのために作られた外部モジュールの、メソッドなどの引数や返り値の型はTypeScriptからは分からないのでその型を実装とは別に記述してあげてやるもの
npm install --save-dev typings $(npm bin)/typings init # typings.json ができる
typings
はTypeScriptの型定義ファイルを管理するためのツール、昔はtsdとかいうのが使われてたらしい。
(この記事だと後に出てくる)assertionのためのchai
というモジュールを例にとってみる
npm install --save-dev chai $(npm bin)/typings search chai $(npm bin)/typings install --global --save-dev dt~chai
dt~*
というので DefinitelyTyped からダウンロードするということ--global
(かつての--ambient
) はdeclare hogehoge
というアンビエント宣言をインストールするときはオプションにつけないといけないみたい(このへんちょっと自信がない)
型定義ファイルは typings
以下に保存される、node_modules
と同じようにgit管理からは外すのが吉
今はなんとなくDefinitelyTypedから適当に型定義ファイルをインストールしてなんとなく使ってるけど、型定義ファイルについては こちらが詳しそう
実際にTypeScriptプログラムを書く
ちょっと分量多いのでgistに(別に読まなくてもOKです) index.html · GitHub
それじゃあこれらをjsにトランスパイルしてstatic/js
以下に生成物を出力しましょう
$(npm bin)/tsc --outDir static/js/ src/ts/*.ts
static/js/index.js
と static/js/piyo.js
ができましたが
// piyo.js var Piyo = (function () { // ... }()); exports.Piyo = Piyo;
// index.js var piyo_1 = require("./piyo"); // ...
このままじゃブラウザで動かせなさそうだな〜
CommonJS
export
module
とかでモジュールを作り、 require
でモジュールをインポートする、という約束事のモジュールシステムのことらしい
こういうモジュールシステムはブラウザ上では動きません、どうすんねん
browserify
CommonJSモジュールで記述されたJSをブラウザでも動くJSに変換してくれる君のようです
$(npm bin)/browserify src/static/js/index.js -o src/static/js/app.js
何だか激しいjsファイルが生成されました、これをHTMLからinclude したらOK、ワイワイ
gulp
これまでやったこと
- TypeScriptをCommonJSモジュールで記述されたJSに変換し
- CommonJSモジュールで記述されたJSをbrowserifyでブラウザからも読み込めるJSに変換
こういった一連の処理は自動化したいですね... 最近は webpackとかいうのが有名らしいけど今回はgulpとかいうので、 gulpはvinylというファイルを抽象化したもののストリームを扱うタスクランナーらしい。
gulp
とついでに vinyl-source-stream
と tsify
をインストールします。
tsify
: typescriptをコンパイルするためのbrowserifyのプラグイン、tsconfig.json
を読み込んでくれるvinyl-source-stream
: ファイルストリームをvynylに変換してくれる
npm install --save-dev gulp npm install --save-dev vinyl-source-stream npm install --save-dev tsify
プロジェクトルートに gulpfile.js
を
// gulpfile.js // gulp build-ts で、functionの処理が行われるように var gulp = require('gulp'); var browserify = require('browserify'); var source = require('vinyl-source-stream'); gulp.task('build-ts', function () { return browserify({ entries: './src/ts/index.ts' }) .plugin('tsify') // tsify プラグインを有効化 .bundle() // コンパイルしてファイルストリームを返す .pipe(source('app.js')) // ファイルストリームをvynylに変換して名前をつける .pipe(gulp.dest('./static/js')); // ./static/js 以下に書き込む });
$(npm bin)/gulp build-ts
これでブラウザでも動く static/js/app.js
が作られたぞ!
これをhtmlから読み込んだらワイワイ完成
テスト
JavaScriptだってテストしたい!
環境構築
- chai: should, expect, assertとか提供してくれる
- 同様のツールに
assert
power-assert
など
- 同様のツールに
- mocha: テストフレームワーク、describe, it とか実行コマンドとか
- 同様のツールに
jasmine
などがある - 基本的にNode.jsで動くテストが書ける
- 同様のツールに
- karma: DOM API を持つブラウザを介してテストできる君
- sinon: Mockとかさせてくれる君
今回はブラウザ上の挙動をテストしたい(ボタン押したらpiyoが追加されて欲しい)のでkarmaもインストール
npm install --save-dev chai npm install --save-dev mocha npm install --save-dev karma karma-browserify watchify # 前処理でtsをトランスパイル npm install --save-dev karma-mocha npm install --save-dev jsdom karma-jsdom-launcher # とりあえず実行環境としてjsdomを利用 npm install --save-dev karma-mocha-reporter # optional テスト出力結果をmochaっぽい感じに
$(npm bin)/typings install --global --save-dev dt~chai $(npm bin)/typings install --global --save-dev dt~mocha
karmaを走らせるにはkarma.conf.js
が必要、karma init
で対話的に生成する
$(npm bin)/karma init
いろいろ質問に答えると karma.conf.js
のスケルトンが手に入る
- 利用するフレームワーク
- テストプログラムの指定
- ブラウザ
- 前処理
などを設定する
// karma.conf.js frameworks: ['mocha', 'browserify'], files: [ 'src/ts/test/**/*.ts' ], preprocessors: { 'src/ts/test/**/*.ts': ['browserify'], }, // karma-mocha-reporter reporters: ['mocha'], browsers: ['jsdom'], browserify: { extensions: ['.ts'], plugin: ['tsify'] }
テストはとりあえずこんな感じで
// src/ts/test/index.ts import { assert } from 'chai'; import { getPiyo } from "../index"; describe('index', () => { describe('getPiyo', () => { it('should always return piyo', () => { const expect = 'piyo'; assert.equal(getPiyo(), expect); }); }); });
それでは実行
$(npm bin)/karma init
うんうん、karma-reporter-mocha
のおかげで小洒落たテストレポートを返してくれた
HTML断片を読んでDOM操作のテスト
piyo.ts Piyo
のようなDOM操作に対するテストを書いていきます。
class Piyo
に渡すべきテストのためのHTML断片を用意、それをkarma
の前処理でテストに読み込ませるために karma-html2js-preprocessor
を使う(document.body.innerHTML = '<div>...</div>'
でも良いけど大変)
npm install --save-dev karma-html2js-preprocessor
// karma.conf.js files: [ 'src/ts/test/**/*.ts', 'src/ts/test/**/*.html', ], preprocessors: { 'src/ts/test/**/*.ts': ['browserify'], 'src/ts/test/**/*.html': ['html2js'] },
これで __html__['path/to/test.html']
でhtmlを読み込めるように
<!--src/ts/test/piyo.html--> <div class="container"> <ul class="piyo-list"> <li><a href="" class="piyo-button">Add piyo</a></li> </ul> </div>
// src/ts/test/piyo.ts import { assert } from 'chai'; import { Piyo } from '../piyo'; describe('Piyo', () => { beforeEach(() => { document.body.innerHTML = __html__['src/ts/test/piyo.html']; }); it('can add piyo on click', () => { const view = new Piyo( <HTMLElement>document.querySelector('.container') ); const list = document.querySelector('.piyo-list'); const button = document.querySelector('.piyo-button'); assert.equal(list.children.length, 1, '最初は1件'); // ボタンをクリック const evt = document.createEvent("HTMLEvents"); evt.initEvent('click', true, true); button.dispatchEvent(evt); assert.equal(list.children.length, 2, 'ボタンクリックでappend'); assert.equal(list.children[1].textContent, 'piyo', 'リストの中身はpiyo'); }); });
$(npm bin)/karma start
npm script からテストなどを呼び出す
これまでは $(npm bin)/karma start
でテストを実行したり、$(npm bin)/gulp build-ts
でTypeScriptをビルドしたりしてたけど大変
npm scripts に登録してやると npm run なんちゃら
で実行できるようになるしパスが通るので $(npm bin)
とか書かなくてよい
package.json
に追記
"scripts": { "build": "gulp build-ts", "test": "karma start" },
感想
JavaScript界隈、特に勉強もせず側から見てるといろんなツールの名前が死ぬほどでてきて意味不明という印象だったけれど、ちゃんとした記事とかを見て勉強してみると各ツールの責任範囲が細かく分かれていて、その範囲の中でいくつかのツールが台頭しているっていうだけで思っていたほど混沌とはしていないなと思いました(もっと異常な状況だと思ってた)
とはいえ勉強することは多いな(設計、tsの型定義ファイルなど)と思ったのでじゃんじゃん勉強していきたい
参考
- 旧石器時代のJavaScriptを書いてる各位に告ぐ、現代的なJavaScript超入門 Section1 ~すぐにでも現代っぽく出来るワンポイントまとめ~ - Qiita
- TypeScriptを使ってECMAScript 2015時代のJavaScriptを先取りしよう! | HTML5Experts.jp
- TypeScript の開発環境構築と周辺ツールの紹介 | HTML5Experts.jp
- TypeScript の型定義ファイルと仲良くなろう - Hatena Developer Blog
- 一から始めるJavaScriptユニットテスト - Hatena Developer Blog
- gulp + browserify + tsifyを利用してTypeScriptコンパイル環境を作る - $shibayu36->blog;
- Karma, Mocha, Chaiを使ってTypeScriptでのテスト環境を構築する - $shibayu36->blog;
- TypeScriptの型定義管理ツールTypingsについて - Qiita
- npm で依存もタスクも一元化する - Qiita