Setting up minimum single page application

React.jsなどを試そうとするときに、browserify? gulp? grunt? webpack? どれ使えばいいのとか、テストは何を使えばいいのかとかよくわからないというのをたまに聞くので、最低限の設定だけどES6で書けたりautobuildやlivereloadが出来る構成のサンプルを作ってみました。

あと、テストはkarma + mocha + power-assertでchromeでテスト出来るようにしています。

本当はpackage.jsonだけにしたかったのですがkarmaの設定だけは別ファイルになってます…。 テストが必要ない場合は、package.jsonの指定だけで大丈夫です。

git cloneしてnpm installしてnpm startすればいいだけの設定になっています。

1
2
3
git clone git@github.com:koba04/minimum-spa-boilerplate.git
npm install
npm start

gif

概要

tree

1
2
3
4
5
6
7
8
9
10
➜  tree -L 1
.
├── README.md
├── index.js         // エントリーポイント
├── karma.conf.js    // karmaの設定
├── lib              // ソース
├── node_modules
├── package.json     // 設定はこの中
├── public           // document root
└── test             // テスト

package.json

基本的にはnpm initで作ったものに色々インストールしてnpm scriptの設定をしているだけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  "scripts": {
    "build": "NODE_ENV=production browserify index.js -t babelify | uglifyjs > public/bundle.js",
    "watch": "watchify -d index.js -t babelify -o public/bundle.js -v",
    "server": "browser-sync start --server public --files public/**/*",
    "test": "karma start",
    "start": "npm run watch & npm run server & npm test"
  },

  "dependencies": {
    "react": "^0.13.1"
  },
  "devDependencies": {
    "babelify": "^5.0.4",
    "browser-sync": "^2.3.1",
    "browserify": "^9.0.3",
    "espowerify": "^0.10.0",
    "karma": "^0.12.31",
    "karma-browserify": "^4.0.0",
    "karma-chrome-launcher": "^0.1.7",
    "karma-cli": "0.0.4",
    "karma-mocha": "^0.1.10",
    "mocha": "^2.2.1",
    "power-assert": "^0.10.2",
    "uglify-js": "^2.4.17",
    "watchify": "^2.4.0"
  }

https://github.com/koba04/minimum-spa-boilerplate/blob/master/package.json

  • npm run build

これはReactに依存したビルドになっているのですが、production用にビルドされたファイルを生成することを想定しています。

  • npm run watch

watchifyによってファイルが変更されたら自動的にbrowserifyのビルドが走るようになっています。 watchifyはキャッシュするので二回目以降のビルドが高速化されるのもポイントです。

  • npm run server

BrowserSyncによってpublicをrootにしたserverを立ちあげつつ、public以下のファイルを監視してlivereloadするようになっています。

  • npm test

テストはkarmaを使うのでkarma startしているだけです。karmaの設定はkarma.conf.jsにあります。

  • npm start

上記のweatchとbrowserとtestをまとめて実行するコマンドで、これだけ実行すれば開発を始められるようになっています。

karma

karmaはkarma initで対話的に作成出来るkarma.conf.jsにbrowserifyの設定を追加したくらいです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'browserify'],


// list of files / patterns to load in the browser
files: [
  'test/**/*.js'
],


// list of files to exclude
exclude: [
],


// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
  'test/**/*.js': 'browserify'
},

browserify: {
  debug: true,
  transform: [
    "babelify",
    "espowerify"
  ]
},

karmaについては以前にも書いたのでそちらも。

今回はカジュアルさを優先したので設定していませんが、travis-ciを使いたい場合は、karma-phantomjs-launcherを入れてnpm testでPhantomJSで実行出来るようにすればOKだと思います。

Code

コードはこんな感じで書けます。

1
2
3
4
5
6
7
import React from 'react';

export default class App extends React.Component {
  render() {
    return <div>Hello World</div>;
  }
}

https://github.com/koba04/minimum-spa-boilerplate/blob/master/lib/components/app.js

Test

テストはこんな感じ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import assert from 'power-assert';
import React from 'react/addons';
import App from '../../lib/components/app';

const {TestUtils} = React.addons;

describe("App", () => {
  let component;

  beforeEach(() => {
    component = TestUtils.renderIntoDocument(<App />);
  });

  it("returns Hello World in div", () => {
    const div = TestUtils.findRenderedDOMComponentWithTag(component, 'div');
    assert(
      React.findDOMNode(div).textContent
      ===
      'Hello World'
    );
  });
});

おまけ

karma-browserify + power-assert + babel

npm scriptでbrowserifyとwatchifyの引数にbabelifyを指定していますが、本当は↓のようにbrowserifyのtransformフィールドにだけ指定してkarmaにはespowerifyだけを指定したいところなのですが、karma-browserifyに指定するtransformはbrowserifyにoptionとしてそのまま渡されるのではなくて事前にtransformするので、espowerifybabelifyされる前のコードを対象としてしまいエラーになります…。

なのでここでは、それぞれに個別に設定する形になっています…。 レアケースですがなんとかしたいなぁと思いつつ…。

1
2
3
4
5
"browserify": {
  "transform": [
    ["babelify"]
  ]
}

そこそこカジュアルな感じになっているかなぁと思います。

Comments