redmine.tokyo 14 で登壇してみた

2018年5月28日 0 イベント ,

2018/5/26 (土) に redmine.tokyo 14 へ参加 & 登壇。以下、レポっす。

開場前

  • 最寄りは都営浅草線の泉岳寺駅だが、自宅からのルートだと乗り換えが面倒なので京浜東北線の田町駅へ
  • 田町駅で昼食、「いろり庵きらく」の「板そば」を頼んだらボリュームかなり多くて食べきれるか心配だったので飲み込む感じで処理
  • 国際興業三田第 2 ビル、休日なので通用口から入るとのことだが案内してくれる方がいたので迷わず入れた
  • 会場には 12:30 ぐらいに到着、着席
  • 開始までの時間に持参 PC のプロジェクター接続確認してほしいとのこと
  • ここでケーブル忘れに気づく、というか会場にあるのを期待する他力本願メソッドにも程があって、つまりは迷惑かけてしまったスミマセン
  • MacBook Air 2011 だと DisplayPort が必要、会場の方から借りて事なきを得る
  • ケーブルを貸していただいた方の顔を思い出せず @akipii 氏に託したのだが、その後どうなったのだろうか?
  • もしケーブルが戻られていない場合は補填・弁償したいので申し訳ないのですが @akabekobeko まで連絡ください
  • 会場、けっこう埋まっている
  • メイン会場はキャパ 60 人ほど、サブ会場は YouTube 配信をみる感じらしい
  • 会場のスクリーンは 2 種類、スライドと Twitter の #redmineT を映しているもの
  • ハッシュのほうは反応がリアルタイムに更新されてゆくので見てて飽きない

Redmine をちょっと便利に!プログラミング無しで使ってみる REST API

  • プログラミング無しで使ってみるREST API
  • ファーエンドテクノロジー株式会社 前田剛氏 @g_maeda
  • Redmine コミッターとして有名な方
  • curl、jq コマンドを利用して Redmine REST API を操作する
  • Redmine のデータへ Web UI を経由せずにアクセスするための手段
  • HTTP リクエストすると XML or JSON を返す
  • 例えば GET /users/1.json で user ID 1 のユーザー情報を JSON で得られる

REST API だと嬉しいこと

  • 他のシステムから Redmine を操作できる
  • 自動化、チャットボットなど
  • Web UI 操作では難しいことも実現できる

ツール例

REST API 利用準備

  • コマンドラインから API を利用
  • 今回は JSON にします
  • OS は Ubuntu か macOS を想定
  • Redmine の管理画面から「RESTによるWebサービスを有効にする」を ON にすることで許可
  • jq というツールを用意、sudo apt-get install jqbrew install jq で入手
  • アクセスできるかテストしてみる、Terminal に JSON がでれば OK

API で遊んでみる

  • ユーザー情報一覧の取得
  • curl で REST API を呼び、返された JSON を jq で整形する
  • ユーザー情報一覧を CSV に変換してみる、これも jq でいける
  • Redmine にはユーザー情報一覧のエクスポート機能がないため、このテスト時点で Redmine 以上のことを実行できていることになります
  • 応用でユーザーの一括登録もおこなえます
  • チケットの題名を正規表現で探してみる、Redmine では OR 検索できないが、正規表現ならなんでもあり

REST API 活用 TIPS

  • URL の拡張子で XML or JSON が決まる
  • コマンドラインから利用するなら処理しやすい JSON がオススメ、なにより jq が便利
  • 一部のオブジェクトはアクセスに Redmine システム管理者の権限が必要
  • デバッグには curl の -v オプションが便利、HTTP 通信内容を出力できる
  • というか -v つけないとエラー情報すらでない

質疑応答

  • Q. REST API でできて Redmine の Web UI の提供していない操作があるのはなぜ?
    • Web UI の方に手が回ってないだけです
  • Q. REST API、ドキュメントだとベータ版となっている箇所があったりするけど大丈夫?
    • ベータなどのステータスが更新されてないだけです
    • 十分にテストされている (Redmne では自動テストを重視している) ので実用上は問題ないと思われます

Redmine と他システムの連動事例

  • 資料みつけられず
  • 山崎進氏
  • 与えよさらば与えられん
  • 受けるより与えるほうが幸いである
  • 聖書にある言葉、OSS 活動の精神と考えている
  • 今日の登壇もその動機から

MS-Project から Redmine への情報移行

  • MS-Project を XML 出力
  • github に公開されている msproject-import というプラグインで Redmine にインポート

Slack と Redmine の情報移行

  • Redmine から Slack へ
    • アプリを追加する設定で incmming webhook を選択
    • Web Hook 用 URL が表示されるのでメモ
    • Redmine 側、github に公開されている redmine-slack というプラグインをインストール
    • プラグイン画面の入力欄にメモしておいた URL を指定
    • 例えばチケット登録すると Slack に通知がくる
  • Slack から Redmine へ
    • outgoing webhook を選択
    • REST API キーを入力する
    • Slack 側からチケット操作したりできる

Excel から Redmine へ

  • プラグインを利用
  • Excel 風の編集機能は Handsontable を利用
  • jQuery 経由とのこと、Redmine は標準で jQuery がバンドルされている
  • 例えば Redmine 上に非表示のテキストボックスを作成、テーブルに入力されるとテキストボックスに自動反映して Rdmine に Submit するような仕組みを使っている
  • カンマ区切りの文字列が送信されるので、サーバー側で解析して DB 反映

なじむ Redmine

  • なじむ Redmine
  • 私の登壇
  • YouTube 配信してるとのことなので YouTuber 風に「登壇してみた」とか「どーもアカベコです。今日は生放送されてるのでさしずめナマベコですかね?」みたいな感じで始める計画だったのだが…
  • akipii 氏による紹介で「アベコベさん?」と読み間違えられ、そちらのほうが面白かったので計画は破棄
  • #redmineT の書き込みとタイムキーパー用のチャイムが誤爆して何度も鳴るのがかなり気になる
  • 緊張し過ぎて時計みる余裕なかった、結果としてすこし時間オーバー

あとで #redmineT みたらよい反応が多くてホッとした。みなさん優しいですね。

LT

Redmine でデザインの強度作業 on リモートワーク

  • Redmineでデザインの共同作業onリモートワーク
  • 財部香織氏 @kaorabe
  • デザイン業務を Redmine でタスク管理している
  • ワークフロー説明、ブログ記事の場合
  • テキスト入稿、画像ラフ、Illustrator
  • 勘所
    • なるべく早く見られるもので PDCA 回すた
    • チケットにスケッチなどを添付しまくる
    • 注記で感謝を伝えることでコミュニケーションの潤滑油とする
  • …ここでチャイム
  • Redmine 同人誌出版を予定しているとのこと

途中でみえたチケット画面いい感じ。注記の区切りが吹き出しになっている箇所に惹かれる。自作テーマだろうか?

Redmine 同人誌は技術書展に出すのを目標という話をしていた。ちょうど先月、技術書展 4 に参加して刺激を受けたのと、社として関わり始めた XMLパブリッシング準研究会で 6/30 あたりに技術書展のサークル側の人と交流会を実施予定なので私としても興味ぶかい話である。

個人的には次の技術書展で Electron 本でもどうか?と考えていたのだが、この Redmine 本で参加するのも面白そうだ。私が書くとしたら minimalflat2 開発で得たテーマ開発まわりの話かな。

効果的な Redmine 導入のヒント

すごくよさそうな内容なのであとで資料を読む。

しかし 5 分の量ではない (22 ページある)。よって飛ばしまくり、しゃべりもあわせて高速化。大変そうだけどその様子が面白すぎた。時間操作系の能力者的ななにか。

LT 5 分ならスライド 10 ページ以内じゃないと厳しそう。昔、だれかに聞いた話では 1 ページ 1 分ぐらいで計算するとよいそうだ。高橋メソッドみたいな例外は除いて。

Redmine で契約を追え!

  • Redmineで契約を追え!
  • かるね氏 @nekosanz1
  • CI と向き合う事務員の戦い
  • インフラ屋の CI
  • 継続的… ではなく Configuretion Item のこと
  • 契約資料をだいじに
  • 契約のはじまりと終了
  • お客様と仕入先とかならず締結するもの
  • すべての物品はなんらかの契約書に結びつく
  • 難しいところ、数の暴力、契約数が多過ぎる
  • 時間経過による内容変化
  • ツール使えば管理できそうだけど?
    • 買ってもらえなかった…
    • なので Redmine で
  • チケット管理単位の話
  • チケットと体制
  • 1 年まわしてみて契約が迷子になることがなくなった
  • 機器や物品にチケット番号 = 契約番号を貼って管理、生きたチケット

チケット番号を物理的に貼るというのは面白い発想。Redmine が十分に浸透していればチケット番号は外部システムや現実社会でも資料へのポインターとして機能する。

Redmine 連携機能ソリューション紹介

  • スマートなテスト管理クラウド Qualityforward
  • syoshikawa0319 氏
  • QualityForward の話
  • 株式会社ベリサーブ
  • 効率的なテスト ツールを探している
  • BTS はレポートのみとおもっている
  • Quality Forword と Redmine 連携してもっと高度に管理
  • めちゃくちゃ飛ばす
  • Excel ライクな入力、集計
  • 効果など

Redmine として弱い部分をうまく補完している感じでよい。Redmine に絡めたビジネスが成立しているのだとしたら Redmine としても好ましいはず。

まったくの余談だが、大昔に在籍していたプロジェクトでベリサーブ社に検証を依頼していたのだが、やりとりのメールの宛名部分を「ベリサーブ殿」としていたのを見たメンバーの誰かが「ベリサーブでん」と読み、その音のインパクトからしばらく「〜殿」を「〜でん」と読むのが流行ったのを思い出した。

この話を懇親会でしようかと思っていたが機会なかったので、ここに記す。いい話でもなんでもないけれど。

プロジェクト管理ソフトの群雄割拠をどう勝ち抜くか?

  • 資料みつけられず
  • 岩崎成記氏
  • Redmine、MS-Project、Excel、…etc
  • これらの比較用にプロジェクト管理を定量化してみた
  • 趣味は見える化、すごい
  • 状態変化モデルなど、きちんと計測、グラフ化してて納得感ある

実データと理詰めで Redmine のメリットを見える化している点に感心。

VIEW CUSTOMIZE から REST API を使用する

  • View CustomizeからREST APIを使用する
  • もりのあさ氏 @forenoonM
  • 寝坊しました、11 時におきてびっくりしました
  • 転職して Redmine から JIRA になった
  • いろいろ困ってるけど「ぜんぶおれがなんとかする」所存らしい
  • RedmineのJavaScriptからREST APIを使用するの実践編
  • View Customize (JavaScript) から REST API を呼び出す
  • API キーはどうする?
  • 個人設定ページの内容を読み込めばよいのでは?で解決
    • これはダイアログと sesionStorage で解決できそう
    • グループ ディスカッションで一緒になったのでこれを振ったら、当初はそうしようとしたがセッションの引き回しの関係でうまく動かなかったとのこと
  • サンプル、小チケットのコメント追加時に親チケットにもコピーする

スライド中の Redmine スクリーンショットを見るに minimalflat2 を利用しているようで嬉しい。

私は Redmine プラグインに懐疑的で、それはプラグイン自体ではなく配布と互換性を担保する標準の仕組みが提供されていないためである。ただしそれを今から構築するのは難しいだろう。よって、

不具合が起きても Redmine 本体をクラッシュさせることがなく、近年の機能強化が著しい JavaScript、つまり Redmine テーマ側で補完してゆくほうがよいのでは?と考えている。

例えば minimalflat2 ではプロジェクト一覧を開閉可能なメニュー化している。これは Web フロントエンド完結しているが、REST API を利用して Redmine 本体も操作するのはどうか。

本日もこのスライド以外に REST API で Redmine Web UI にない操作をする演目があった。URL を判定すれば画面を特定できるので、例えばガントチャート画面を WBS 風に編集できるとかも画面判定して REST API でチケット期限更新するとかで実現可能なのでは。

という感じのことをこの演目を聞いて考えていた。

redmine git work in progress プラグインの紹介

リポジトリー側の操作がチケットに影響するというコンセプトが面白い。標準でも特定のコミット コメントに応じた処理 (例に挙げられている refs とか) はあるが、この分野は可能性を感じるのでもっと実験されてほしい。

Redmine とスマートスピーカーを連携させてみた

  • Redmine とスマートスピーカー連携させてみた
  • Kohei Nakamura 氏 @netazone
  • スマートスピーカーはいいぞ
  • もってる人は挙手…数人
  • もってない人は拍手…万雷の拍手
  • チケット登録めんどくさい、スピーカーに話しかけるだけでチケット登録はどうか?
  • 実演、会場の反応が熱い
  • OK, Google、チケットを… で登録とか更新する、すごい
  • 仕組みは IFTTTGoogle Assistant、Redmine 連携
  • デバイス、IFTTT、Redmine の順に処理

プログラムわからなくても、これらの要素技術で手軽にこういうのができる時代みたいな話をしていたが、まさしくそうだし、この流れは加速してゆくだろう。

最前列に置かれた猫型?のオブジェが気になっていたのだけど、これがスマート スピーカーだった。ハードウェアが絡み、しかもわかりやすいデモなので会場の受けがよい。スマート スピーカーが持つ印象面の効果は無視できない強さがある。

余談だが @nekosanz1 と @netazone は字面が似ている。

パネル ディスカッション

  • Git と Redmine をどのように連携するのが効果的か
  • 司会は松谷秀久氏 @mattani
  • パネラーは楠川智久氏、川端光義氏、NAITOH Jun 氏 @naitoh

Redmine と GitLab の連携利用

  • Redmineとgitの 連携利用事例
  • 楠川智久氏 @tkusukawa
  • Redmine プラグイン WorkTimeWikiLists などを公開してます
  • 自分たちの Git 連携方法についてお話するので、それについて意見や議論したいです
  • システム的には Redmine サーバーから GitLab の Bare リポジトリーを参照
  • Git リポジトリー運用は master で開発して std と production リポジトリーでリリースする形式

Redmine 設定については私の会社と一緒。ウチは Gitflow 風に master でリリース、開発は develop ブランチという感じで運用してる。feature も develop から生やす。

Redmine と GitHub のうまい関係

  • RedmineとGitHubをゆるく使う
  • 株式会社アジャイルウェア 川端光義氏 @agilekawabata
  • アジャイルウェアのコード管理は GitHub
  • レビュー機能が重要、GitHub の PR はそこが優秀
  • エンジニアのための最適なレビュー プロセス
    • 品質向上
    • 集中できる環境
    • エンジニア育成
  • Redmine と連携しづらい
    • Redmine は同一サーバーに Bare リポジトリが必要
  • アジャイルウェアでは
    • マネージメント管理に Redmine
    • 開発は GitHub
  • Lychee Redmine と GitHub 連携

ディスカッション

  • 松谷氏は Git 未経験、SVN と Redmine 連携は利用している
  • SVN に比べて Git のよさってなんですか?
    • 楠川 もう SVN には戻れない、merge が楽、branch 切るのが楽、分散・集約については重視していない
    • 内藤 Redmine 上で PR したい、これを SVN & レビュー プラグインでおこなうとコミットされた後になってしまう、GitHub のように merge 前に PR でレビューしたい
    • 川端 エンジニアは他の人に迷惑かけたくない、SVN だと push しかないのだが Git だと細かく branch 切って他に影響しないように作業をすすめて必要におうじて merge するスタイルなのでエンジニアにとってよい
  • Redmine リポジトリー連携としてどうか、川端さんはおこなっていない (GitHub 管理)、楠川さんはおこなっている
    • 楠川 逆に我々は PR を使っていないのでそのメリットが理解できていない、そうではない部分としてチケットと Git コミットが関連づいているのはよい
    • 川端 エンジニアの開発とコミュニケーションが GitHub 完結しているので困っていない、ただしマネージメントの Redmine との動機はとれていない、齟齬は Slack などで解決、PR に Redmine チケットのリンクをはることはある
  • 対象的な事例だが、GitHub/GitLab と Redmine の使い分け、棲み分けについて聞きたい
    • 楠川 内藤さんのところはどうですか?
    • 内藤 プライベートは GitHub、仕事では Redimine だがローカルで git-svn つかってる、HTTP で Git 公開できないので気軽につかえない
    • ここで挙手して聴者側から参加、これは Gitolite はどうか?ここで Gitolite の仕組みについて説明
    • 内藤 社内規定 (?) におけるアカウント管理の関係があり Gitolite は採用できなさそう
  • このあとも盛り上がっていたが PC のバッテリーが尽きそうなのでメモはここまで
  • 開発者としては Git と GitHub を利用したい、マネージメント層としては Redmine 的なタスク管理のほうが向く、棲み分けるもの?Redmine の機能強化で GitHub 的なもの (PR など) を取り込み一元化できないか?といった感じの話になっていた

GitHub はすでにソフトウェア開発者の中で全世界的に標準インフラ化した感がある。多少の不便があっても運用や周辺ツールでなんとかしているから Redmine に取り込むとしたらかなりの強化が必要だろう。

私としては Redmine から Git リポジトリーを簡単に作成して連携まで自動化するのが第一歩。次に PR 対応するあたりでようやく勝負の入り口という感じ。

グループ ディスカッション

  • 145 名分のアンケート公開
    • Redmine プラグインのシェアなど
  • ディスカッションは 6 人ひと組
  • 登壇内容だったり、その他 Redmine に関するよもやま話をする時間
  • もりのあさ氏などと Redmine 運用や現場の話などで盛り上がる

懇親会

  • ItalianBar KIMURAYA 品川 (キムラヤ)にて懇親会
  • redmine.tokyo の会場から徒歩 20 〜 30 分ぐらい
  • 徒歩組とタクシー組にわかれる、乗り合いならワンメーターちょっとでいけそうな感じなので私はタクシーで
  • 登壇に関する感想や Redmine 運用についていろいろと話す
  • 前田剛氏と Redmine の開発体制について議論
    • 英語力を気にせず redmine.org に参加してほしい
    • Google 翻訳と Ginger 英文チェッカー を通すぐらいでも案外、理解してもらえるのではないか、プロダクトに関する話であれば英語圏の人も拙さは汲み取ってくれるもの
    • Redmine の行く先に不安がある、バージョン系の多様に起因する開発リソース不足、互換性に縛られることで保守化して先進機能 (SPA 化など) へ取り組みにくくなっているのでは、redmine.org 自体が最新 Redmine を採用していないのは大丈夫なのか、…etc
    • 前田剛氏や他の Redmine 開発者もこうした問題点は理解しているのだが、対応に苦慮している
    • オフレコっぽい話もあったので、それは書かないでおく
  • @akipii 氏は Twitter のアイコンがヒグマ?の写真なので赤カブト的な人が来たらどうしようかと思っていたのだが、温厚そうな紳士で助かった

まとめ

自社むけなどは何度かプレゼンしたことがあるのだけど、そうではない一般向けに登壇するのは初めて。

そのため非常に緊張したのだが、結果としては登壇してよかった。

Twitter の #redmineT や懇親会での反応をみるにつけ、巧拙は気にせずリアルでもアウトプットすべきだと感じる。反応を得てこそ。ブログや Twitter でも反応してくれる人はいるが、その場で生の熱意を受けるのは特別な体験である。

以前の私はリアルな交流の内輪ノリが好きになれなくて避けてきた。しかしここ数年、現職の社長や営業の意向もあってセミナーや勉強会で交流を広めるようにしている。こういうのも視野を拡大するために必要なことだと考えるようになった。

そうした経験の一環として redmine.tokyo 登壇はよい機会で、今後も関わっていけたらな、と思っている。

npm icon-gen v1.2.1 release

2018年4月2日 0 開発 ,

icon-gen v1.2.1 をリリースした。

icon-gen 本体については特に機能追加とかバグ修正はないのだけど PNG ファイルから画素を抽出するのに利用している pngjs が Node v9.x 系で動作しないため、代替として pngjs-nozlib へ乗り換えた。

これは pngjs の fork 版で Node 標準の zlib 依存を排除したもの。npmjs からの repository リンクだと本家 pngjs 側になっているけどコードは mikolalysenko/pngjs に公開されている。

pngjs の問題は元々、icon-gen の issue として 2017/11/27 に報告されていた。

これを報告者の lmm-git (Leonard Marschke) 氏が pngjs 本家 issue としても登録。

そして現時点では Pull Request をもって close。pngjs v3.3.2 へ反映されたのだが、残念なことに直っていない。icon-gen の dependencies を更新しても再発するので v3.3.2 時点の pngjs を clone してユニット テストを走らせたも、やはり通らない。

というわけで関連 issue をたどって見つけた pngjs-nozlib を採用することにしたのだった。ちなみに pngjs 作者の lukeapage (Luke Page) 氏いわく、

I closed it because a contributor claimed their Pr fixed it.. sorry it didn’t.

I don’t actively maintain pngjs any more.. I’m happy to merge prs and release but I don’t have time to fix this. If you send a Pr I will merge it.

とのこと。Pull Request がきたら採用するけれど自身として積極的に修正することはしないそうだ。本家 issue の関連や npmjs 上の Dependents を見るとわかるように pngjs は相当に著名なツールなのだけど、そうしたものでもやはり個人開発だと運用持続に期待するのは厳しいなぁ、と感じた。

かくいう私も例外ではない。せっかく issue 登録してもらっても長く放置することがある。趣味プロジェクト運用は私生活や気分に左右されやすい。最近だとエレキ ギターを十数年ぶりに再開したこともあって趣味プロジェクトに注いでいた気力をだいぶ奪われてる。

著名プロジェクトなら Organization にすれば?という意見もあるだろう。それは分かるけど Organization もなんだかんだコミュニティー運営することになるわけで、それも結構な気力を使う。どうしても機能追加とかバグ修正したいユーザーがあれば Pull Request してくれるだろうし、それ待ちでいいやとなるのはよくわかる。

以前 Takuto Wada(@t_wada)さんが書いた

を思い出した。pngjs は「良いソフトウェア」である。一方で今回の問題は Node v9.x で破壊的な変更が入って巻き添えを受けた形になる。こうした参照される側の影響で「良さ」以前に互換性が壊されたとき、それに対する責任は参照する側に問われるべきだろうか。LICENSE テキストに書かれた免責事項はどれぐらい有効なのか。

気分がのったとき自分のほしいものを自己責任で公開して適当に雑にゆるやかに運用してゆくだけ。そんな OSS はたくさんありながらもそうと認識されておらず、ある日とつぜん問題が顕在化して騒ぎになるのだろう。これからも。

免責事項により参入障壁は下がりプレーヤー増加と競争を促している側面がある。強く責任を求められる文化だったらここまで OSS が隆盛することはなかっただろう。いつか誰かが作り直せばいい。以前よりも少しだけ前進すればいい。という楽観的で寛容な文化と責任のせめぎあい。

などと、とりとめもなく考えてみたものの結論はない。有効な手立ても思いつけないでいる。

CSS Modules をもっと試す

2018年2月20日 0 開発 , , , ,

業務プロジェクトへ CSS Modules 導入を検討中。しかし JS/CSS ともに大きく設計変更されるため、いきなり採用するのは怖い。というわけで、そこそこの規模と複雑さを持つ examples-electron/audio-player で試してみた。このプロジェクトは Electron 製の簡易音楽プレーヤーになる。JS/CSS まわりはこんな感じ。

  • JavaScript
    • AltJS は Babelbabel-preset-env、ES.next 範疇の機能と構文のみ使用、ターゲット設定は electron なので少し古い Chorome 相当になる
    • View は React
    • Flux は material-flux、いずれ reduxmobx あたりへ乗り換える予定
    • Bundler は webpack
    • Electron の Main/Renderer プロセスともに Bundle/Transpile 対象
  • CSS
    • AltCSS は Stylus
    • Bundler/Transpiler ともに stylus で完結
    • CSS クラスは BEM、定義で楽するため Stylus のネスト機能に強く依存している
    • IcoMoon で生成した Icon Font を利用

このような環境に CSS Modules (with Sass) を導入して遭遇した問題や対策を記録する。

webpack.config.js

Sass を試す の CSS Modules (with Sass) + webpack 構成を踏襲しながら少し修正。Babel や package.json は CSS Modules とあまり関係ないので webpack.config.js のみ掲載。webpack v3 の設定になる。近くリリースされるという webpack v4 で変更が必要になるかもしれないけど、CSS Modules 部分はそのまま維持できると思う。

import WebPack from 'webpack'
import MinifyPlugin from 'babel-minify-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'

export default (env) => {
  const MAIN = env && env.main
  const PROD = !!(env && env.prod)

  return {
    target: MAIN ? 'electron-main' : 'web',
    entry: MAIN ? './src/js/main/App.js' : './src/js/renderer/App.js',
    output: {
      path: PROD ? `${__dirname}/dist/src/assets` : `${__dirname}/src/assets`,
      filename: MAIN ? 'main.js' : 'renderer.js'
    },
    devtool: PROD ? '' : 'source-map',
    node: {
      __dirname: false,
      __filename: false
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader'
          }
        },
        {
          test: /\.scss$/,
          use: ExtractTextPlugin.extract([
            {
              loader: 'css-loader',
              options: {
                modules: true,
                localIdentName: PROD ? '[hash:base64]' : '[name]-[local]-[hash:base64:5]',
                url: false,
                importLoaders: 1,
                sourceMap: !(PROD),
                minimize: PROD ? { autoprefixer: false } : false
              }
            },
            {
              loader: 'sass-loader',
              options: {
                outputStyle: PROD ? 'compressed' : 'expanded',
                sourceMap: !(PROD)
              }
            }
          ])
        }
      ]
    },
    plugins: PROD ? [
      new MinifyPlugin({
        replace: {
          'replacements': [
            {
              'identifierName': 'DEBUG',
              'replacement': {
                'type': 'numericLiteral',
                'value': 0
              }
            }
          ]
        }
      }, {}),
      new WebPack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify('production')
      }),
      new ExtractTextPlugin({ filename: 'bundle.css' })
    ] : [
      new ExtractTextPlugin({ filename: 'bundle.css' })
    ]
  }
}

デバッグ用 CSS クラス名

CSS Modules は CSS クラス名を自動的にハッシュ化してくれるのだが、このままだと DevTools から確認したいときに困る。Source Maps があれば元のスタイルを参照できるものの、HTML 要素の class 属性から React コンポーネントを人間が識別するのは難しい。よって css-loader の options を工夫する。

{
  localIdentName: PROD ? '[hash:base64]' : '[name]-[local]-[hash:base64:5]'
}

デバッグ時は [name]-[local]-[hash:base64:5] としておく。生成されたクラス名は AudioPlayerControl-player-1sCWC のような感じとなり人間にもわかりやすい。終端のハッシュでユニークさも担保される。モジュールとクラス名だけでユニークさを保証できるならハッシュを外してもよい。私は保険のためにつけてる。

リリース版についてはお好みで。私はエンド ユーザー仕向けなので実装の詳細は見せないほうがよいと考えており、それを明示するための難読化としてハッシュ化することにした。エンド ユーザーに開発の協力をあおぐとしてもデバッグ版を提供すればよい。

Sass の @import と CSS Modules の注意点

Sass の SCSS 内で @import するとその単位で参照が解決される。これと CSS Modules を組み合わせる場合は注意が必要。例えば Test.scss に

.test {
  color: red;
}

を定義して Content.scss と Footer.scss がそれぞれ @import したとする。これらを更に JavaScript から参照すると Test.scss の内容が Content.scss と Footer.scss で個別に展開されてから CSS Modules に結合されるため

/* Content.scss から @import "Test.scss" した部分 */
._3lCv-t9tW054o8vckddf2y {
  color: red;
}

/* Footer.scss から @import "Test.scss" した部分 */
._15Cb513oAuCYe4NCm3MT69 {
  color: red;
}

のように重複してしまう。CSS Modules は JavaScript からの参照解決だけを担当してモジュール内の参照は考慮しないようだ。

これを避けるために共用したい SCSS は CSS セレクターとして有効な定義をせず、変数や Mix-In に限定すればよい。そもそも CSS セレクターを参照しても利用する方法がないわけで、継承とか合成ならば Sass として @mixin@extend が提供されている。

CSS Modules の結合順とグローバル

CSS Modules の結合は JavaScript 側の参照順となる。CSS Modules を利用しようと考える時点で JS/CSS は疎結合になっているだろうから、普段は順番を意識することはない。しかし例えば

  • <html><body> などのスタイルを CSS 全体で一度だけ定義したい
  • アイコン フォントは個別に参照せずグローバルに一度だけ定義したい

ということもあるだろう。これを実現してみる。

まずは CSS の参照位置。これは JavaScript 側のエントリー ポイント冒頭にする。例えば App.js がエントリー ポイントなら

import './App.scss'

// 以下、エントリー ポイント処理...

のように読み込む。JavaScript 側で直に参照するものはないため from は不要。考え方としては babel-polyfill に近い。これで App.scss に定義されたスタイルは CSS の先頭に展開される。

次にグローバルな CSS クラス名。これは css-modules/css-modules の README で解説されているとおり :global で囲む。例えば .app というクラス名をハッシュ化せず維持したいなら以下のようにすればよい。

:global {
  .app {
    width: 100%;
    height: 100%;
    background-color: $color_white;
  }
}

これはサード パーティ製 CSS 内のハッシュ化を防ぐためにも使える。

:global {
  @import "~library.css";
}

こうすれば対象となる CSS 内に定義されたクラス名が維持される。配布形態が npm なら ~モジュール名 とすることで node_modules 配下のパスを省略も可能。

ただしこの機能にはバグがある。2018/2 時点の css-loader だと :global 内に font-face があると { font-face {} } のように余計なブラケットで囲まれてしまう。そのため font-awesome を対象にすると CSS が壊れてしまう。

アイコン フォントのクラス名

アイコン フォントを利用する場会は前述の問題があるため font-face:global に囲えない。よってアイコンの CSS クラス名を維持するならそこだけを :global に囲み font-face はむき出しにする必要がある。CSS クラス名を維持せずハッシュ化を許容するとしても別問題がある。

例えば

[class^="icon-"], [class*=" icon-"] {
  font-family: "icon";
  speak: none;
  font-style: normal;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  line-height: 1;

  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-rew:before {
  content: "\e800";
}

.icon-warning:before {
  content: "\e801";
}

のようにセレクター名で共通部分を定義する場合、そこを維持しなければならない。これは「デバッグ用 CSS クラス名」の項で書いたように css-loader の localIdentName を工夫すれば解決可能ではある。しかしこの方法だと全 CSS クラス名に影響するため設計的に好ましくない。またアイコン フォントは複数箇所から同時に利用したくなるだろうし、その場合は性質としてグローバルだ。

よって font-face 以外は :global でまるごと保護する方が運用しやすい。React コンポーネント側はそのまま直値で CSS クラス名を指定する。

font-faceurl 参照

font-face の参照で src:url() にローカルの相対パスを指定した場合、css-loader の処理時点で参照が解決されなければならない。そのため

.
├── assets
│   ├── index.html
│   └── fonts
│       ├── icon.eot
│       ├── icon.svg
│       ├── icon.ttf
│       └── icon.woff
└── js
    ├── App.js
    └── App.scss

のような構成で以下のように定義しているとエラーになる。

@font-face {
  font-family: "icon";
  src:url("fonts/icon.eot");
}

CSS の出力先が assets/ であっても css-loader は CSS から参照されているものを直に解決しようとするため、App.scss から fonts/icon.eot が見えないといけない。これを防ぐためには css-loader の optionsurl: false を設定する。これで src: url() の参照を無視してくれる。自前でアイコン フォントを用意するなら、これとグローバル指定を組み合わせて CSS のエントリー ポイントへまとめて定義するとよい。

@charset "UTF-8" 問題

アイコン フォントの定義で content: "\e800"; のように非 ASCII 文字を指定すると sass-loader が気を利かせて @charset "UTF-8"; を挿入してくれる。しかし「Sass の @import と CSS Modules の注意点」で書いた問題を考慮しなければならない。

例えば <html><body> などのスタイルを Base.scss、アイコン フォント系を Icon.scss に定義したとしよう。これらをエントリー ポイントになる App.scss で

@import "Base.scss";
@import "Icon.scss";

のように読み込んだ場合、

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

@charset "UTF-8";
@font-face {
}

という感じで出力される。Icon.scss で初めて非 ASCII 文字が登場したため、このファイルを展開する位置に @charset "UTF-8"; が挿入されてしまった。これは @charset で解説されているとおり CSS として不正である。

対策するなら

  • 順番を意識して慎重に @import する
  • グローバルなものはファイル分割せず、エントリー ポイントで直に定義する

ことになる。私は設計的に単純な後者を採用した。直に定義するとファイルが長くなる懸念はある。しかし CSS Modules を採用しているならグローバルは少なくなるはず。そうでないならコンポーネントやモジュールの分割に問題があるため、設計を見直したほうがいい。

npm の font-awesome を利用するなら?

npm の font-awesome を利用するなら、ここまで書いたアイコン フォントに絡む問題をすべて解決しなければならない。

CSS クラス名の維持

CSS クラス名を維持する場合は :globalfont-face が壊れる問題を対策する。そのためには npm で配布されたファイルを加工する必要がある。font-face 以外を :global で囲むことになるだろう。これは npm 管理の観点からおこなうべきではない。配布されたものをそのまま利用できないなら、npm を採用する意味がない。

font-facesrc:url() 問題

font-facesrc:url() 問題について。これは大まかに二つの対策がある。

  1. url-loader で参照解決して出力 CSS に base64 文字列としてフォント ファイルを埋め込む
  2. 参照をスキップさせて CSS 出力先へフォント ファイルを都度コピーする

1 は CSS ファイルが巨大になる問題あり。これを許容できるなら Bundler という観点として正当な方法といえる。2 は出力された CSS から参照できればよい点は好ましいのだけど font-awesome の font-face 定義は

@font-face {
  font-family: 'FontAwesome';
  src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
  font-weight: normal;
  font-style: normal;
}

のように ../fonts/ を参照しているため、出力先でこの構造を再現しなければならない。例えば

.
├── index.html
├── css
│   └── bundle.css
└── fonts
    ├── FontAwesome.otf
    ├── fontawesome-webfont.eot
    ├── fontawesome-webfont.svg
    ├── fontawesome-webfont.ttf
    ├── fontawesome-webfont.woff
    └── fontawesome-webfont.woff2

こんな感じ。Bundler で単一 CSS ファイルを出力していると、それひとつだけ格納するフォルダーを用意するのは抵抗がある。webpack loader/plugin を駆使してパスを書き換える手もあるだろうけど font-awesome の定義変更に影響されるため微妙。

@charset "UTF-8" 問題

font-awesome の @import を全 CSS の先頭にもってくればよい。

これは制約になるためコミット ログだけでなくコメントでソース自体に明記しておいたほうがよい。第三者が @import 周りを整理しようとして事故らないためにも。

そもそも Bundler と相性よくない

font-awesome を Bundler と組み合わせて利用したい需要はそれなりにあるようで、

といった記事で対策が紹介されている。しかし特定 npm の設計による問題を解決するために特別な npm や設定を用意するのは泥縄ではないか。問題が問題を呼んでいる。依存も増えるし労に見合わない。

よって私が Font Awesome を利用するとしたら npm ではなく静的リソースとして直に組み込む。fonts/ を好みの場所に置き、CSS も書き換える。Releases – FortAwesome を見るに更新頻度はさほどでもないし、手動で管理しても大した手間ではないだろう。なお npm 版は CSS/SCSS/LESS ファイルをまとめて提供してくれるため、手動で組み込むための元ネタとしてインストールする価値はある。

Babel や Sass のように Font Awesome が公式に webpack loader/plugin をサポートしてくれることへ期待している。

JS/CSS の配置

CSS Modules を採用することで CSS は特定の React コンポーネント専用になる。よって JS/CSS は併置すると運用しやすい。例えばこんな感じで。

.
├── AlbumListContainer.js
├── AlbumListContainer.scss
├── AlbumListHeader.js
└── AlbumListHeader.scss

併置することで JavaScript 側から参照するときの相対パスが短くなる。ファイル名は一緒にして拡張子だけ変えると関連していることがわかりやすい。ただし同名ゆえに参照する際は拡張子まで記述すること。拡張子を省略すると参照している自身と区別できないので。

CSS クラスのネストについて

CSS Modules を利用しない場合、CSS クラスはすべてグローバルになることを前提に名前衝突を避けるため様々な工夫が必要だった。私も命名規則として BEM (Block Element Modifier) を採用していた。しかしベタに書いてゆくと

.button {}
.button--state-success {}
.button--state-danger {}

のようになってダルいため Stylus のネスト機能を使って

.button {
  &--state {
    &-success{}
    &-danger {}
  }
}

のように定義していた。CSS Modules を利用するなら衝突を気にする必要がないため、ネストさせず

.button {}
.success {}
.danger {}

のように書けばよい。モジュール分割されることでその単位のコンテキストが明示されるから従属しているものを簡素なクラス名にしても通じる。この例であればファイル名が Button.scss なら .button.container.base といった基礎部分をあらわすようにしてもよいだろう。

DOM 要素、擬似要素、擬似クラスに関しては CSS Modules の範疇外だが、これらも

.button {
  img {}
  &:hover {}
}

とするよりは

.button img {}
.button:hover {}

のように CSS 標準で書くほうがよいのかもしれない。これについては私もどうするか迷っている。しかし CSS 標準に寄せておけば CSS Modules や Sass への依存度をなるべく下げることでツールを捨てやすくなる。CSS を第三者と共有する際にも前提知識が CSS 標準の範疇で済むため混乱が起きにくいだろう。というわけで新たに書くならネストなしを採用する予定。

CSS Modules と CSS in JS について

React コンポーネントと CSS を連携させる手段として CSS Modules の他に CSS in JS という勢力がある。これらの違いについては古い記事だけど以下がわかりやすい。

特に後者ではそれぞれの弱点をとりあげていて納得感もある。しかし私は Free-Style の弱点で触れられている

CSS Modules は CSS で書くので、「いつでも引き返せる」みたいな安心感は CSS Modules の方が強いと思う

を重視するため CSS Modules を選ぶことにした。CSS in JS は Free-Style 以外にもいくつかあって、これも古い記事になるけど

が参考になる。で、この「いくつかあって」というのが問題。

CSS の Property/Value 記法は JavaScript オブジェクトの Key/Value と似ているため、ほぼそのまま書けそうに思える。しかし擬似要素や擬似クラスなどは対応しそうな構文がないため、諦めるか特別な記法を持ち込むことになる。そしてこの対策がツール間でまちまちなのだ。依存すると引き返すのが難しくなる。

CSS in JS を利用する動機として JavaScript から動的に変更しやすいというのがある。これについては外観に関わるものなら値そのものを書き換えるより CSS クラスを変更する従来式で十分だと考えてる。

静的でよければ Sass や Stylus にも変数はあるので外観プリセット切り替えのような目的には対応できる。動的なものとしても CSS 標準で calc などが控えてる。ならばわざわざ JavaScript 側でがんばらなくてよいのでは?というのが私の見解。

高機能なのはわかるけど、それなしに CSS 標準へ寄せて運用できるならそうしたい。この辺、CSS in JS について解説してる記事を読んでもピンとこなかったところなので識者の意見をうかがいたい。