だれも聞いていないと思って歌え

dance as if no one’s watching, sing as if no one’s listening, and live everyday as if it were your last.

Circle CI 2.0 へ移行してみた

爆速と噂の CircleCI 2.0 の β が取れたので、テストに時間が掛かっていたこともあり早速移行してみました。
結論を先に言うと、テスト時間が半分に短縮されました。

Circle CI とは

GitHub と連携させることで、リモートリポジトリへの Push を検知してテストの自動実行などを行う Web サービスです。
設定を行うことで、静的解析、自動ビルド、自動デプロイなども行うことができます。
この他にも Jenkins 、 Travis CI などの CI ツールがあります。

2.0 になることで何が変わったか

engineer.crowdworks.jp

上記記事から引用させていただきました。

カスタムビルドイメージ

1.0では多くの言語やツールがあらかじめインストールされたUbuntuイメージをCIのビルドイメージとして利用します。しかし、イメージに含まれていないものを利用する場合はCIの中でインストールする必要がありました。 2.0ではDockerイメージをベースにしてCIを実行できるようになりました。これにより、必要なライブラリなどをあらかじめセットアップしたDockerイメージを利用できるようになります。

ネイティブDockerのサポート

1.0でもDockerサポートは存在しています。しかし、CircleCI自身がベースコンテナとしてLXCを利用していることの影響で、独自パッチを当てた古いバージョンに限定されています。1 2.0では仮想マシンが起動して純粋なDockerを動かせるようになったため、最新の機能がフルに利用できるようになりました。CIのベースとしてこの仮想マシンを起動することも、Dockerイメージをベースにしつつ、途中でこの仮想マシンを起動することもできます。2

柔軟なジョブ構成

1.0では自動的にプロジェクトの種別を推測して、最適なセットアップとテストのコマンドが実行されます。CIの実行は明確なフェーズに分かれており、キャッシュをリストア・保存するタイミングも、コマンドを実行するタイミングも固定されています。実行コマンドをカスタマイズしたい場合はフェーズごとにコマンドを上書きしたり、フェーズの前後にコマンドを差し込んだりします。 2.0ではプロジェクト種別の推測はされず、決められたフェーズもなくなり、自分でステップを定義します。また、キャッシュも任意のタイミングで柔軟に行えるようになりました。

こちらの 柔軟なジョブ構成 の変更により、設定ファイルの書き方が大きく変わりました。

移行してみた

このあたりを参考にしました。

circleci.com

circleci.com

github.com

Circle CI 1.0 では、 PJ 直下にこのような circle.yml ファイルを置いていました。

machine:
  timezone: Asia/Tokyo
  node:
    version: 6.11.1
  post:
    - npm install -g npm@4
test:
  pre:
    - mkdir -p $CIRCLE_TEST_REPORTS/reports
    - eslint --config .eslintrc.yml src --format=junit --output-file $CIRCLE_TEST_REPORTS/reports/eslint.xml
  override:
    - karma start --reporters junit

Circle CI 2.0 では、 .circleci/config.yml へ設定を書くように変更になりました。

version: 2

jobs:
  build:
    docker:
      - image: node:6.11.1
    steps:
      - checkout
      - run:
          name: update npm
          command: |
            mkdir -p ~/tmp
            cd ~/tmp
            npm install npm@4
            rm -rf /usr/local/lib/node_modules
            mv node_modules /usr/local/lib/
            cd
      - restore_cache:
          key: dependency-cache-{{ checksum "package.json" }}
      - run: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package.json" }}
          paths:
            - ./node_modules
      - run:
          name: lint
          command: |
            mkdir -p /reports
            ./node_modules/.bin/eslint --config .eslintrc.yml src --format=junit --output-file ./reports/eslint.xml
      - run:
          name: test
          command: ./node_modules/.bin/karma start --grep ''
          environment:
            JUNIT_REPORT_PATH: ./reports
      - store_test_results:
          path: ./reports
      - store_artifacts:
          path: ./reports

変更点にもあった 最適なセットアップとテストのコマンド である checkout やテストの実行も package.json の設定を解析して自動的に実行していました。(上記の場合はレポートへの出力を行うため override していますが、出力しない場合は override は不要です)
しかし、それらを全て自分で定義する必要があるため、同じことをさせるだけでも設定内容は増えています。

Node.js のマルチバージョンビルドを行うときなどは、利便性はかなり向上しているように思います。

teppeis.hatenablog.com

躓いたところ

node.js と npm のバージョンの指定

サンプルのように Docker Image に circleci/node:latest と設定すると使用できる node.js のバージョンが v8 となります。
ただし、 v6 を使いたかったので circleci/node:6circleci/node:6.11.1 と設定するとエラーが発生。

#!/bin/bash -eo pipefail
$ npm install
module.js:471
    throw err;
    ^

Error: Cannot find module 'semver'
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/unsupported.js:2:14)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
Exited with code 1

次に、Docker Image を CicleCI が公開しているものではなく node が公開している node:6.11.1 を使用しても内容は違うけれどエラーが発生して npm install が失敗……

#!/bin/bash -eo pipefail
$ sudo npm install -g npm@4
/bin/bash: sudo: command not found
Exited with code 127

sudo が付いていたりいなかったりするのは、苦戦していた結果です。

なかなか原因と解決方法が分からず諦めかけていましたが、同僚も一緒に調べてくれてその結果からアドバイスをもらい npm のインストール方法を変更しました。

- run: 
    name: update npm
    commands: | 
      cd ~
      npm install npm@4
      rm -rf /usr/local/lib/node_modules
      mv node_modules /usr/local/lib/

自分では気付けなかったのですが、 npm の issue に対策とともに公開されていました。

github.com

レポート出力

1.0 では $CIRCLE_TEST_REPORTS という環境変数があらかじめ用意され、こちらを利用することにより Test Summary へ表示を行えるようになります。 ↓こっちでやりました。

38fanjia.hatenablog.com

ただし、 2.0 ではこの環境変数はなくなりました。出力先を自分で指定し、 reports と artifacts のパスを指定します。

- run:
    name: lint
    command: |
      mkdir -p /reports
      ./node_modules/.bin/eslint --config .eslintrc.yml src --format=junit --output-file ./reports/eslint.xml
- store_test_results:
    path: ./reports
- store_artifacts:
    path: ./reports

これで、これまでと同じようにエラー結果と結果のファイルを参照できるようになりました。

circleci.com

CircleCI の TestSummary に ESLint やテストの結果を表示するように設定してみた

レビュー時に ESLint でチェック可能な指摘を行い、それの修正をしてもらう・・・
そんな時間の手間を省いて、みんなが幸せになるために CircleCI で ESLint を実行するように設定してみました。

設定方法についてはこちらを参考にしました。

circleci.com

ESLint の結果を表示させる

circle.yml への設定追加のみでそのまま出来ました。

// circle.yml
test:
  pre:
    - mkdir -p $CIRCLE_TEST_REPORTS/reports
    - eslint spec src --format=junit --output-file $CIRCLE_TEST_REPORTS/reports/eslint-spec.xml

CIRCLE_TEST_REPORTS は CircleCI で使用可能な環境変数で、上記のように設定することで CircleCI の画面上から結果のファイルを確認できるようになります。

f:id:fanjia:20170706124226p:plain

ちなみにエラー時はこのように表示されます。

f:id:fanjia:20170706124238p:plain

テストの実行結果を表示させる

テスト対象が増え、エラーになった際にその内容を見るのにスクロールが非常に長くなってしまったので設定してみました。
ESLint の設定追加と比べてやることが多かったです。

今回は karma-junit-reporter を使用するため、パッケージに追加します。

npm install -D karma-junit-reporter

テストの設定を上書きし、 --reporters junit の設定を追加します。

// circle.yml
test:
  override:
    - karma start --grep '' --reporters junit

こちらも ESLint と同様に結果を CIRCLE_TEST_REPORTS の中に格納するようにします。
ただし、ローカル環境で実行すると CIRCLE_TEST_REPORTSundefined となるため、それに対応するため reportDir に出力先のパスを設定します。

// karma.conf.js
const reportDir = process.env.CIRCLE_TEST_REPORTS || '.';

reporters: ['junit'],

junitReporter: {
  outputDir: `${reportDir}/reports`,
  outputFile: 'test-results.xml',
  useBrowserName: false
},

結果ファイルは ESLint を設定した時と同様に確認ができます。

f:id:fanjia:20170706130009p:plain

ちなみにエラー時はこのように表示されます。
[more]をクリックすると、ローカルでテスト実行した時と同じ内容が表示されるため普段通りにエラー内容を確認できます。

f:id:fanjia:20170706130019p:plain

CircleCI で色々細かく設定できることが分かったので、またやりたいことが出来たらどんどん設定していきたい。

「Node女学園 1限目」に行ってきました

4月13日に G’s ACADEMY TOKYO BASE で開催された 「Node女学園 1限目」 に行ってきました。

nodejs.connpass.com

イベント概要

イベントページから引用させていただきました。

Node女学園はNode.jsに興味がある&学んでいる女性のためのコミュニティです。

今回は基礎を勉強&実践ハンズオン! 1限目の今回はみんなでNode.jsの基礎を勉強します! 「Node.jsのよさって何?」「ぶっちゃけ非同期処理とは」など、基礎から知りたい方一緒に勉強しましょう♩

参加動機

  • 前回 Node学園 へ参加した時と同様、 Node.js に力を入れていきたかったため。
  • 女性向けの勉強会であり、基礎の部分の話を他の方から(しかも会長から)していただく機会は滅多にないと思ったため。
続きを読む

「Node学園 24時限目」に行ってきました

まとめるのが遅くなってしまいましたが、 3月31日に株式会社FiNCで開催された 「Node学園 24時限目」 へ行ってきました。

nodejs.connpass.com

イベント概要

Node.js 日本ユーザーグループ主催による、 Node.js の勉強会です。 月に一度のペースで開催され、24回目の今日のテーマは 「 Node x WebAssembly 」 でした。

参加動機

  • Slack Bot を作ることで Node.js を初めて触り楽しかったため、今年は Node.js を頑張っていきたいと思っている。
  • Node学園という勉強会は知っていたが、今まで参加したことがなく参加して見たかった。
  • 先輩からこんな人がいると教えてもらっていた、こさまりさんのLTを聞ける貴重な機会だと思った。
  • WebAssembly 自体も最近よく耳にするため、どんなものなのか知りたいと思った。
  • Node.js 初心者のため、参加動機はふわっとしています……。
続きを読む

jsdom を 10.0.0 に上げた際のメモ

Node.js と jsdom は勉強中ですが、 10.0.0 リリース直後にアップデートを行って対応した際のメモです。

ちなみに現在の最新バージョンは、 11.0.0 です。
9.xx から 10.0.0 に上げた時のように大きな変更はないようで、アップデート後も今回記事にした内容から変更していません。

変更内容

  • API の変更(古い API も一部提供しているようですが、理由のない限り新しい API を使用することを推奨)
  • Node.js v6以降でサポートしている新しいJavaScript機能の使用を開始、 Node.js v4およびv5のサポートを削除
  • 空の文字列にinnerHTMLを設定しないように修正
  • 引数が足りない場合に表示される xhr.open() のエラーメッセージを改善

詳しい内容はこちらを確認してください。

github.com

jsdom.fromURL

URL を参照して dom を取得するように jsdom.env を使用していましたが、バージョンアップにより使用できなくなっていたため jsdom.fromURL を使用するように変更しました。

jsdom.fromURL とは

  • URL が有効で DOM の生成に成功した場合、非同期処理で JSDOM オブジェクトを返す
  • url と contentType をオプションで指定できない
  • userAgent オプションは、 HTTP User-Agent Request Header として使用する

サンプル

JSDOM.fromURL("https://example.com/", options).then(dom => {
  console.log(dom.serialize());
});

書き換えてみた

before

import jsdom from 'jsdom';

jsdom.env({
  url: url,
  userAgent: xxxxx,
  done: (error, window) => {
    if (!error && typeof window !== 'undefined') {
      // DOM 取得後の処理を実行する
    } else {
      // エラー処理を行う
    }
  }
});

after

import { JSDOM } from 'jsdom';

JSDOM.fromURL(url, {
  userAgent: xxxxx
}).then(dom => {
  if (typeof dom.window !== 'undefined') {
    // DOM 取得後処理を実行する
  } else {
    // DOM が正しく取得されなかった場合のエラー処理を行う
  }
}).catch(error => {
  // リクエストが通らなかった場合のエラー処理を行う
});

躓いたところなど

  • これまで window.document で参照していたが、 dom.window.document を参照するように変わった
  • import 文が import { JSDOM } from 'jsdom' と変更した(別の解決策があったかも知れませんが、 README を参考にこのように解決しました)
  • JSDOM.fromURL に catch が必要か分からなかった(念のために Promise の書き方に倣って追加しましたが、不要であれば削除しようと思っています)
  • then の中で setTimeout() を実行していましたが、これが原因で警告が表示されていたので削除した
    • jsdom.env で使用していた名残でしたが、今回の API の変更によりそもそも不要だった可能性もありそうです

setTimeout() に関してはもう少し理解を深めたいと思います。

Botkit で Slackbot を作ってみた

以前 Go 言語の勉強も兼ねて、 Go 言語で Slackbot を作りました!
が、動くものはちゃんと出来たけれど、 Go 言語そのものの理解が足りなかったため、メンテナンスもできずエラーを放置。どうしたものかと悩んでいる時に Node.js 製のフレームワークである Botkit を知り、作り直してみることにしました。

先に結論を書いておくと、 JavaScript の方が書き慣れていることもあって Botkit の方がずっと作りやすかったです!

Botkit とは

github.com

手順

手順を書いた記事は溢れているので、簡単にまとめました。

  1. こちら から Slack の Bot User を作成して Bot Token を取得する
  2. npm プロジェクトを作成し、 Botkit を npm からインストールする( 参考 )
  3. サンプルコード を基にコードを書く
  4. Heroku に Push する

サンプルコードから少し改良してみた

1. ES2016 を使いたい

Babel を dependencies にインストールします。

$ npm install -S babel-cli
$ npm install -S babel-preset-es2015

ビルドの手間を省くために Webpack を使わない代わりに、 Babel を読み込み、 bot を実装したファイルを呼び出すようにします。

こんな感じに実装しました。

// file: start.js
require('babel-register');
var Bot = require('./app/bot.js').default;

const bot = new Bot();
bot.run();
// file: app/bot.js
'use strict';

import Botkit from 'botkit';

export default class Bot {

  constructor() {}

  run() {
    // ...
  }
}

Bot の機能追加するのをもっと楽したい

社内で動いている、先輩が作った Bot が確かそのような仕組みになっていると聞いた覚えがあったので真似しようと思いました。

Botkit を作成した方が作った Botkit Starter Kit for Slack Bots でそういったことが実現されていたので、参考にしました。このリポジトリ自体は、 Botkit Studio というツールを介して使うもののようです。

// file: app/bot.js
const path = require('path').join(__dirname, 'skills');
require('fs').readdirSync(path).forEach(file => {
  const skill = require(`./skills/${file}`).default;
  new skill().hears(this.controller);
});

もっと良い書き方があるのかも知れないですが、 Node.js 初心者の今の私にはこれが精一杯。

やってみての感想

冒頭にも書きましたが、本当に簡単でした。 Go 言語では、未熟ゆえに Heroku へ Push する手順を毎回調べていたように思います。ただし、 Go 言語の方が色々と悩んだ分、 Bot の動かし方などを理解することができた気もします。

それから、 Botkit を使うことで初めて Node.js に触れ、知らないことがまだまだ多いことを知ったので Node.js を今年は学んでいきたいです。

作った Bot はこちらで公開しています。

github.com

  • 今後やりたいこと。
    • 色々な機能追加(天気予報など、何番煎じだろうととりあえずやっていきたい)
    • README をもう少しちゃんと書きたい。
    • TypeScript に置き換えたりしてみたい。

がんばろう。

「Vue.js Tokyo v-meetup="#3"」に行ってきました

勉強会の記事が続きますが、 3月16日にMicrosoftで開催された Vue.js Tokyo v-meetup=“#3” へ行ってきました。

vuejs-meetup.connpass.com

イベント概要

Vuejs 日本ユーザーグループ主催による、 Vue.js の Meetup イベントです。LT のテーマは、 Vue.js に関するものであれば何でも OK というもの。 イベント名から分かるように初心者向けではなかったです。

参加動機

  • Vue.js が最近話題なので、どんなフレームワークなのか知りたかった
  • 知人がこれから Vue.js に触れる予定だったので、一緒に参加した
  • 全体的に割とふわっとした参加動機です……
続きを読む