@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

今週の作業メモ

JavaScripting

runコマンドのバグ

https://github.com/workshopper/javascripting/issues/218 javascripting run hogohoge.jsを実行するとたしかに[object Object]が表示されます。 javascriptingではrunコマンドは使っていません。

https://github.com/workshopper/workshopper-adventure/blob/master/index.js#L264 workshopper-adventure 経由です。 アンドキュメントなコマンド発見して実行して上手く動かないと言われるのは心外だが、不親切なのも確か。 こういうときはどうするのがいいのかなあ? コマンドをworkshopperに投げる前に検査してUsage表示するとか?

helpコマンドでrunコマンドでの説明が出てくるのか・・・全然アンドキュメントじゃないな

~ bin/javascripting help

 # JAVASCRIPTING

 ## javascripting のワークショップに対して質問がありますか?

  エキスパートチームはあなたの質問を待っています。あなたもNode.jsのマスター
  になれるようにここに質問(New Issue)をしてみてください:

     https://github.com/nodeschool/discussions/issues

  どんな質問でもどうぞ!

  一般的なNode.jsのヘルプが必要であればNode.jsの日本のGoogleグループがありま
  す:

     https://groups.google.com/forum/#!forum/nodejs_jp

 ## javascriptingのバグが見つかった場合または貢献したい場合:

  javascriptingのリポジトリはここにあります:

     https://github.com/sethvincent/javascripting.git

  バグの報告やPullリクエストは日本語でいつでもどうぞ!

 ## 使い方

  javascripting .....................
  ワークショップを選択する対話的メニューを表示します。

  javascripting list ................
  登録されているすべてのワークショップの名称一覧を表示します。

  javascripting select NAME ......... ワークショップを選択します。

  javascripting current .............
  現在選択されているワークショップの名称を表示します。

  javascripting print ...............
  現在選択されているワークショップのガイドを表示します。

  javascripting next ................
  現在選択されているワークショップの次の未修了のワークショップのガイドを表示
  します。

  javascripting reset ...............
  ワークショップの進捗状況をリセットします。

  javascripting run program.js ......
  あなたが作成したプログラムを実行します。

  javascripting verify program.js ...
  あなたが作成したプログラムが正しいかを検証します。

  javascripting -l <language> .......
  システムで使用する言語を指定されたものに変更します。

ソースコードリーディング

javascriptingソースコードからjavascriptingworkshopperの役割分担を読みときます。 https://github.com/workshopper/javascripting/blob/master/index.jsworkshopper-adventureインスタンスを読み込んでいます。 サブコマンドによる分岐の全体的な制御フローは workshopper-adventure が担当します。

https://github.com/workshopper/javascripting/blob/master/index.js#L8 で、 menu.json に含まれる problem の一覧を読む。

https://github.com/workshopper/javascripting/blob/master/index.js#L12-L14problemごとに require('problems/introduction') な処理をする関数を作る。

https://github.com/workshopper/javascripting/blob/master/problems/introduction/index.jsrequire("../../lib/problem")(__dirname) なので 実質的に https://github.com/workshopper/javascripting/blob/master/lib/problem.js を実行している。

createProblem 関数で作った exercise を引数にして https://github.com/workshopper/workshopper-adventure/blob/master/index.js#L345 が呼ばれます。

この辺は VSCodeデバッグしながら追いかけています。 https://code.visualstudio.com/docs/nodejs/nodejs-debugging

{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "program": "${workspaceFolder}/bin/javascripting",
      "args": ["verify", "index.js"]
    }
  ]
}

修正案

https://github.com/workshopper/javascripting/blob/master/lib/problem.js ではrunメソッドも呼んでいる。この辺をいじると、クリーンな修正ができるのだろうか?

デバッグするとたしかに run メソッドは呼ばれています。ただし、出力文字列はこで作っているわけでは無さそうです。 https://github.com/workshopper/javascripting/blob/master/lib/problem.js#L53-L55 workshopper-adventure が出力文字列は作っていそう、javascripting 側から上書きする方法があるのかな?

対処

単に run メソッドの定義を消せばよいだけだった。こんなに簡単な話だったとはね、ふふ

~ bin/javascripting run index.js
run is not required for this exercise.

github.com

今週の作業メモ

spring

Rails 5.2.3でrails consoleを実行すると、プロンプトが出る前で止まります。

~ bundle exec rails --version
Rails 5.2.3
~ bundle exec rails c
^CTraceback (most recent call last):
    15: from bin/rails:3:in `<main>'
    14: from bin/rails:3:in `load'
    13: from /Users/shigerunakajima/circular_dependency_detected_job/bin/spring:15:in `<top (required)>'
    12: from /Users/shigerunakajima/circular_dependency_detected_job/bin/spring:15:in `require'
    11: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
    10: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
     9: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
     8: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
     7: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
     6: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/rails.rb:24:in `call'
     5: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
     4: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:35:in `call'
     3: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:42:in `warm_run'
     2: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:62:in `run'
     1: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:117:in `verify_server_version'
/Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:117:in `gets': Interrupt

https://github.com/rails/spring/blob/v2.1.0/lib/spring/client/run.rb#L117 ここのserver(どうもUnixソケットらしい)の読み込みで止まっている。

試しに、Gemfileからspringを消してみると、期待通りにプロンプトが表示されます。

~ bundle exec rails c
Loading development environment (Rails 5.2.3)
irb(main):001:0> 

@p_ck_ 曰く

springが悪さをするのに疲れ果てて最近は DISABLE_SPRING=1 してます 似たような状況のときは bin/spring stopをしたら直っていたので毎回それをしていた記憶があります

Bingo!

bootsnap

ruby 2.7-preview1でRails 6.0.0.rc2の rails --version を実行すると、エラーが起きます。

~ rails --version
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Traceback (most recent call last):
        36: from bin/rails:3:in `<main>'
        35: from bin/rails:3:in `load'
        34: from /Users/shigerunakajima/my_first_rails_6/bin/spring:15:in `<top (required)>'
        33: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        32: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        31: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
        30: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
        29: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
        28: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
        27: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
        26: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
        25: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `load'
        24: from /Users/shigerunakajima/my_first_rails_6/bin/rails:8:in `<top (required)>'
        23: from /Users/shigerunakajima/my_first_rails_6/bin/rails:8:in `require_relative'
        22: from /Users/shigerunakajima/my_first_rails_6/config/boot.rb:4:in `<top (required)>'
        21: from /Users/shigerunakajima/my_first_rails_6/config/boot.rb:4:in `require'
        20: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/setup.rb:30:in `<top (required)>'
        19: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap.rb:30:in `setup'
        18: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/compile_cache.rb:9:in `setup'
        17: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `require_relative'
        16: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `require'
        15: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:288:in `load_dependency'
        14: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `block in require'
        13: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
        12: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
        11: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
        10: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
         9: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
         8: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/compile_cache/iseq.rb:1:in `<top (required)>'
         7: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `require'
         6: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:288:in `load_dependency'
         5: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `block in require'
         4: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
         3: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
         2: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
         1: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require': no implicit conversion of String into Integer (TypeError)

エラーが起きているのはこの辺 https://github.com/Shopify/bootsnap/blob/v1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb#L17-L24

エラーが起きるときの path の値は "bootsnap/bootsnap"です。 require_without_bootsnap は require の alias_methodなので、 require "bootsnap/bootsnap"で再現します。

~ irb
irb(main):001:0> require "bootsnap/bootsnap"
Traceback (most recent call last):
        7: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/bin/irb:23:in `<main>'
        6: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/bin/irb:23:in `load'
        5: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        4: from (irb):1
        3: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:34:in `require'
        2: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:130:in `rescue in require'
        1: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:130:in `require'
TypeError (no implicit conversion of String into Integer)

これでRuby 2.7 + Rails 6ではなくRuby 2.7 + bootsnapの問題だと絞り込めました。bootsnapのissueを漁ると https://github.com/Shopify/bootsnap/issues/258 が 発見できます。 https://github.com/Shopify/bootsnap/issues/258#issuecomment-518726704 をみるとmasterブランチでは修正済みのようです。

https://github.com/Shopify/bootsnap/commit/a163c7cddfccd68277f6ea43b1831378509817c9 パッチの作者は須藤さんや!さすがや、俺の遥かに前を行っているッ!!! Ruby2.7からはRubyのリビジョン番号が数値からgitのSHAハッシュに変わったので、数値前提のソースコードが壊れたらしい。なるほど・・・そういう変更もあるのか。 bootsnap.c なので、bootsnapはC拡張なのねえ。

ていうかBootsnapって、なんなんだろう?名前しか知らないや

READMEによりますと、Rubyプログラムの起動を早くするためのgemです。大きなRailsアプリケーションだと、50%以上の高速化の実績があります。主な手法としては Kernel#require を上書きして、キャッシュファイルをつかうことで、読み込むrubyスクリプトの検索を速くします。 今回エラーが出ていた https://github.com/Shopify/bootsnap/blob/v1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb#L17-L24 は、まさにこのKernel#require を上書きしている部分!

もう一つの最適化手法にiSeq(コンパイル済みのRubyバイトコード)のキャッシュがあります。MessagePack形式でファイルに保存しておくことで、Ruby本体による「ファイルの読み込み〜コンパイル」を「ファイルの読み込み」だけにできます。iSeqはコンパイルしたRubyのバージョン(だけでなくリビジョン単位)によって変わるので、キャッシュが有効かどうか判断するには、Rubyのリビジョン番号が必要です。このリビジョン番号を取るところがRuby 2.7で動かなく成っていたのの修正が https://github.com/Shopify/bootsnap/commit/a163c7cddfccd68277f6ea43b1831378509817c9 なるほどー!

jQueryUI ダイアログ

jQueryUIダイアログには、閉じた時にその前に選択していた要素を再選択する機能があるようです。

Upon closing a dialog, focus is automatically returned to the element that had focus when the dialog was opened.

https://api.jqueryui.com/dialog/

なるほど・・・

closeするときに this.opener をfocusします。 https://github.com/jquery/jquery-ui/blob/master/ui/widgets/dialog.js#L224

this.opnerは開いたときの document.activeElement なのかな? https://github.com/jquery/jquery-ui/blob/master/ui/widgets/dialog.js#L273

$.ui.safeActiveElement は、IEのエラーを出なくした document.activeElement 取得関数です。 https://github.com/jquery/jquery-ui/blob/74f8a0ac952f6f45f773312292baef1c26d81300/ui/safe-active-element.js#L12