読者です 読者をやめる 読者になる 読者になる

ミルク色の記録

やったこと、やってみたこと

【NDSiN#12×Niigata.js×C4N】JavaScript 2017春に参加した

nds-meetup.connpass.com

久しぶりのNiigata.js絡みのイベントに参加して、「SPAのルーティング」の話をしてきた。

www.slideshare.net

SPAのルーティングに関してはあまり話に出てるの見たことがなくて(この前のWEB+DB PRESS 97号で初めて見たくらい)、みんな当たり前のようにやってる事なのかなーと思っていたけど、自分がSPAなアプリケーションとか、SPA用のルーティングライブラリ作った時に結構やらかしたり学んだりしたことがあったので題材にしてみた。

ホントは更にサーバサイドレンダリングと絡めて、ページロード時のサーバサイドのルーティングや初期化データ取得とのViewの生成、ページロード直後のフロントエンドの動きあたりのとこまで踏み込めると、また深みがあるのでそこまで行きたかったんだけど、まだ自分も模索中みたいなところが多すぎて無理だった(その辺りの良いやり方知りたい…)。

他の発表者さんの発表を聴いてて、みんな落ち着いて話してるなーという感じで、自分もっと場数踏まないとだなーと思った。

2016年にフロントエンド開発に使った環境

今年も終わるのでメモしておく。

去年との大きな違いはwebpackの設定を個別ファイルにするのをやめたくらい。 webpackの設定はgulpfile.js内で関数用意して、引数で対象の環境(development, production, testing)を切り替えるようにした。

こんな感じで使う。

他にはbabelのバージョンが上がったことに伴うあたりが変わったくらいで、そんなに大きな変化はなかったかな…と。

以下、package.jsonとか。

2016 development tool

SVGのtext要素でUnicodeのHTML文字参照を使う

意外なやり方だったのでメモしておく。

方法

次のようなsvg#screen1にHTML文字参照を含むコンテンツを持つtext要素を追加したい場合、

text要素をcreateElementNSで生成したらinnerHTMLでコンテンツをセットする。

これで"test@foo"が表示される。

見つけた経緯

レイアウトの調整のために、直接タグを書いていたときには問題なく表示できていた。

<svg width="400" height="20">
  <text x="0" y="16" fill="black">test&#x0040;foo</text>
</svg>

動的に描画するためにtextContentやcreateTextNodeを使ったら、"&"が"&amp;"になるので、"test&#x0040;foo"と表示されてしまった。

直接タグを書いていた時にはできていたので何かあるはずと思って、text要素のインスタンスのプロパティを直接見たらinnerHTMLがあった(SVGの要素なのに)。

気になること

W3Cとかでこのあたりの資料探してみたけどいまいち見つからない...

React.jsの場合

上記のことを踏まえて、dangerouslySetInnerHTMLを使えば実現できる。

確認したブラウザとバージョン

次のブラウザで表示できるのを確認した。

ブラウザ バージョン
Firefox 47.0
Google Chrome 52.0.2743.82
Safari 9.1.2

ソースコード

こちら: SVG Text · GitHub

YAP(achimon)C::Asia Hachioji 2016 mid in Shinagawaに行ってきた

すっかり時間が開いてしまったけど、7月の2日、3日にYAP(achimon)C::Asia Hachioji 2016 mid in Shinagawaへ行ってきたので残しておく。

感想

ベストトーク賞の上位に上がっていたトークはすべて会場で聴いていてどれもこれも良かったのだけど、特に@kamadangoさんの「2人で楽しくサービスやアプリを作る話」がすごく良かった。

そもそもトークの応募リストに上がったのを見た時から、ポッドキャストのdandy.fmでデザイン思考の話聴いてたこともあり、これは絶対聴きに行きたいやつだ...と思ってたので、当日会場で聴いて、デザイン思考のフローに沿ったポイントとか、「ものづくり」する上での大事なこと、ストレスにしないための工夫・気づかいなどなど、予想通り大満足だった。あと、始まる前の突発的な@uzullaさんと@yusukebeさんの平和なエゴの話も良かった。

自分がやりたいのはこういう「ものづくり」なんだよなーと改めて思って、それが自分はできているか?と考えると、遠い目になる...という感じではあったのだけど、個人的に方向性を探るきっかけみたいにはなったかも。

今回、「遠方だから絶対決まってないと死ぬ」枠で2日間申し込んだのだけど、実は申し込む前に「遠方扱いで申し込んでいいものか?」とか思ってちょっとためらった。申し込んで、後日にconnpass見なおしたら「※ 新潟は遠方です。」と追記されてて笑ったし、些細なことかもしれないけれども、嬉しかった。

楽しい2日間だった。

Niigata.js #1に参加して発表した話

(いろいろあって忘れてた)一ヶ月前の話だけど、記録のために書いておく。

3行でふりかえり

4月23日にNiigata.jsの第1回目で「Web Worker使って◯◯する」という話をしてきた。

話した内容は、Web Workerの基礎に見せかけたWeb Worker使ってFluxする話。

去年のYAPCの前夜祭で聴いた「質問があると嬉しいんですよ」と言う話がなんとなく解った。

Niigata.jsについて

新潟でJavaScriptにフォーカスした濃ーーいお話ができる場所が欲しかったので、すごく嬉しい。

ぜひ2回目もおねがいします...

Web WorkerでFlux

FluxとかReduxやっていて、ステートって単純にJSONとかでいいんだなーと思っていたら、以前、Backbone.js使っていた時に「ModelのレイヤーをWorkerの中に入れてプレゼンテーションレイヤーと分離してみたいなー、でもうまくできないなー」と思っていたやつが今度はできそうだと気づいたのがきっかけ。

資料は下。

www.slideshare.net

Universal(Isomorphic)な辺りを考えるとWorkerを直接使わずに、もうひとつ抽象化しないと行けないような気がするけど、どんなもんか。

発表してみて

他の参加者さんから質問とか反応をもらえて、なるほど。と思ったので、また、なにかネタができたら...。

2015年が終わるのでフロントエンド開発に使ってるツールまわりをざっと書き出してみた

題名の通り、ざっと書き出し。

このあたりのことを相談された時に、手元のリポジトリを漁って「こんな感じでやってます」とやってたけど、 とりあえずこのURL渡して参考にしてもらうみたいなことができれば...

ご注意

  • Xubuntu 14.04でやってるのでWindowsOSXは未確認。
  • こじんまりした規模の開発で使っている構成なので、規模によっては向いていないかも。
  • サーバとはJSONとかのAPIでお話するだけの、SPAなフロントエンド用(ルーティングはハッシュチェンジ系向け)。

おしながき

共通用

nodeの管理

とりあえずnodeがないと始まらないのでnodeから。

nodeの管理はnodebrewを使っている。 変なハマり方をしないように、v4.2.x系を使っている。

JavaScriptのLint

eslintを使っている。 Vimプラグインsyntasticを使って、ファイルの保存時にチェックしてる。

インストール

$ npm install -g eslint

次のようにVimに設定して有効にする。

let g:syntastic_javascript_checkers=['eslint']

プロジェクト用

プロジェクトディレクトリの下準備

npm initでpackage.jsonを作り、giboを使ってnode用の.gitignore設定を作っている。

$ mkdir project
$ cd project
$ npm init
$ gibo Node | tee .gitignore

スクランナー

スクランナーはgulpの3.9系を使っている。

gulpコマンドはプロジェクト配下にインストールしたものをnpmのコマンド経由で使う。 後々追加することになるgulpのプラグインはgulp-load-pluginsでまとめてロードできるようにしている。

HTMLやCSSJavaScriptなどのファイルはappディレクトリ配下に置いて行き、 各種ツールを通してビルド済みディレクトリ(dist)に配備されるようにする。

ビルド済みディレクトリはpackage.jsonに設定として書いておき、gulpfile.jsなどから参照して使っている。

ディレクトリ構成は次のような感じ。

.
├── app
├── dist
├── gulpfile.js
├── node_modules
└── package.json

インストール

$ npm install --save-dev gulp gulp-load-plugins del

開発時に使う共通的なgulpのタスクは次の通り。

タスク名 用途
clean ビルド済みディレクトリを削除する
dev 開発環境を起動する
default デフォルト(cleanしてdevを実行する)

gulpfile.js

var path = require('path');
var pkg = require('./package.json');
var distDir = path.join(__dirname, pkg.dist);
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var del = require('del');

gulp.task('clean', del.bind(null, [distDir]));

gulp.task('dev', [], () => {

});

gulp.task('default', ['clean'], () => {
  gulp.start('dev');
});

package.jsonにgulpコマンドとビルド済みディレクトリの設定を次のように追加している。

package.json

-- 中略 --
  "dist": "dist",
  "scripts": {
    "gulp": "gulp"
  },
-- 中略 --

ビルド済みディレクトリはgitignore対象。

.gitignore

dist
-- 中略 --

これでgulpはこんな感じに使う。

$ npm run gulp

HTMLと開発用Webサーバ

開発時にブラウザで確認するためのWebサーバはconnectを使っている。

Webサーバはビルド済みディレクトリをドキュメントルートとして動作させる。 gulpのwatchでファイル監視して、変更があったら必要なビルドをしてLiveReloadでブラウザを更新させる。

BrowserSyncをしばらく使っていたけど、 connectで十分な機能しか使ってなかったのでconnectに戻した。

インストール

$ npm install --save-dev connect connect-livereload serve-static gulp-livereload

HTMLファイルをappディレクトリに追加する。

app/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

HTMLと開発用Webサーバのためのgulpタスクは次の通り。

タスク名 用途
html HTMLファイルをビルド済みディレクトリへ配備する
serve 開発用Webサーバを起動する
dev 開発環境を起動し、HTMLファイルを監視する

gulpfile.js

-- 中略 --
var connect = require('connect');
var connectLivereload = require('connect-livereload');
var serveStatic = require('serve-static');
-- 中略 --
gulp.task('html', () => {
  return gulp.src('app/**/*.html')
  .pipe(gulp.dest(distDir))
  .pipe($.livereload());
});

gulp.task('serve', () => {
  $.livereload.listen();
  connect()
  .use(connectLivereload())
  .use(serveStatic(distDir))
  .listen(3100);
});

gulp.task('dev', ['html', 'serve'], () => {
  gulp.watch('app/**/*.html', ['html']);
});
-- 中略 --

これでgulpを起動して、開発用Webサーバを立ち上げてブラウザで確認できる。

$ npm run gulp

CSSや画像ファイルなどのスタイルまわり

CSSBootstrapをベースにlessで書いて、 gulp-lessでビルドしている。

インストール

$ npm install --save bootstrap
$ npm install --save-dev gulp-less

アプリ用にapp.lessファイルを作り、importでbootstrapのlessを読ませて必要なスタイルを書いていく。

Reactみたいなコンポーネント作って行く系のを使ってると、 コンポーネントごとにlessファイル作って管理とかしたほうがいいのかなーと思うけど、 Bootstrapに毛の生えた程度にしかスタイル追加してないのでapp.lessだけで今のところは済んでいる。

アイコン増やしたいみたいなときだけfont awesomeとか足してる。

app/styles/app.less

@import '../../node_modules/bootstrap/less/bootstrap.less';

body {
  background-color: #efffef;
}

HTMLにCSSの読み込みを追加。

app/index.html

-- 中略 --
<head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
  <link rel="stylesheet" href="./styles/app.css" />
</head>
-- 中略 --

スタイル周りのgulpタスクは次の通り。

タスク名 用途
styles:dev 開発環境用スタイルビルド(less:dev、fonts、imagesを実行する)
less:dev lessファイルをビルドする
fonts フォントリソースをビルド済みディレクトリへ配備する
images 画像リソースをビルド済みディレクトリへ配備する
dev 開発環境を起動し、HTMLファイル、lessファイルを監視する

gulpfile.js

-- 中略 --
gulp.task('styles:dev', ['less:dev', 'fonts', 'images']);

gulp.task('less:dev', () => {
  return gulp.src('app/styles/**/*.less')
  .pipe($.less({
    paths: [
      'node_modules/bootstrap/less'
    ]
  }))
  .pipe(gulp.dest(path.join(distDir, 'styles')))
  .pipe($.livereload());
});

gulp.task('fonts', () => {
  return gulp.src([
    'node_modules/bootstrap/fonts/*'
  ])
  .pipe(gulp.dest(path.join(distDir, 'fonts')));
});

gulp.task('images', () => {
  return gulp.src([
    'app/images/**/*'
  ])
  .pipe(gulp.dest(path.join(distDir, 'images')));
});
-- 中略 --
gulp.task('dev', ['html', 'styles:dev', 'serve'], () => {
  gulp.watch('app/**/*.html', ['html']);
  gulp.watch('app/styles/**/*.less', ['less:dev']);
});
-- 中略 --

JavaScriptまわり(モジュールバンドラ)

Babelを使ってES6で書いたコードをES5にトランスパイルしている。 モジュールバンドラにはwebpackを使っている。

webpackは多機能だけど、あくまでJavaScriptのモジュールバンドラとして使っているので、 必要に応じてbrowserifyに差し替えたりしている。

インストール

$ npm install --save-dev webpack babel-loader babel-preset-es2015 gulp-util

Babelの設定は.babelrcに書く。

.babelrc

{
  "presets": ["es2015"]
}

開発環境用のwebpack設定を作る。

エントリポイントのapp.jsを読み込んで、Babelを通してソースマップ付きでビルド済みディレクトリに配備している。

webpack.config.js

var path = require('path');
var pkg = require('./package.json');
var distDir = path.join(__dirname, pkg.dist);

module.exports = {
  entry: {
    app: './app/scripts/app.js'
  },
  output: {
    path: path.join(distDir, 'scripts'),
    filename: '[name].js'
  },
  devtool: 'inline-source-map',
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel'
      }
    ]
  }
};

JavaScript用のgulpタスクは次の通り。

タスク名 用途
bundle:dev JavaScriptの開発用ビルドを行う
dev 開発環境を起動し、HTMLファイル、lessファイル、JavaScriptファイルを監視する

gulpfile.js

-- 中略 --
var webpack = require('webpack');
var bundler = webpack(require('./webpack.config.js'));
-- 中略 --
gulp.task('bundle:dev', cb => {
  bundler.run((err, stats) => {
    if (err) {
      throw new $.util.PluginError('webpack:build', err);
    }
    $.util.log('[webpack:build]', stats.toString({
      colors: true,
      chunkModules: false
    }));
    cb();
    $.livereload.reload();
  });
});
-- 中略 --
gulp.task('dev', ['html', 'styles:dev', 'bundle:dev', 'serve'], () => {
  gulp.watch('app/**/*.html', ['html']);
  gulp.watch('app/styles/**/*.less', ['less:dev']);
  gulp.watch('app/scripts/**/*.js', ['bundle:dev']);
});
-- 中略 --

JavaScriptソースコードはapp/scripts配下に置いて行く。

app/scripts/Hoge.js

export default class Hoge {

  constructor(name) {
    this._name = name;
  }

  greet() {
    return `Hello! ${this._name}`;
  }
}

app.jsをエントリポイントとして、HTMLファイルに読み込ませる。

app/scripts/app.js

import Hoge from './Hoge';

const hoge = new Hoge('test');

console.log(hoge.greet());

app/index.html

-- 中略 --
<body>
  <h1>Hello World!</h1>
  <script src="./scripts/app.js"></script>
</body>
</html>

プロダクション用のビルド

開発用のビルド設定とは別にプロダクション用ビルド設定(ソースマップなし、ミニファイ化)も用意している。 ビルド済みリソースはzipで固めて、直接フロントエンドの開発をしないメンバーに配れるようにする。

インストール

$ npm install --save-dev gulp-zip gulp-minify-css

gulpのプロダクション用ビルドタスクは次の通り。

タスク名 用途
styles:prod プロダクション環境用のstyleビルド(less:prod、fonts、imagesを実行する)
less:prod lessファイルのプロダクション用ビルドを行う
bundle:prod JavaScriptのプロダクション用ビルドを行う
prod プロダクションビルドを起動する
build プロダクションビルド(cleanしてprodする)

less:devとless:prodの共通部分をless関数として切り出して、それぞれのタスクに利用する。 webpackのプロダクションビルド用設定を追加してbundle:prodではそちらを使うようにする。

gulpfile.js

-- 中略 --
function less() {
  return gulp.src('app/styles/**/*.less')
  .pipe($.less({
    paths: [
      'node_modules/bootstrap/less'
    ]
  }));
}
-- 中略 --
gulp.task('styles:prod', ['less:prod', 'fonts', 'images']);

gulp.task('less:dev', () => {
  return less()
  .pipe(gulp.dest(path.join(distDir, 'styles')))
  .pipe($.livereload());
});

gulp.task('less:prod', () => {
  return less()
  .pipe($.minifyCss())
  .pipe(gulp.dest(path.join(distDir, 'styles')))
});
-- 中略 --
gulp.task('bundle:prod', cb => {
  webpack(require('./webpack.production.config.js'), cb);
});
-- 中略 --
gulp.task('prod', ['html', 'styles:prod', 'bundle:prod'], () => {
  return gulp.src(path.join(distDir, '**/*'))
  .pipe($.zip(pkg.name + '.zip'))
  .pipe(gulp.dest('build'));
});

gulp.task('build', ['clean'], () => {
  gulp.start('prod');
});
-- 中略 --

webpackの設定を開発用とプロダクション用で分離する。両方の共通部分をcommon設定ファイルとして取り出す。

webpack.common.js

var path = require('path');
var pkg = require('./package.json');
var distDir = path.join(__dirname, pkg.dist);

module.exports = function() {
  return {
    entry: {
      app: './app/scripts/app.js'
    },
    output: {
      path: path.join(distDir, 'scripts'),
      filename: '[name].js'
    },
    module: {
      loaders: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'babel'
        }
      ]
    }
  };
};

開発用のwebpack設定は、共通設定を読み込んでソースマップを追加した設定にする。

webpack.config.js

var config = require('./webpack.common.js')();
config.devtool = 'inline-source-map';

module.exports = config;

プロダクション用のwebpack設定は、共通設定を読み込んでUglifyプラグインを追加した設定にする。

webpack.production.config.js

var config = require('./webpack.common.js')();
var webpack = require('webpack');
config.plugins = [
  new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false
    }
  })
];

module.exports = config;

buildディレクトリはgitignore対象。

.gitignore

dist
build
-- 中略 --

ビルド用コマンドをpackage.jsonに追加する。

package.json

-- 中略 --
  "scripts": {
    "gulp": "gulp",
    "build": "gulp build"
  },
-- 中略 --

プロダクション用ビルドはコマンドを叩いて行う。

$ npm run build

テスト環境

テストはmochapower-assertで書いている。 テストコードもES6で書きたいのでespower-babelを使っている。

インストール

$ npm install --save-dev mocha power-assert espower-babel

mochaの設定を入れておく。

test/mocha.opts

--compilers js:espower-babel/guess

テストコードはこんな感じ。

test/Hoge-test.js

import Hoge from '../app/scripts/Hoge';
import assert from 'power-assert';

describe('Hoge', () => {

  const name = 'Test';
  let hoge;
  beforeEach(() => {
    hoge = new Hoge(name);
  });

  describe('#greet', () => {
    it('returns greet message', () => {
      assert(hoge.greet() === `Hello! ${name}`);
    });
  });

});

テストを叩くためのnpmコマンドをpackage.jsonに追加する。

package.json

-- 中略 --
  "scripts": {
    "gulp": "gulp",
    "build": "gulp build",
    "mocha": "mocha",
    "test": "mocha test/*-test.js"
  },
-- 中略 --

テスト叩くときはこんな感じで一気に。Vimで編集のファイルとか個別に叩くときはnpm run mocha で。

$ npm test

APIサーバとつなぎながら開発

AjaxでバックエンドのAPIを使う場合、可能であればバックエンドのサーバを起動しておいて、 http-proxy-middlewareを使ってAPIへのリクエストだけリバースプロキシしている。

インストール

$ npm install --save-dev http-proxy-middleware

この場合、gulpのserveタスクに次のような修正を加える。 下の設定例だと/apiなパスへのリクエストはlocalhostの3000ポートの方へプロキシされる。

gulefile.js

-- 中略 --
var proxyMiddleware = require('http-proxy-middleware');
-- 中略 --
gulp.task('serve', () => {
  var port = process.env.API_PORT || 3000;

  $.livereload.listen();
  connect()
  .use(connectLivereload())
  .use(serveStatic(distDir))
  .use(proxyMiddleware([
    '/api'
  ], {
    target: 'http://localhost:' + port,
    changeOrigin: true
  }))
  .listen(3100);
});
-- 中略 --

fetchを使ってAPIを叩くサンプルが次のような感じ。

app/scripts/Hoge.js

export default class Hoge {

  constructor(name) {
    this._name = name;
  }

  greet() {
    return `Hello! ${this._name}`;
  }

  fetchGreet() {
    return fetch('/api/greeting')
    .then(res => res.json())
    .then(json => {
      return `${json.message} ${this._name}`;
    });
  }
}

app/scripts/app.js

import Hoge from './Hoge';

const hoge = new Hoge('test');

console.log(hoge.greet());

hoge.fetchGreet()
.then(message => {
  console.log(message);
});

余談:バックエンドサーバのAPIをモック

バックエンドサーバのAPI仕様がJSON Hyper-Schemaで定義されている場合 (下のfixture/api.jsonみたいな感じ)、雑にモックサーバを起動して代替にしている。

fixture/api.json

{
  "$schema": "http://interagent.github.io/interagent-hyper-schema",
  "type": [
    "object"
  ],
  "definitions": {
    "greeting": {
      "$schema": "http://json-schema.org/draft-04/hyper-schema",
      "title": "Greeting",
      "description": "ご挨拶API",
      "stability": "prototype",
      "strictProperties": true,
      "type": [
        "object"
      ],
      "definitions": {
        "message": {
          "description": "ご挨拶メッセージ",
          "example": "こんにちは",
          "type": [
            "string"
          ]
        }
      },
      "links": [
        {
          "description": "ご挨拶メッセージを取得する",
          "href": "/api/greeting",
          "method": "GET",
          "rel": "self",
          "title": "Info"
        }
      ],
      "properties": {
        "message": {
          "$ref": "#/definitions/greeting/definitions/message"
        }
      }
    }
  },
  "properties": {
    "greeting": {
      "$ref": "#/definitions/greeting"
    }
  }
}

インストール

$ npm install --save-dev json-schema-mockserve

この場合、gulpのserveタスクを次のように修正する。 環境変数MOCKに値が設定された場合、モックサーバを起動するようにしている。

gulpfile.js

-- 中略 --
var MockServe = require('json-schema-mockserve').MockServe;
-- 中略 --
gulp.task('serve', () => {
  var port = process.env.API_PORT || 3000;

  if (process.env.MOCK) {
    new MockServe({
      port: port,
      path: path.join(__dirname, 'fixture', 'api.json')
    }).start();
  }

  $.livereload.listen();
  connect()
  .use(connectLivereload())
  .use(serveStatic(distDir))
  .use(proxyMiddleware([
    '/api'
  ], {
    target: 'http://localhost:' + port,
    changeOrigin: true
  }))
  .listen(3100);
});
-- 中略 --

モックサーバで起動するコマンドをpackage.jsonに追加する。

package.json

-- 中略 --
  "scripts": {
    "gulp": "gulp",
    "gulp:mock": "MOCK=ON gulp",
    "build": "gulp build",
    "mocha": "mocha",
    "test": "mocha test/*-test.js"
  },
-- 中略 --

コマンドから起動する。

$ npm run gulp:mock

fetchをモックしてテスト

Ajaxなことは、Promiseベースのインターフェイスになっていることから、 XMLHttpRequestよりもfetchを使うようになった。 fetchを使うAPIをテストする場合、fetch-mockを使っている。

インストール

$ npm install --save-dev fetch-mock

テストコードは次のような感じに。 fetchMockのmockでモック設定して、afterEachでrestoreしてモック解除している。

test/Hoge-test.js

import Hoge from '../app/scripts/Hoge';
import assert from 'power-assert';
import fetchMock from 'fetch-mock';

describe('Hoge', () => {

  const name = 'Test';
  let hoge;
  beforeEach(() => {
    hoge = new Hoge(name);
  });
  afterEach(() => {
    fetchMock.restore();
  });

  describe('#greet', () => {
    it('returns greet message', () => {
      assert(hoge.greet() === `Hello! ${name}`);
    });
  });

  describe('#fetchGreet', () => {
    beforeEach(() => {
      fetchMock.mock('/api/greeting', 'GET', {
        status: 200,
        body: '{"message":"Hello Hello Hello..."}'
      });
    });
    it('returns greet message promise', () => {
      return hoge.fetchGreet()
      .then(message => {
        assert(message === `Hello Hello Hello... ${name}`);
      });
    });
  });

});

ブラウザでテスト

nodeの環境だけでなくブラウザ環境でもテストする場合はtestemを使っている。

webpackでJavaScriptコードをブラウザ用にビルドしてtestemに読ませてる。 このとき、power-assertとwebpackでjsonの読み込みを可能にしておかないと、 ビルド後のコードが動かないのでjson-loaderを追加しておく。

インストール

$ npm install --save-dev testem glob json-loader

テスト用のwebpack設定を追加する。 testディレクトリ配下のテスト用コードをglobでごそっと集めて変換する。

webpack.test.config.js

var path = require('path');
var glob = require('glob');

module.exports = {
  entry: {
    test: glob.sync(path.join(__dirname, 'test/**/*-test.js'))
  },
  output: {
    path: path.join(__dirname, '.powered-assert'),
    filename: '[name].js'
  },
  devtool: 'inline-source-map',
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel'
      },
      {
        test: /\.json$/,
        loader: 'json'
      }
    ]
  }
};

testemの設定をtestem.jsonに行う。 実行前にwebpackでテスト用ビルドを行い、実行後にテスト用ビルドディレクトリ(.powered-assert)ごと破棄する。

testem.json

{
  "framework": "mocha",
  "before_tests": "webpack --config webpack.test.config.js",
  "on_exit": "rm -rf .powered-assert/",
  "src_files": [
    ".powered-assert/**/*.js"
  ]
}

テスト用ビルドディレクトリもgitignore対象にする。

.gitignore

dist
build
.powered-assert/
-- 中略 --

testemの実行をコマンドとして追加しておく。

package.json

-- 中略 --
  "scripts": {
    "gulp": "gulp",
    "gulp:mock": "MOCK=ON gulp",
    "build": "gulp build",
    "mocha": "mocha",
    "testem": "testem",
    "test": "mocha test/*-test.js"
  },
-- 中略 --

testemを起動する場合は次のようにして行う。起動したらブラウザでアクセスする。

$ npm run testem

一気に行う場合はciオプションで実行する。

$ npm run testem ci

まとめ

一通りやったリポジトリこちら

必要なものを必要なときに必要なだけ取り入れつつ、全体的に薄めに取り替えが効くようにシンプルな構成で...というのが今のところの自分の方針。

勉強会に初めてLT参加した話

ちょっと時間開いてしまったけれど、記録のために書いておく。


NDS meetup #8nds-meetup.connpass.com
という勉強会で初めてLTやってみた。


自分は35歳の平凡なプログラマ
勉強会とかデベロッパー向けイベントに参加しても毎回聴くだけの側。


この夏YAPCに行った体験とか、日頃の尊敬するエンジニアさんたちのアドバイスとかでいろいろ思うことがあり、ミリ単位でいいから踏み出してみたいかな…とか思ったので、テーマが自分の好きなJavaScriptという事もあり初挑戦。


発表内容はECMAScript6で書けるようになったシンタックスについて。
普段コード書いていて「あ、コレってもっと簡単に書けるようになったよな…」みたいなのに絞った(クラスとかブロックスコープみたいな忘れにくいのはすっ飛ばした)。

www.slideshare.net

終わったあとに気づいたんだけど、ES6じゃないじゃんってのも混じってた...ごめんなさい。


今回の経験で劇的に何か変わったみたいなのはないけれど、またミリ単位で進んで行きたいかなーみたいな感じ。