valid,invalid

関心を持てる事柄について

PhantomJS + Poltergeist を Selenium + Headless Chrome で置き換える (1) Rails + Capybara による feature spec 編

レガシーに半身浴しているような、ふだんなかなか触らないけれど現役のレポジトリに潜んでいた亡霊を退治!!! Poltergeist + PhantomJS を消し去り、 Selenium + Headless Chromium に置き換えた。

このレポジトリは Rails による API バックエンドと SPA フロントエンド両方を管理しているもので、以下の2箇所で PhantomJS に依存していた。

  1. Capybara による feature spec。PhantomJS の driver として poltergeist を使用。
  2. Teaspoon による frontend の JavaScript test。Teaspoon は JS のテストランナーでもあり PhantomJS driver も内包している 。

両方について書いたら思いの外長くなってしまったのでこの記事では 1. の方のリプレイスについてのみ書く。後半の 2. の方は PhantomJS + Poltergeist を Selenium + Headless Chrome で置き換える (2) Teaspoon による JavaScript test 編 - valid,invalid で。

動機

  1. PhantomJS は元々ベースにしている Webkit が古くて最新のブラウザで利用可能な JS, CSS を充分サポートできていなかったのに加え、Chrome 59 の headless mode リリース以降コアメンテナが離れたことでプロジェクトの将来性が危ぶまれる。

  2. PhantomJS が依存している Qt のインストールでしょっちゅう問題が起きる。

  3. 他のレポジトリは既に Selenium + headless Chrome を使うようにアップデート済。

結果、可及的速やかにリプレイスすることが望ましい状況だと思いました。(というか、全部リプレイス済だとなぜか勝手に思ってた)

変更内容について

このリプレイスは同僚が既に他のレポジトリで実践していた内容をほとんど拝借した。とはいえ自分で pull request を送るのだからきちんと理解したいと思い Selenium webdriver の Ruby binding 実装を少し読んでみたりもした。

実際のコード差分

Gemfile は少しスッキリ。開発環境では Docker compose を使い、アプリケーションとは別の Docker コンテナ上で立ち上げている selenium/standalone-chrome に接続しているので今回は chromedriver や chromedriver-helper gem のインストールはしていない。

# Gemfile
group :test do
  gem 'capybara'
-  gem 'capybara-webkit'
-  gem 'phantomjs', :require => 'phantomjs/poltergeist'
-  gem 'poltergeist'
+  gem 'selenium-webdriver'
end

capybara.rb の設定方法はぐぐると諸々出てくるので今なら悩みどころは多くなさそうだ。

Headless Capybara Feature Specs with Chrome RSpec の feature spec でヘッドレス Chrome を使う - Speee DEVELOPER BLOG

など。

# spec/support/capybara.rb
- require 'capybara/poltergeist'
- require 'phantomjs'
+ require 'capybara/rspec'
+ require 'selenium-webdriver'

- Capybara.javascript_driver = :poltergeist
+ Capybara.javascript_driver = :selenium

- Capybara.register_driver(:poltergeist) do |app|
+ Capybara.register_driver :selenium do |app|

-  options = { } # 省略

+  options = {
+    browser: :chrome,
+    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
+      chrome_options: {
+        args: %w(headless window-size=1680,1050), 
+      }
+    )
+  }

+  options.merge!(url: ENV['SELENIUM_URL']) if ENV['SELENIUM_URL'].present?

-  Capybara::Poltergeist::Driver.new(app, options)
+  Capybara::Selenium::Driver.new(app, options)

  # その他 remote で Selenium を動かすための設定が続くがリプレイスとあまり関係ないので省略
end

実際はアプリケーション固有の設定などもあってもう少し複雑になったがだいたいこのような感じ。

Selenium の URL を環境変数で渡せることで local でも remote でも実行できるようになっている(はず)。

ちなみにサンプルによく出てくる disable-gpu フラグは Google Developers ガイドにも載っているので広まったのだと思うが、現時点で最新の Chrome 65 では不要になっている。Chrome 63で動いたという報告も見かけた。(ただし Windows では動くかわからない)

737678 - Headless: make --disable-gpu flag unnecessary - chromium - Monorail


Selenium なんとなく設定周りが複雑でとっつきづらい印象があったのだが wiki は丁寧に書かれている上にコードも思ったほど入り組んでいなかった。

どうやら自分の敬遠は Selenium をラップしてる各々のライブラリの統一感の無さとかから来ているのかもしれない…。


PhantomJS + Poltergeist を Selenium + Headless Chrome で置き換える (2) Teaspoon による JavaScript test 編 - valid,invalidに続く

Bootstrap3 要素の順番をレスポンシブに入れ替える

Bootstrap3 のグリッドシステムでは pull / push という要素を押し込む/引っ張るためのクラスが使える。これを使うことでレスポンシブに要素の順番を入れ替えることができる。

押し込む/引っ張ると言ってもわかりづらいので実例を見たい。

要素を入れ替える例

PC view

「戻る」が左、「すすむ」が右。一般的な構成。

f:id:ohbarye:20180225104646p:plain

Mobile view

モバイルで閲覧しているときには「すすむ」が上、「戻る」が下。これも一般的な構成。しかし要素の順番が PC と入れ替わっている。

f:id:ohbarye:20180225104654p:plain

コード

See the Pen push/pull with Bootstrap by Masato Ohba (@ohbarye) on CodePen.

PC view では右側にある「すすむ」が HTML では先に記述するのがポイント。この「すすむ」を col 6つぶん押し込む (push)。逆に後に記述する「戻る」を col 6つぶん引っ張る (pull)。

この組み合わせで順序が入れ替わる。また、おなじみの col-**- prefix でレスポンシブなマークアップができる。

TreasureData (Presto) で複数行をまとめて1行に集約

OracleLISTAGG, MySQLGROUP_CONCAT みたいなことを TreasureData (Presto) でやりたい。

答え

array_join と array_agg を組み合わせることで実現できる。

6.15. Array Functions and Operators — Presto 0.195 Documentation

  • employee : department = N : N
  • department_employee は中間テーブル

という前提で、素直に組み合わせをすべて選択すると以下のようになる。

SELECT
  e.id AS employee_id,
  e.name AS employee_name,
  d.name AS department_name
FROM
  department_employee de
     LEFT JOIN employee e ON de.employee_id = e.id
     LEFT JOIN department d ON de.department_id = d.id
ORDER BY 
  employee_id
;
   employee_id   employee_name          department_name
---------------- ---------------------- ---------------
         1       Rick                   Development
         1       Rick                   Sales
         2       Chris                  Development
         3       IOROI                  Sales
         3       IOROI                  Marketing
         3       IOROI                  HR

この結果を employee ごとにグループ化しつつ department_name も結合する形で select したい。

array_join と array_agg を組み合わせることで実現できる。

SELECT
  e.id AS employee_id,
  e.name AS employee_name,
  array_join(array_agg(d.name), ',') AS department_names
FROM
  department_employee de
     LEFT JOIN employee e ON de.employee_id = e.id
     LEFT JOIN department d ON de.department_id = d.id
GROUP BY
  employee_id,
  employee_name
ORDER BY 
  employee_id
;
   employee_id   employee_name          department_names
---------------- ---------------------- ---------------
         1       Rick                   Development,Sales
         2       Chris                  Development
         3       IOROI                  Sales,Marketing,HR

YAMLのAnchorとAliasを使ってconfigをDRYに書く

あらすじ

  • ふだん無意識に読み飛ばしているが使おうと思ったときに出てこなかった
  • YAML の anchor と alias を使うと色々 DRY に書ける
  • DRYに書いた

Anchor/Alias

YAML では &name (Anchor) で名前をつけて *name (Alias) で参照することができる*1

Example1: 重複排除

こんな感じの CircleCI 用の config.yml。

version: 2
jobs:
  bundle_npm_dependencies:
    docker:
      - image: circleci/node:8.7.0
    steps:
      - checkout
      - restore_cache: # <= 1つめ
          keys:
            - key-name-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
            - key-name-{{ arch }}
      - run: yarn install --pure-lockfile
      - save_cache:
          key:  key-name-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
          paths:
            - node_modules

  test:
    docker:
      - image: circleci/node:8.7.0
    steps:
      - checkout
      - restore_cache: # <= 2つめ
          keys:
            - key-name-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
            - key-name-{{ arch }}

  deploy:
    docker:
      - image: circleci/node:8.7.0
    steps:
      - checkout
      - restore_cache: # <= 3つめ
          keys:
            - key-name-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
            - key-name-{{ arch }}
      - deploy:
          command: ./scripts/${CIRCLE_JOB}.sh

中身はともかく restore_cache のステップが複数箇所存在する。キャッシュを invalidate するために key name を変える場合は3箇所漏れ無く更新しないといけない。

これを anchor & alias 使い、初出箇所で参照可能にする。

version: 2
jobs:
  bundle_npm_dependencies:
    docker:
      - image: circleci/node:8.7.0
    steps:
      - checkout
      - restore_cache: &restore_cache # <= anchor 作成
          keys:
            - key-name-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
            - key-name-{{ arch }}
      - run: yarn install --pure-lockfile
      - save_cache:
          key:  key-name-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
          paths:
            - node_modules

  test:
    docker:
      - image: circleci/node:8.7.0
    steps:
      - checkout
      - *restore_cache # <= 参照

  deploy:
    docker:
      - image: circleci/node:8.7.0
    steps:
      - checkout
      - *restore_cache # <= 参照
      - deploy:
          command: ./scripts/${CIRCLE_JOB}.sh

宣言箇所がわかりづらいのでファイルの先頭にまとめたりする。

version: 2
anchors:
  - cache_key: &cache_key key-name-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
  - restore_cache: &restore_cache
      keys:
        - *cache_key
        - key-name-{{ arch }}
  - image_name: &image_name circleci/node:8.7.0

jobs:
  bundle_npm_dependencies:
    docker:
      - image: *image_name
    steps:
      - checkout
      - *restore_cache
      - run: yarn install --pure-lockfile
      - save_cache:
          key: *cache_key
          paths:
            - node_modules

  test:
    docker:
      - image: *image_name
    steps:
      - checkout
      - *restore_cache

  deploy:
    docker:
      - image: *image_name
    steps:
      - checkout
      - *restore_cache
      - deploy:
          command: ./scripts/${CIRCLE_JOB}.sh

Example2: 環境変数として定義した値を参照

CircleCI の environment で設定した環境変数を使いまわしたいとき。

# NG例
version: 2
jobs:
  test:
    docker:
      - image: image_name
        environment:
          ARTIFACT_PATH: "/tmp/artifacts"
    steps:
      - run: mkdir -p ${ARTIFACT_PATH}
      - store_artifacts:
          path: ${ARTIFACT_PATH}

shell command の中に書かれた値は変数展開されるが、そうでない箇所は当然ながらただの文字列として解釈されてしまう。JSON に parse するとわかる。

{
  "version": 2, 
  "jobs": {
    "test": {
      "docker": [
        {
          "environment": {
            "ARTIFACT_PATH": "/tmp/artifacts"
          }, 
          "image": "image_name"
        }
      ], 
      "steps": [
        {
          "run": "mkdir -p ${ARTIFACT_PATH}"
        }, 
        {
          "store_artifacts": {
            "path": "${ARTIFACT_PATH}" # <= ココ
          }
        }
      ]
    }
  }
}

こういうときにも anchor & alias は使える。

version: 2
jobs:
  test:
    docker:
      - image: image_name
        environment:
          ARTIFACT_PATH: &artifact_path "/tmp/artifacts"
    steps:
      - run: mkdir -p ${ARTIFACT_PATH}
      - store_artifacts:
          path: *artifact_path

値の一部として展開することはできないので run: mkdir -p *artifact_path はNG。

その他

  • Online YAML Parser 便利
  • そもそも YAML の仕様書ってどこにあるんだ => YAML™ Specification Index のようだが読む気が起きない
  • DRYに書いたからといって読みやすくなるわけではないことも多いので留意

*1:記号はC言語由来?

社員は「会社のために」「いつまでも」働くという欺瞞を葬る本『ALLIANCE 人と企業が信頼で結ばれる新しい雇用』

同僚から「"退職"に関する本ですよ…(暗黒微笑)」とスッと渡された本、『ALLIANCE 人と企業が信頼で結ばれる新しい雇用』を読んだ。

ALLIANCE アライアンス

ALLIANCE アライアンス

著者の一人リード・ホフマンは、リンクトインの創業者であり、ベンチャーキャピタリストとして様々なスタートアップを支援しており、「シリコンバレーヨーダ」の異名を取る重鎮的な存在です。 (Amazon レビューより引用)

とのことで、随意雇用*1が慣例のアメリカ、その中でもシリコンバレーを前提にした議論が展開されている。特に環境変化・事業の栄枯盛衰・人材流動が激しいとされるシリコンバレーで成功企業が多く生まれる要因の一つとして、企業と個人は信頼関係で結ばれるべきという考え方が浸透していることを本書は指摘する。

『ALLIANCE』を読んで「よーし弊社でもシリコンバレー式を完全再現しちゃうぞー」というのは法も雇用慣習も異なる日本では難しいかもしれない。また、企業と社員間のありかたは一人の思い込みで変えられるものではなくトップダウンで示さなければいけないと本書にはある。そうしたハードルが現実には存在するものの、企業と個人がどう繋がっていくべきかを考える材料・指針としては良い題材だと思う。

以下の3点は印象深かった点。

社員はいつまでもいてくれるわけではないと思え

個人的にはこの主張が最も気に入っている。"存在しない終身雇用を信じているふり"という欺瞞を葬れるかどうかが会社と個人の信頼関係を作る上で決定的な要因だからだ。

同書が提案する「コミットメント期間」という考えに基づけば、社員はコミットメントしたミッションをこなす一定期間を終えたら次のステップに進む、その前提で採用すべきということになる。

従業員の次の数年のステップが自社の可能性も勿論あるのでそうなるよう信頼関係を築かなければならない。しかし多くの会社は「優れた社員にいつまでもいてほしい」と思いつつ、その結果の囲い込みが信頼関係を壊している

就職活動をしているときによく見たのが「自分の成長ばかり考えていて」「応募動機に自分のことばかり」という企業本位の意見たち。成長意欲がありキャリア形成を真剣に考える応募者に対して見合う仕事・ミッションを交渉の中で提案できるかは会社の責任だ。

信頼関係がないから社員は転職活動を秘密にする

社員のキャリアを正直に話し合える信頼関係があるなら、自社と他社どっちがいいですかね〜という相談もできる。むしろキャリアを考えればこそ他社を推薦することもある。

「採用して研修して育ったと思ったらすぐ転職してしまう(#^ω^)」という不満もまた企業側の怠慢に思える。転職を止められないのも同様に、次のステップとして自社がふさわしくないと判断されていることを真摯に受け止めないといけない。

ネットワークに価値を置く

社員のネットワークは会社の事業にとっても採用にとっても価値となるということを示すべき。

  • 会社は信頼関係に基づく「卒業生」ネットワークを構築しよう
  • リファラル採用は現職員だけでなく卒業生からも見込める
  • 消費者でなく従業員目線での会社のブランドが存在する。これを高めよう

社内だけで問題を解決しようとするといずれ頭打ちになる。社内の合理性は社会の非合理ということはよくある。


以下、読書メモ。

「アライアンス」とは?

会社と個人の間のフラットで互恵的な信頼に基づくパートナーシップを指す。

互恵的というところが重要で、両者が相互に投資し合うことで事業の変革と個人の成長が同時に達成でき、会社も社員も満足度が高まる。

多くの会社の現状

  • 終身雇用は終わった
  • 個人に対して永遠に雇用を保障しようという誠実さを持った会社はほとんどいない
  • 社員を大事にするとか言っていても会社の本音は「優れた人だけに」「いつまでも」いてほしい
  • このあいまいな態度こそが信頼関係を壊す。社員側に忠誠を求めながら会社は何も約束しない
  • 社員もこのことを知っているからリスクヘッジをする。忠誠を疑われないように影で転職活動をする。チャンスがあればとびつく。転職したら現職との関係は終わり

処方箋

会社と個人は信頼関係に基づく「アライアンス」関係におく。

  • 個人は能力を提供し、会社は機会を提供する。自立したプレーヤー同士が互いにメリットを得るために結ぶ期間限定の提携関係。
  • 従業員が永久的に働くことを前提としていない、かつそれを両者が合意している。すなわち会社に閉じたキャリアプランやスキルを伸ばすことを強いられることもなければ、個人の市場価値を高めるための活動に制限や背徳がない。むしろ会社はそうした機会を提供する
  • 「ある程度長くいてくれるんですよね」「え、ああ、はい(^-^)」みたいな欺瞞がないからこそ個々人の「やりたいこと・やりたくないこと・なりたい姿・なりたくない姿」について忌憚なく意見を交わせる。誠実さは信頼関係をつくる

限定のこの期間を「コミットメント期間」と呼ぶ。

コミットメント期間

コミットメント期間には以下の3型が存在しする。期間の終了に伴い別の型へと移行する、またはキャリアの転向(転職)が発生する。

  1. ローテーション型:比較的代替可能なジョブに適用。会社との相性を見極めるのが目的。新卒の研修・OJTもここに含む。この期間終了後、変革型へ移行するのが通例だが、ミスマッチ解消のために転職してもまったく問題ない。
  2. 変革型:会社にとっては事業の、社員にとってはキャリアの変革をもたらすことが期待される。ミッションを完了する前に次のコミットメント期間のあり方を会社と相談する。まとまらなければ転職する。
  3. 基盤型:3種の中では特殊。個人のキャリアや人生と会社が密接に結びついているケースなので通常、期間は定められない。創業者、10年選手の経営層が代表的だが、組織の末端(CSリーダー、インフラ担当)でもありえる。

ミッションに応じて給与や具体的な職務内容が決まるので「マネージャーにならないと給料があがらない」はありえないし、高い給与に値するミッションを提供できないのは会社の責任となる。

ネットワーク情報収集力

社員が持つネットワークは会社に大きな力をもたらす。なぜなら社外には社内に存在するより遥かに多くの優れた頭脳が存在するからだ。

ペイパルはなぜビルポイントに圧勝したか

ペイパルは競合だったイーベイ+ビルポイント陣営に勝利できたのはこのためだと考えられている。ペイパルは営業からエンジニアに至るまで多くの社員のネットワーク情報収集力を最大限に用いたが、ビルポイントはこうした活動を行わなかったため顧客が本当に欲しかったものを見抜くことができなかった。彼らはそれが「銀行との提携による不正防止と安心感」だと考えていたがそれは衛生要因*2でしかなく、イーベイのプラットフォームを利用する企業とエンドユーザーはEメールでの簡易なやりとりといったUI/UXを重視していた。

ネットワーク情報収集力を強化する4つの手段

信頼関係を築けている社員はこれを最大限に活用してくれる。この能力を活性化・最大化させるには以下のような手段がある。

  1. ソーシャルメディアの活用を会社は社員に進めよう
  2. 業務中のTwitterを諌めない
  3. 無理に強要するとすぐばれる。社員が結託して「弊社に遊びに来ませんか?」などと一時に言い始めると「そういうのを強要される会社なのか」と怪しまれる
  4. ネットワーキング予算を設ける
  5. 社外の面白い人とランチをする場合は会社が費用負担する。ただしレポート必須
  6. 社員による講演会を設ける
  7. 自社オフィスでイベントを開催する

これらはいずれも有効だが活用されなければ意味がない。マネージャーたちが積極的にこの制度を活用するとともに、面談を通じて社員に重要性を説く必要がある。「この活動はあなたのキャリアにとっても非常に有益なものですよ」と。

「卒業生」ネットワークに投資すべき4つの理由

生涯続くアライアンス関係を作る。LinkedIn, Tesla, YouTube, Yammer, Yep, Space X…これらは皆ペイパルの「卒業生」が設立した。

残念がら辞めた社員との関係構築に注目している企業は驚くほど少ない。対称的に辞めた社員も過去の勤務先がどれほど自分のキャリアに影響しうるかをわかっていない。調査によれば64%の卒業生グループは自主的に結成され、卒業生同士での人脈づくりに使われる。これは会社にとってはメリットがほとんどないのだが、一歩はたらきかけるだけでとてつもないROIを生み出す。

  1. 人材獲得に有益。卒業生は社内のルールや文化を熟知しつつも社外の眼で会社を評価できる

    • 出戻り社員になる。卒業生の次のコミットメント期間は一度離れた会社で過ごすべきかもしれない
    • 採用候補者を紹介してくれる。文化に合う人間を卒業生が見極めてくれる
    • 紹介してくれた卒業生には謝礼を支払う
  2. 有力な情報を得られる

    • 同業他社に転職した元従業員からは業界のトレンドやニュースのみならず人材情報を得られる。
    • 卒業生を対象にしたアンケートを定期的に行うのもよい
    • 自社製品に対する批評をお願いするのもよい。社内の人間だと身内びいきのバイアスがかかってしまうが卒業生からは忌憚ない意見がもらえることがある
  3. 顧客を紹介してくれる

  4. ブランド・アンバサダーになってくれる

    • ブランドイメージを企業が完全にコントロールするのは不可能になっている。「あの会社はブラック企業だ」と一度名指しされればなかなか汚名は晴らせない
    • 反対に褒めてもらうことがあれば、いち消費者でなく従業員目線での会社のブランドを高まる

卒業生ネットワークにを活かすには「紹介ボーナス」「卒業生優待・割引」「自社開催イベントへの招待」「定期的な情報交換(メールでもよい)」などが挙げられる。

卒業生との信頼関係は在職中に作られるが、決定的となるのは退職時の面談だ。これを活かさない企業はダメ。

直近だとgrooves社の話が好例かもしれない。

*1:期間の定めのない雇用契約は雇用者・被用者のどちらからでも・いつでも・いかなる理由でも・理由がなくても自由に解約できるという原則のこと

*2:無いと不満だがあってもプラスにはならない要素

Bower は deprecated なので Yarn へ移行した

一度もまともに使ったことなかったけど bower って死んでたんだね…。正確にいうと "maintained, but deprecated" か。

github.com

snyk.io

自分には関係ない話かと思っていた…が、普段まったく触らないがひっそりと稼働を続けるレポジトリに2014年来の bower が残っているのを見つけてしまったので葬った。

構成

こんな感じの bower.json に依存性がガーッと書いてあって

# bower.json
{
  "dependencies": {
    "jquery": "~2.1.1",
    "backbone": "~1.1.2", # そう、backbone です…
    "marionette": "~2.0.2",
    "backbone.stickit": "~0.8.0",
    ...
  },
  "overrides": {
    "backbone.stickit": {
      "main": "backbone.stickit.js"
    },
    ...
  },
  "devDependencies": {
    ...
  }
}

dependencies のライブラリ群をまとめて concat して特定のパスに出力していた。

# gulpfile.coffee
gulp       = require 'gulp'
concat     = require 'gulp-concat'
bowerFiles = require "main-bower-files"

gulp.task 'vendor', ->
  gulp
    .src bowerFiles()
    .pipe concat('vendor.js')
    .pipe gulp.dest('./app/assets/javascripts/vendor')

migration

依存性管理を yarn へ

まず依存性管理を yarn に移す。

bower.jsondependenciesdevDependencies を package.json に移動する。一個一個 yarn add してもいいのだが、version はなるべくキープしたかったのでそのまま。

bower.json は削除する。

# package.json
{
  "dependencies": {
    "jquery": "~2.1.1",
    "backbone": "~1.1.2",
    "backbone.marionette": "~2.0.2",
    "backbone.stickit": "~0.8.0",
    ...
  },
  },
  "devDependencies": {
    ...
  }
}

単にコピーした状態で yarn install を走らせると失敗した。主に以下の理由による。

  1. パッケージ名が bower と npm で異なる
    • npm で調べて置換する
    • importrequire を使っていたらそのパッケージ名も要編集
  2. 指定の version が存在しない
    • version 差異を少なくするため、最低の version を選んでおいた

ビルド周りの整理

gulpfile を修正する。

まず main-bower-files のような packege は yarn remove する。

gulp の src として、dependencies に含まれるライブラリのパスをちまちま書いていく。package.json 中の dependencies をうまく引っ張ってくるとか、もっと冴えたやり方がありそうだけど一旦これで。

# gulpfile.coffee
gulp       = require 'gulp'
concat     = require 'gulp-concat'

gulp.task 'vendor', ->
  libs = [
    "node_modules/jquery/dist/jquery.min.js"
    "node_modules/backbone/backbone-min.js "
    "node_modules/backbone.marionette/lib/backbone.marionette.min.js"
    "node_modules/backbone.stickit/backbone.stickit.js"
    ...
  ]

  gulp
    .src libs
    .pipe concat('vendor.js')
    .pipe gulp.dest('./app/assets/javascripts/vendor')

他にも "bower_components/..." 配下を参照しているパスがある箇所は全部 "node_modules/..." を参照するように置換した。

ビルド

この状態で gulp を走らせる。

アプリケーションは dest ('./app/assets/javascripts/vendor') の中身を読んでいるだけなので特に変更は必要なかった。

ここまでで CI に push したらテストも通った 😇 ということで即マージしてもらえた。

余談

終わった後に気付いたが yarn を使いつつ "bower_components./" を使い続けるソフトマイグレーションもできるようだ。

How to migrate away from Bower? · Bower blog

# 
{
  "dependencies": {
    "@bower_components/almond" : "jrburke/almond#~0.2.9",
    "@bower_components/angular" : "angular/bower-angular#^1.0.8",
    "@bower_components/d3" : "mbostock-bower/d3-bower#~3.3.10"
  }
}

可能ならまとめて葬った方が良いと思うが…。


しかしながら、bower ってなんで必要だったんだろうか…。2014年頃 npm にはフロントエンド周りのアセットがあんまり揃っていなかった、ということなんだろうか?