技術書典 5 所感

2018年10月9日 0 イベント

2018/10/8 (月) に開催された技術書典 5 へ参加した。以下に所感をまとめる。

待機列から入場まで

会場となる池袋サンシャインシティ 2F 展示ホール D (文化会館ビル 2F) へ到着したのは 10:48。開場は 11:00 なので遅いほうだ。すでに待機列は建物に入り切らず、外まで続いている。

私が並んだのは豊島自動車練習場を見下ろすサンシャインシティ 2F の外階段あたり。最後尾に到着したというツイートを書いていたら、そのる間も人は増え続けて投稿時には後ろに 100 人ぐらい増えてた。その後も順調に増加し続ける。

前回の技術書典 4 では整理券が配られた。そのため公式の状況報告ツイートを確認しながらアキバ散策したのだけど、今回は整理券なしとのことなので一抹の不安が。

しかしこれは杞憂だった。待機列はモリモリ消化されてゆき 11:29 に入場。待ち時間としては 40 分ほど。あとで技術書典 5 関連のツイートを確認したところ開場してからは更に流れが速まったようだ。これぐらいの時間であれば夏冬でもなければ十分に待てるだろう。

待機列すごいツイートを見て参加を断念された方もいたようだが、整理券なしでもこんな感じで済んだので参考にしてほしい。

会場と通路

私は毎年ここで開催される JAGAT の page へ参加しているため事前に規模を知っていた。更に技術書典 5 のサークルリストを見ても配置に余裕のある感じだったので、入場制限が適切なら混雑度もさほどではないと予想していた。

実際に混雑はしたものの前回に比べればかなりスムーズに移動できた。島間の通路は 5 〜 6 人ぐらい横並びで余裕な感じ。おかげで通り過ぎた後によさそうな本を見つけたとしてもサッと戻って購入できた。

しかしこの広さを活かすならば道路のように中央線を引き、往路と復路を明示的に設けたほうがよかったのではないか。進行方向が決められていないため、各人の配慮による譲り合いでかろうじてトラブルを避けられていた印象。

途中でスタッフがサークルの客は端へ寄ってほしいとアナウンスしていたが、これも道路でいう歩道にあたるものがあれば案内しやすいだろう。

技術書典の参加者は私が見るにマナーはよかったので、公式なルールや案内さえあれば協力的なはず。運営側の負担も減らせそう。

QR 決済 (後払い)

技術書典は iOS/Android 向けアプリを提供しており、これを利用すると QR 認証で決済できる。支払いは後日 PayPal か Sprite でおこなう。私は本日 (2018/10/9) に案内がきたので PayPal で支払った。しめて ¥10,400 なり。

技術書典 4 は初参加ということもあり、下調べも不十分でアプリに気づいたのは会場だった。時たま QR コードをスキャンしている様子と QR 決済を推奨しているサークルを見かけて存在を知る。また現金の支払いでまごついたり千円札が不足して購入を諦めた本もあったので次こそ必ず QR 使おうと決めていた。

それを踏まえ今回は事前にアプリをインストールしておいたので積極的に QR 決済を利用した。14 サークルを巡り、うち QR 対応していない 1 サークルを除きすべてこれで決済できた。

QR 認証はスムーズで現金よりもずっと快適。財布の中身を意識せずに済むのも精神衛生上よい。購入の流れはこんな感じ。

  1. サークルへ購入の意思を伝える
  2. アプリから QR コードを読み取る
  3. 頒布物リストが表示されるので種類 (複数の本を扱っている場合) と冊数を選択して決定
  4. 認証後に決済されたことが表示される
  5. サークルに決済表示を見せて本を受け取る

課題はスマートフォンの出し入れ。会場では断続的に QR 決済するため、スマートフォンを持ちっぱなしにするか頻繁に出し入れすることになる。落下による破損などを考慮するとこれは実に危険だ。

もし対策するとしたら長めのネック ストラップがよさそう。首から下げたまま机上の QR を読めてサークル側に画面を見せられるぐらいの長さがあればよい。次回はそうする予定。

買い逃し

技術書典サイトにアカウント登録すると前述のアプリや Web サイトのサークル チェック リストを利用できる。しかし事前にチェックしたにも関わらず買い逃しが発生した。

会場ではサークルの島配置とリスト確認しながら回ったのだけど混雑によりサークル番号が見えなかったり人に流されて通り過ぎてしまうなど、この種のイベントに不慣れな私には厳しかった。疲労もあってリストを見るのが億劫になったのもある。結果としていくつか漏れた。

これはまったく私の落ち度だが、技術書典アプリの対応により改善できそうだとも思う。以下、構想 (妄想)。

まずアプリと Web サイトはアカウントを共有しているのだから、アプリ側にもサークル チェック リストを表示する。アプリには QR 決済のログが表示されるので、これもリストに連携。この組み合わせにより少なくとも購入候補の消化状況はリアル タイムに確認できる。

つぎに島配置。Web サイトや冊子案内のようなサークルの島配置をアプリに表示。QR 決済を利用するユーザーなら常時、または頻繁にスマートフォンを手にするだろうから、この情報も確認しやすいはず。これは効率的にサークルを回るため有用なはず。

島配置については島のタップで所属するサークルのリストをモーダル表示。これがチェック リストと QR 決済に連携され、チェックと決済 (購入) 状況をアイコン表示してくれたら更によし。決済状況については頒布物すべてを購入したら特別なアイコン (王冠とか?) になったりすると嬉しいかも。コレクション欲を刺激するのだ。

入手した頒布物

読むのに時間がかかりそうだから感想はあとで別記事に書く予定。

他にも気になった本は多数あるのだが、技術書典バッグの容量的に厳しくなったので見送った。買い逃しも含む。V8 本が落ちたということで東京ラビットハウスは通り過ぎてしまったのだけどサークル詳細みたら Universal JavaScript Framework 本を出してたようなので寄ればよかった。うかつ。

とはいえ電子版を BOOTH 販売しているサークルもあるので、肉体的な負担を低減するための制約としてバッグひとつ分というのを設けた。薄い本でも集まれば重い (昔の映画は古い、的な表現)。この記事を書いた後で更に BOOTH 購入することもあるだろう。

Babel 7 を試す

2018年9月11日 0 開発 , ,

2018/8/27 に Babel 7 がリリースされたので既存プロジェクトに導入してみたので変更や問題点などをまとめる。

参考資料とサンプル プロジェクトは以下。

Scoped Packages

Babel 公式の npm が Scoped Packages に変更された。scope は @babelbabel-XXXX@babel/XXXX という命名へ移行された。私のよく利用するものだと以下のようになる。

{
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "@babel/preset-react": "^7.0.0",
    "@babel/register": "^7.0.0"
  }
}

第三者の Babel preset/plugin について babel-XXXX 命名を維持させながら、公式 npm を明確に区別できるようになった。webpack など npm で第三者の拡張を許可しているものに、この方法が広まってほしい。

babel.config.js

これまで Babel に関する設定はプロジェクトの package.jsonbabel プロパティーで記述していた。しかしこんな議論があったのと、Babel の設定ファイルとして JavaScript サポートが追加されたので移行することにした。

ファイル名は babel.config.js。機能は Config Files で解説されている。移行後の内容は以下。

module.exports = (api) => {
  const presetEnv = [
    '@babel/preset-env',
    {
      targets: {
        electron: '2.0'
      }
    }
  ]

  return {
    presets: api.env('development') ? [
      presetEnv,
      '@babel/preset-react',
      'power-assert'
    ] : [
      presetEnv,
      '@babel/preset-react'
    ]
  }
}

新形式は JavaScript というか Node モジュールとして定義する。このファイル自体は Babel の対象にならないようなので export default ではなく module.exports としている。アロー関数は現時点の Node LTS である v8.x 系なら対応しているので採用。

モジュールとする関数には api という引数が渡される。これは命名そのままで Babel を操作したり環境情報を取得するためのもの。

api.env() を呼ぶと現在の環境変数名が得られる。api.env('環境変数名') とした場合は、それが指定されていれば true を返す。これを利用して development 時だけ power-assert を処理するようにした。

webpack の DefinePlugin が機能しない?

サンプル プロジェクトでは Babel と webpack を組み合わせている。そして Babel 7 対応は npm 更新と Babel 関連の設定変更だけでよい認識していた。実際、正常に動作する。

しかし babel.config.js 移行にともない webpack の DefinePlugin による development 分岐が本当に機能しているか不安だったので、ログをとってみた。

module.exports = (api) => {
  console.log(api.env())
  // ...後略
}

webpack の設定は以下。

new WebPack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify('production')
})

この状態で developmentproduction 両方のビルドを試してみたところ、標準出力には いずれも development が表示されるではないか。webpack の GitHub issues を調べてみたら #2121 を見つけた。issue コメントにあった設定例をみると JSON.stringify せず直に 'production' を設定していたので、そのように修正してみたが結果は変わらず。

気づいていなかっただけで従来の package.json における babel.env 分岐も失敗していた可能性がある。仮に power-assert が処理されても assert が置き換えられるぐらいだろうし、テスト コードは実装側から参照していないため害はなさそう。しかし意図しない動作になっているのは怖いので対策してみた。

方法は単純。webpack 処理で process.env.NODE_ENV を設定するだけ。webpack 処理はすでに --modedevelopmentproduction を分岐していたので

export default (env, argv) => {
  const MAIN = !!(env && env.main)
  const PROD = !!(argv.mode && argv.mode === 'production')
  if (PROD) {
    process.env.NODE_ENV = 'production'
  }
}

とした。これをそれぞれの --mode で実行したところ、babel.config.js 側の api.env() が想定どおりの値を返すことが確認できた。

ちなみに webpack の設定ファイルを webpack.config.babel.js にすると Babel 変換されるため babel.config.js が呼ばれる。しかし webpack に --mode production を指定してもこちらのログは当然ながら常に development だった。

環境変数を npm-scripts から分岐するために cross-env を利用しているなら、webpack CLI より前に production を設定できるかもしれない。ただし webpack を利用しているならビルド系の設定を webpack に集約するほうが管理しやすいのでそうしている。

mocha + power-assert

Babel の Scoped Packages 化にともない npm-scripts へ定義していた mocha + power-assert 実行コマンドも対応させる。まずは関連 npm を更新。

{
  "devDependencies": {
    "babel-preset-power-assert": "^3.0.0",
    "mocha": "^5.2.0",
    "power-assert": "^1.6.0"
  }
}

続けてコマンド。

{
  "scripts": {
    "test": "mocha --require @babel/register src/js/**/*.test.js",
  }
}

--require へ指定するモジュールを babel-register から @babel/register に変えるだけで正常に動作した。

まとめ

いくつか問題はあったものの Babel 公式資料が充実していることもあり、さほど苦労せず移行できた。他のプロジェクトも順次 Babel 7 にする予定。

破壊的な変更があるため、このブログも含む古い記事を読むと混乱するかもしれない。ソフトウェアに関する調べ物をするときに資料の日時と言及対象のバージョンを確認するのは鉄則。とはいえ利用する機会のありそうなものならメジャー更新は定点観測するようにしておくと、知識更新の差分を小さくできてよい感じ。

今回は試さなかったけれど Babel 7 の目玉機能に TypeScript 対応がある。私としては ECMAScript で型定義が標準化されることを望んでいる。しかし vscode で優遇されていることもあり TypeScript に移行したくなってきた。あとで試すかも。

npm rewire を試す

2018年7月18日 0 開発 ,

最近 React SFC (Stateless Functional Components) や Redux の影響を受けて、ES.next な環境でもクラスより関数で実装するようにしている。ES Modules であれ Node であれ個別に関数を外部公開できるからクラスを使用せずとも実装には十分だし、むしろ関数を積極的に採用することで外部依存を引数に限定できる。

しかし困ったことが。非公開の関数を単体テストする手段がない。

そもそも非公開なものをテスト対象とするのは悪手では?と言われればそのとおり。しかし公開関数が単一単純でも内部で多くの非公開な関数へ依存しているなら、それらを個別に単体テストしたくなるだろう。外部からみたら公開関数を単体テストしているつもりでも実際には結合テストなわけで。

クラスなら今のところアクセス指定子がないため、非公開メソッドは命名を工夫する慣習 (アンダースコアを接頭辞にする) があるだけだから

export default class Sample {
  publicMethod () {
  }

  _privateMethod () {
  }
}

と定義してあれば _privateMethod を呼び出せる。

一方、モジュール スコープに定義された非公開の関数を外部参照するためには Java や C# でいうところのリフレクション的な処理が必要になる。というわけで、ようやく rewire の話。

rewire

非公開な関数をテストする方法やツールについて調べたたら以下の記事をみつけた。

まさにほしかったもの。記事が書かれたのは 2013 年ということもあり ES2015 以降で動くのか不安だったが

を見ると現在もメンテされているようだ。では試してみよう。まずは環境から。package.json からテストに関するものを抜粋。

{
  "scripts": {
    "test": "mocha --timeout 50000 --require babel-register src/**/*.test.js"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-power-assert": "^2.0.0",
    "babel-register": "^6.26.0",
    "mocha": "^5.2.0",
    "power-assert": "^1.6.0",
    "rewire": "^4.0.1"
  }
}

この環境で shortcode.js というファイルに定義された以下の非公開 API をテストしてみる。

const trimLineBreak = (text) => {
  return text.replace(/^[\n]|[\n]$/g, '')
}

テスト コードは以下。

import assert from 'assert'
import Rewire from 'rewire'

describe('trimLineBreak', () => {
  const Module = Rewire('./shortcode.js')
  const trimLineBreak = Module.__get__('trimLineBreak')

  it('trimLineBreak', () => {
    let actual = trimLineBreak('\nText\n')
    assert(actual === 'Text')

    actual = trimLineBreak('\n\nText\n')
    assert(actual === '\nText')
  })
})

npm test を実行すると非公開にも関わらず trimLineBreak を呼び出しテストは成功。assert の引数を書き換えて意図的にテストを失敗させても、きちんと power-assert による失敗の詳細が表示された。

rewire の使い方について。まずは rewire を読み込む。

import Rewire from 'rewire'

次に非公開の関数が定義されているモジュールを rewire 経由で読み込む。

const Module = Rewire('./shortcode.js')

最後にモジュールから非公開 API の名前を指定して参照を得る。

const trimLineBreak = Module.__get__('trimLineBreak')

これで非公開 API を呼び出せる。なお関数だけでなく変数も読み込めるため、テストに非公開な定数が必要な場合でも rewire でいける。実に便利。

余談だがクラスの非公開メソッドについて。tc39/proposals によると 2018/7 時点で

が Stage 3 なので近いうちに事情が変わる可能性はある。これに対して rewire が効くのかは babel-preset-env にきたら改めて試すかも。