valid,invalid

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

package.json の dependencies を JavaScript のコード内で参照する

package.json に書かれた、依存しているライブラリの名前 (dependencies) を JavaScript のコード内で参照するやり方。

import pkg from './package.json';

const dependencies = Object.keys(pkg.dependencies)
const devDependencies = Object.keys(pkg.devDependencies)
const peerDependencies = Object.keys(pkg.peerDependencies)

たとえば rollup.config.js で external を指定するときとか。

{
  external: Object.keys(pkg.dependencies)
  ...
}

brew cleanup でディスクの空き容量を増やす

brew は古くなった formula を自動的に消してくれないので、使い続けるほどにインストールしたパッケージが積もってディスクを圧迫する。

FAQ — Homebrew Documentation に書いてある brew cleanup を試したら 4GB ほどの空き領域を得られた。

How to use brew cleanup

# 特定の formula を消す
$ brew cleanup <formula>

# outdated な formula をまとめて消す
$ brew cleanup

# 削除対象の formula と得られる空き容量を確認する
$ brew cleanup -n

Read help

FAQ 以上の情報は help を見てみる。

$ brew cleanup -h

brew cleanup [--prune=days] [--dry-run] [-s] [formulae]:
    For all installed or specific formulae, remove any older versions from the
    cellar. In addition, old downloads from the Homebrew download-cache are deleted.

    If --prune=days is specified, remove all cache files older than days.

    If --dry-run or -n is passed, show what would be removed, but do not
    actually remove anything.

    If -s is passed, scrub the cache, removing downloads for even the latest
    versions of formulae. Note downloads for any installed formulae will still not be
    deleted. If you want to delete those too: rm -rf $(brew --cache)

新たにわかったこと

  • --prune=days で n 日以前のキャッシュを破棄できる。指定しない場合の記述がないが code を見るとデフォルトは14日のようだ。
  • -n--dry-run の 短縮形だった
  • -s で 最新の formula のダウンロードファイルも削除してキャッシュを掃除する。ただしインストールされたものについては消えない。これらも消したい場合は rm-rf $(brew --cache) を使う。

より詳細な挙動を追うならエントリーポイント実際の処理あたりを読むと良さそう。

環境

スプレッド構文と Object.assign の違い

JavaScript の Object の spread operator (スプレッド構文) と Object.assign の違いを調べてわかった気になるが何度も忘れるので整理

結論

実質ほぼ同じことが実現できるが互換性の問題があるので Babel や TypeScript でトランスパイルするなら spread operator を使っておきたい。

前提

実質ほぼ同じことが実現できる。

const object1 = {
  a: 1,
  b: 2,
  c: {
    d: 3
  }
};

const object2 = {
  b: 4,
  c: {
    d: 5
  }
};

const spreaded = {...object1, ...object2};
console.log(spreaded);
// > Object { a: 1, b: 4, c: Object { d: 5 } }

const assigned = Object.assign({}, object1, object2);
console.log(assigned);
// > Object { a: 1, b: 4, c: Object { d: 5 } }

また、どちらも shallow copy である。

Spread operator

メリット

  • サポートしていない環境で動かす場合には、Babel などでトランスパイルするだけでOK
  • 簡潔

デメリット

Object.assign

メリット

  • 標準化されている
  • 引数を動的に取ることができる
const sources = [{ a: 1 }, { b: 2 }];

const assigned = Object.assign.apply(Object, [{}].concat(sources));
console.log(assigned);
// > Object { a: 1, b: 2 }

デメリット


感想

「標準化されている」ことはメリットとして挙げられるはずなのに、標準化されているからこそトランスパイル対象外になってしまい、避けたくなってしまう??

何か間違っている気が…。

参考

Object.assign() - JavaScript | MDN

スプレッド構文 - JavaScript | MDN

javascript - Object spread vs. Object.assign - Stack Overflow

React で SVG を描画するための use タグでは xlinkHref を使う

React で use tag 使うときの躓き

結論

sprite - SVG use tag and ReactJS - Stack Overflow にある通り xlinkHref を使うとよい。

<svg>
  <use xlinkHref="#my-logo" />
</svg>

古いバージョンでは dangerouslySetInnerHTML を使うしか回避策が無かったようだが、今ではこの方法は避けたほうが良い。

経緯

HTML と同じノリで SVG を render できるかと思ったら compile error になってしまった

<svg>
  <use href="#my-logo" />
</svg>
TS2559: Type '{ href: string; }' has no properties in common with type 'SVGProps<SVGUseElement>'.

xlink もダメ

<svg>
  <use xlink:href="#my-logo" />
</svg>
TS1003: Identifier expected.

環境

  • TypeScript 2.7.2
  • React 16.0.4

JavaScript が動作する Capybara の feature spec でブラウザのコンソールを確認する

結論

page.driver.browser.manage.logs.get(:browser)

調べた経緯

フォームの text input に対する fill が働かないのでどうしたかと思い、Selenium で操作中のブラウザをのぞいてみると React の component が一切 render されていなかった。

どこかで JavaScript の error が起きているだろうとと思ったがブラウザの console を確認する方法がわからず調べた。

visit 直後に binding.pry を張り、このワンライナーで確認したところたしかに js error で死んでいた。csrf_meta_tag で生成される meta tag から CSRF token を js 側から取り出そうとしているが、そもそもその要素が DOM tree 内に存在せずにエラーになる、というものだった。

たしかにテスト時の環境では CSRF チェックを無効にするよう config/environments/test.rb で以下のように設定しているのでわかればどうということはない問題だった。

# config/environments/test.rb
config.action_controller.allow_forgery_protection = false

しかし、直接の原因はクライアントサイドの js にあっても RSpec の failure message 上は Element Not Found で失敗しているので何が起きているか突き止めるのに結構悩んでしまった。

環境


(追記)

記事を書いた後に見つけたのだが、以下の記事では SEVERE レベルの js error が起きた場合にテストを fail させる方法を紹介している。これは良いかも。

medium.com

休日の成果を手放しに称賛しない

土日祝日などの勤務時間外にがんばって出した成果を「やっていき」「圧倒的当事者意識」などと手放しに称賛しない方が良いと思っている。

「いやー土日にがんばるなんてスゴイっすね〜〜〜」と褒められて気分良くなったりするんだけど往々にしてそもそも実現不可能なスケジュールの帳尻合わせに加担してしまっていたりする。そういうのは個人の頑張りで巻き返すのではなくいっそ破綻させた方が全体の教訓になるので好ましい。

こういう振る舞いを迂闊に繰り返すとだんだん周囲の期待値も変わってきて「休日で巻き返せる/巻き返してくれるからいっか」「今週末は働いてくれなかったのか…」となってくる。*1

ボランティア精神に近い個人の貢献は当たり前ではないことを共有し続けないといけない。


誤解しないようにしたいのが問題なのは「やり方」であって「出した成果」それ自体は尊いということ。「休日に対応したからゴミ」みたいなことは、ない。平日にやろうが土日にやろうが生んだ価値は変わらない。

なので成果に対しては「めちゃ良いですね〜〜」と言いつつ「でも次からはスケジュールやリソースの使い方をみんなでもっと考えましょう!!!」と言っていかないといけない。そして最後にもう一度「いや〜それにしても成果はめちゃ良いですね〜〜〜」と言っておく。


リモートワーク可能な勤務環境にある場合、ふと思い立った実装を試してみたり休日前やりかけて気になっている仕事に手を付けたりするのはとても簡単で自分もやってしまいがちなので一層気をつけたい。という気持ちの表明、以上。

とはいえアイデアはナマ物、勢いでやってしまう時はある。その時はちゃんと勤務時間として計上するとか代休をきちんと取るといった当たり前のところから始めましょう。

*1:それは突飛すぎると思われるかも知れないが過去にあった話。超絶ホワイトなチームだったが、やたらスケジュールを巻きたがるチームリーダーがこれを繰り返した結果、顧客のスケジュールに対する期待値が変わってしまった。他のチームメンバーの働き方も加熱する"巻き"に引っ張られた

PhantomJS + Poltergeist を Selenium + Headless Chrome で置き換える (2) Teaspoon による JavaScript test 編

PhantomJS + Poltergeist を Selenium + Headless Chrome で置き換える (1) Rails + Capybara による feature spec 編 - valid,invalid の続き。

teaspoon.env の変更内容について

Capybara + Selenium + headless Chrome の設定例はググると結構出てくるものの、Teaspoon + Selenium + headless Chrome の例はぜんぜん出てこなかった…。

それもそのはず、詳細は後述するが Teaspoon 自体のメンテナンスがあまりアクティブでないために組み合わせるためにモンキーパッチを当てなければいけない始末だった。

変更部分

以下はコードの追加分。

# spec/teaspoon_env.rb

# Teaspoon doesn't allow you to pass client driver options to the Selenium WebDriver. This monkey patch
# is a temporary fix until this PR is merged: https://github.com/jejacks0n/teaspoon/pull/519.
require 'teaspoon/driver/selenium'

Teaspoon::Driver::Selenium.class_eval do
  def run_specs(runner, url)
    # この1行のためのモンキーパッチ
    driver = ::Selenium::WebDriver.for(driver_options[:client_driver], @options.except(:client_driver))
    driver.navigate.to(url)

    ::Selenium::WebDriver::Wait.new(driver_options).until do
      done = driver.execute_script("return window.Teaspoon && window.Teaspoon.finished")
      driver.execute_script("return window.Teaspoon && window.Teaspoon.getMessages() || []").each do |line|
        runner.process("#{line}\n")
      end
      done
    end
  ensure
    driver.quit if driver
  end
end

Teaspoon.configure do |config|
  config.driver = :selenium

  options = if ENV['SELENIUM_URL'].present?
              http_client = Selenium::WebDriver::Remote::Http::Default.new(read_timeout: 120)
              {
                client_driver: :remote,
                url: ENV['SELENIUM_URL'],
                http_client: http_client,
              }
            else
              { client_driver: :chrome }
            end

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

  config.driver_options = options

  # test suite の記述が続く
end

driver options あたりは Capybara のときとほぼ同じ。

実際にはこのリプレイスのほか、remote の Selenium Chrome でも動くようにする変更を足したりして大変だったのだが本題ではないので省略する。

driver options 渡せない問題

まずいちばんの問題が Teaspoon v1.1.5 では Selenium::Webdriver に driver options を渡せないこと。これを改善する PR が提案されているのだが10ヶ月ほど放置されている…。

teaspoon.env は同様に困っている人々が書いた monkey patch を拝借しつつ書いた。

Net::ReadTimeout 問題

Capybara の設定との大きな違いは HTTP client を渡しているところ。これは remote Chrome -> Teaspoon server へのリクエストがデフォルトの HTTP client だと頻繁に Net::ReadTimeout してしまうため。

最初は Chrome の起動が遅いのかと疑ったが driver の初期化付近にブレイクポイントを張ってデバッグしたところ、driver.navigate.to(url) が高確率でタイムアウトしていることがわかった。

    driver = ::Selenium::WebDriver.for(driver_options[:client_driver], @options.except(:client_driver))
+   binding.pry
    driver.navigate.to(url)

Teaspoon の立ち上がりがそれだけ遅いのは実行されるテスト側に問題があるのかもしれないが、ほぼ初めて触るこのレポジトリのテストコードがどんな感じか見定めるのは厳しかったので read_timeout を伸ばした HTTP client を渡すことで回避した。

設定は 30 -> 60 -> 90 -> 120と試したが安定したのは 120 だけだった。

感想

PhantomJS + Poltergeist を Selenium + Headless Chrome で置き換えて多幸感を得たはずが Teaspoon との戦いで疲弊してしまった。次は Teaspoon を消そう。