mizdra's blog

ぽよぐらみんぐ

巨大なコードベースに対して段階的に新しい ESLint rule を導入する

背景

  • 既存の巨大なコードベースに対して新しい ESLint rule を導入したいことがある
    • ESLint を導入した段階では厳しすぎて OFF にしていたけど、やっぱり便利なので ON にしたい、みたいなケース
    • 例えば @typescript-eslint/no-floating-promises とか
  • しかし既存のコードベースはそのルールに従っていないため、ON にすると大量に lint エラーが出てしまう
    • 例えば数百件とか
  • 手で修正するのは現実的ではない、eslint --fix で修正できる rule でもない、けど便利な rule なので有効化したい
    • さてどうしよう

解決策

以前このブログでも紹介した eslint-interactive というツールに、lint エラーが出ている行に一括で // eslint-disable-next-line xxx を挿入する機能があります。これを使うと、ひとまず既存のコードでは該当 rule は無視して、新規のコードでは rule を有効化する、といったことが簡単に実現できます。

youtu.be

github.com

eslint-interactive とは何か、については以前記事を書いたので是非そちらをお読み下さい。

www.mizdra.net

使ってみてね。

補足: suppress-eslint-errors との違いについて

同等の機能を持ったツールに suppress-eslint-errors というものがあって、これでも全く同じことができます。

github.com

じゃあ何で eslint-interactive 側に rule を無効化する機能を追加したかというと、良いユーザ体験を提供したかったからです。元々 eslint-interactive にはコードベース中の lint エラーを見やすくまとめてくれる機能があったり、rule ごとに eslint --fix できる機能があって、それと一緒になっていたほうが何かとユーザ的には嬉しいだろう、みたいな狙いがあります。1つのツールで完結する嬉しさを実現したかった。

2021/9/8 追記

v2.0.0 をリリースして、 suggestion *1 も機械的に適用できるようになりました。

svh という策定中の CSS Unit について

CSS Values and Units Module Level 4 という提案にて、 svh という新たな CSS Unit の導入が検討されています。簡単に言うと、ブラウザのナビゲーションバーやオムニボックスの高さを除外した vh です。

今までは実際にユーザがブラウザで見ている範囲内に要素を収めるには JS で動的に可視領域の高さを計算する必要がありましたが、これが導入されると CSS だけで実現できます。従来の JS を使ったハックでは CSS が適用されてから JS の実行が行われて高さが変更されるため、Layout Shift が発生していましたが、svh を使うとこの Layout Shift を回避できます。Above the fold に動画や画像を画面いっぱいに表示したい時などに便利そうですね。

ブラウザベンダの実装状況

そもそもまだ仕様がドラフトの段階で、実装は全く進んでいません。一応 Issue は出ているので、興味があればトラッキングしてみましょう。早く使えるようになると良いですね *1


以前 Twitter を見ていたら目に入ったので、調べて書いてみました。

追記 (2022/3/15)

Safari 15.4 でサポートが入りました。

*1:もちろんまだドラフト段階なのでどうなるか分かりませんが

nodebrew と homebrew の Node.js が重複しないようにする

普段 yarn を homebrew で管理していているのだけど、yarn が node への依存を持っているせいで、インストールしたり、更新したりする時に Node.js が homebrew で追加でインストールされることがある。けど Node.js は nodebrew で管理しているので、2重でバイナリが存在してむず痒い。何とかしたい、という話。*1 *2

作戦

  1. homebrew でインストールした Node.js バイナリをアンインストールする
  2. nodebrew でインストールした Node.js バイナリを homebrew の管理化に置く
  3. homebrew 側で node fomula のバージョンを固定して、brew upgrade でアップグレードされないようにする

1. homebrew でインストールした Node.js バイナリをアンインストールする

brew uninstall node でアンインストールできる。ただし yarn などから依存されている関係で、そのまま実行してもアンインストールできない。--ignore-dependencies を付けて実行する必要がある。

$ # まず homebrew でインストールしたバイナリがあることを確認して...
$ brew list node
/usr/local/Cellar/node/15.12.0/bin/node
/usr/local/Cellar/node/15.12.0/bin/nodebrew
/usr/local/Cellar/node/15.12.0/bin/npm
/usr/local/Cellar/node/15.12.0/bin/npx
/usr/local/Cellar/node/15.12.0/include/node/ (482 files)
/usr/local/Cellar/node/15.12.0/lib/dtrace/node.d
/usr/local/Cellar/node/15.12.0/lib/node_modules/ (2550 files)
/usr/local/Cellar/node/15.12.0/share/doc/ (2 files)
/usr/local/Cellar/node/15.12.0/share/man/man1/node.1
/usr/local/Cellar/node/15.12.0/share/systemtap/tapset/node.stp

$ # アンインストール
$ brew uninstall node --ignore-dependencies
Uninstalling /usr/local/Cellar/node/15.12.0... (3,283 files, 56MB)

$ # アンインストールされたことを確認
$ brew list node
Error: No such keg: /usr/local/Cellar/node

2. nodebrew でインストールした Node.js バイナリを homebrew の管理化に置く

homebrew には brew link という、自前ビルドしたバイナリなどを homebrew の管理化に置くためのコマンドが用意されている。

github.com

これを使うと、nodebrew でインストールした Node.js バイナリを homebrew の管理化に置くことができる。

$ # まず brew doctor して node が足りないぞと言われていることを確認
$ brew doctor
...
Warning: Some installed formulae are missing dependencies.
You should `brew install` the missing dependencies:
  brew install node

Run `brew missing` for more details.

$ # /usr/local/Cellar/node に nodebrew でインストールした Node.js へのシンボリックリンクを貼る
$ mkdir /usr/local/Cellar/node
$ ln -s ~/.nodebrew/current/ /usr/local/Cellar/node
$ brew link --overwrite node

$ # 再度 brew doctor して警告が出なくなったことを確認
$ brew doctor

$ # homebrew から見えるバイナリの位置も確認しておく
$ brew list node
/usr/local/Cellar/node/current/bin/node
/usr/local/Cellar/node/current/bin/nodebrew
/usr/local/Cellar/node/current/bin/npm
/usr/local/Cellar/node/current/bin/npx
/usr/local/Cellar/node/current/include/node/ (482 files)
/usr/local/Cellar/node/current/lib/dtrace/node.d
/usr/local/Cellar/node/current/lib/node_modules/ (2550 files)
/usr/local/Cellar/node/current/share/doc/ (2 files)
/usr/local/Cellar/node/current/share/man/man1/node.1
/usr/local/Cellar/node/current/share/systemtap/tapset/node.stp

3. homebrew 側で node fomula のバージョンを固定して、brew upgrade でアップグレードされないようにする

最後に brew upgrade で homebrew でインストールした Node.js がアップグレードされてめちゃめちゃにならないよう、バージョンを固定するようにしておく。これは brew pin を使うとできる。

$ brew pin node

おわり。

おまけ

実は今回紹介した手法は、以下の nvm + homebrew 向けの記事を homebrew 向けにアレンジしたものになっている。nvm の人はこの記事を見れば全く同じことが達成できるはず。

github.com

*1:正直 yarn を homebrew でインストールしなければ良いのだけど、yarn 以外でも依存関係でインストールされることがある。最近だと https://github.com/antonmedv/fx とか…

*2:ちなみに homebrew でインストールした Node.js がどの fomula から依存されているかは brew uses node と打つと分かります。

icon-suggestion v2 をリリースしました

あの大人気 Scrapbox UserScript の v2 をリリースしました。既に icon-suggestion をお使いの方は、自動で v1 からアップデートされているはずです。

「icon-suggestion って何?」という方はこちら:

www.mizdra.net

前バージョンからの変更点

  • ポップアップを開く度に最新のアイコンを suggest するように
    • 以前のバージョンでは suggest されたアイコンはキャッシュされ、Ctrl+R を押すか、リロードするまでアイコンリストが更新されませんでした
    • 本バージョンから、ポップアップを開く度に最新のアイコンリストが表示されるようになります
  • isSuggestionReloadKeyDownオプションが廃止されました
    • アイコンリストの手動更新が不要になったため
    • キーワードにマッチするアイコンが無い場合に Enter を押すと、[入力したキーワード.icon]が入力されるようになりました
  • presetIconsオプションでプリセットアイコンを登録できるようになりました
    • 好きなアイコンをプリセットとして登録しておくと、suggest box表示中にCtrl+Lを押した際に suggest されます
  • Firefox でアイコンの挿入ができない問題を修正

1 番目と 3 番目の変更については、以前からユーザから欲しいとご意見を頂いていたものです。お待たせしました!是非使ってみて下さい。プリセットアイコンの登録方法は、以下の Scrapbox にて解説しています。

scrapbox.io

開発者向け情報

本バージョンから、ソースコードが GitHub で管理されるようになりました。もし不具合を発見したり、欲しい機能があれば、Issue を出したり PR を送って頂けるようになっています。テストも Unit テストから E2E テストまで用意していて、安心して contribution して頂けるような環境になっていると思います。あと Preact 使ってます。皆さんからの contribution をお待ちしています。

github.com

Special Thanks

JavaScript で print デバッグ時に変数名を出力する

数列の和を求めるプログラムを作成することになり、意気揚々と以下のようなプログラムを書いたという状況を想像して下さい。

function sum(nums, acc = 0) {
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(sum(nums)); // expected: 55

一見すると何も問題なさそうに見えるプログラムですが、実はバグがあります (皆さん分かりますか?) *1。実際に上記プログラムを実行すると 55 ではなく 10 が出力されます。

こうした場面に遭遇すると、自然と sum 関数が呼び出される度に numsacc がどう変化していくかを知りたくなってきます。この時取りうるデバッグ方法には様々なものがありますが、最も簡単なのは print デバッグでしょう。

function sum(nums, acc = 0) {
  console.log(nums);
  console.log(acc);
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

これを Chrome devtools の Console パネルで実行すると以下のように出力されます。出力がどう整形 (色が付いたり、クリックで折りたたみを展開できたり) されるかは、その JavaScript 実行環境 (Chrome や Firefox、Node.js など) によりますが、大体どの実行環境でも似たような見た目で出力されるはずです。

f:id:mizdra:20210502225505p:plain

複数行に渡る出力を1行にまとめたい

実は console.log は複数の引数を渡せるようになっていて、以下のように書くこともできます。一度に複数の値を渡すと、それらの値は一緒の行にまとめられて出力されるので、1回の呼び出しで複数行出力されて見にくいという不満を解消できます。便利ですね。

function sum(nums, acc = 0) {
  console.log(nums, acc);
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

f:id:mizdra:20210502230145p:plain

ここまではよくある tips ですね。

変数名も出力したい

出力する変数がだんだん増えてくると、出力の何番目がどの変数だったのか判断が難しくなります。当然変数名も一緒に出力して、どの値がどの変数のものかがひと目で分かるようにしたくなってくると思います。

そうした状況でオススメなのが、次のテクニックです。

function sum(nums, acc = 0) {
  console.log({ nums, acc });
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  return sum(nums.slice(1), acc + nums[0]);
}

f:id:mizdra:20210502230518p:plain

console.log の引数リストを { ... } で囲うだけです。これだけで変数名も一緒に出力されるようになります。簡単ですね。

変数名が出力される仕組み

「何でこれで変数名が出力されるの?」という疑問を持った方も居るともうので、軽く解説します。先程のコードは Shorthand property names という、オブジェクトを初期化するためのシュガーシンタックスです。ES2015 で導入された比較的新しい *2 記法です。以下の2つのコードは等価になります。

console.log({ nums, acc });
console.log({
  nums: nums,
  acc: acc,
});

つまり先程のコードは、変数 nums の値を nums という名のプロパティに、変数 acc の値を acc という名のプロパティに持つオブジェクトを console.log で出力していた訳ですね。変数名を出力していたというよりは、たまたま変数名と同じ名前のプロパティ名が出力されていて、変数名が出力されているように見えていた、という感じです。

別解: degugger; を仕込む

結構忘れられがちなのですが、専用のデバッガを使うという手もあります。変数名の表示はもちろん、スタックトレースの表示やステップ実行ができたりと、print デバッグよりもずっと高級なデバッグ体験が得られます。もし高級なデバッグ体験が欲しければこちらを使うのが良いでしょう。

デバッガを利用する最も手軽な方法は、 debugger 文です。変数の値を覗き見したい行に debugger; と書いて Chrome などで実行すると、その行に到達した瞬間にデバッガが自動で起動して、実行が一時停止します *3。後はデバッガのメニューから変数の状態を見たり、ステップ実行してみたりと好き放題できます。

f:id:mizdra:20210502234958p:plain

デバッガは利用するための環境構築が大変なイメージがあり、敬遠されがちですが、便利なのでスキを見て使っていけると良いと思っています。最近の VSCode でもエディタ組み込みのデバッガの開発に力を入れていたりと、どんどんデバッガの利用が容易になっているように感じています。debugger; くらいなら簡単に始められるので、是非使ってみてはいかがでしょうか。

おまけ1

実行環境がChrome devtools、かつ関数呼び出しの入力を監視したい時限定で、変数名を出力出来るわけではないけど、monitor 関数も便利だよと教えて頂いた。便利!!!

blog.pastak.net

おまけ2

サムネオチって言葉久々に聞いた気がする。

*1:解: 3行目で acc を足し忘れている

*2:といっても標準化されてから約6年経過してますが。

*3:何となく分かると思いますが、debugger; のある行に breakpoint を仕掛けたかのような挙動になります。

思考実験: Wasm が普及した未来を考える

…という題でサークル仲間とディスカッションして盛り上がっていた。面白いアイデアが色々出てきたので、たまには正確性を無視して思い思いに書いてみる。裏取りは全くしていない。

  • もし Wasm が普及したら、.wasm に含まれる ホスト言語 (Wasm のコンパイル元の言語) のランタイムサイズが問題になって微妙な未来がやってきてしまうのでは
    • どういうことか:
      • アプリケーションというのは、ユーザコードとランタイムから構成される
      • 例えば Hello World を出力する C++プログラムがあったら、printf で Hello World を出力するコードがユーザコードで、printf の実装を提供したり *1、実行時例外の処理機構を提供するのがランタイム
      • アプリケーションを動作させるには、ユーザコード以外にも様々なコードが必要になる
      • 当然 Rust にもランタイムと呼ばれるものがあって、標準ライブラリや allocator 、async runtime などがそれに該当する *2
      • これが思いの外に大きくて *3、.wasm のサイズが許容できる範囲を超えてしまうのでは、という考え
    • React のランタイムサイズが大きくて困っている、みたいな話と同じ
    • 仮に React for Rust みたいなものが誕生したら、React のランタイムサイズと Rust のランタイムサイズの二重苦がやってくる、みたいな
    • Blazor はそんな感じだと聞いている (C# のランタイム + .NET のランタイム)
  • 何か解決策あるだろうか
    • ランタイムをブラウザに bundle させるとか *4
      • Rust ランタイムをブラウザに bundle させる、みたいな
      • 真の zero-runtime binary を実現できるが、Wasm は色んな言語に対して開かれるよう設計することになっているはず *5 なので、Rust だけ優遇することはなさそうな気もする…
      • Rust のランタイムそのまま bundle すると、ブラウザ側で Rust のランタイムの後方互換性を一生維持しないといけなくなる問題もある
        • ex: typeof null === 'object'
    • やるとしたら色々な言語のランタイムの共通部分をブラウザに bundle させる、みたいな形になりそう?
      • 分かりやすいのだと GC とか
      • 現に Wasm に GC 入れる proposal が出ている
      • まあでも切り出し可能な共通部分そんなにあるのかな…
        • コンパイラ基盤あんまり詳しくないので合ってるか知らないけど、exeption handing する runtime とか、async runtime とかはありそう
      • Reference Types proposal とかもそう
        • Wasm <=> JS のグルーコードを減らせるはず
    • あとはランタイムをある CDN から配信するようにして、色んなサイトで同じ CDN からランタイムを DL するようにするとか *6
      • CDN で配信された jQuery を import すれば色々なサイトでキャッシュを共有できて最高、みたいな取り組みに近い *7
      • CDN がパフォーマンス・可用性の観点で十分信頼できるならそういう形も良さそう?
  • 一方 JS はブラウザに全部ランタイムが bundle されていて、サーバからはユーザコードだけを配信すれば良いので、めちゃめちゃズルい
  • まあ Wasm が盛り上がるの少なくとも 5 年後だろうし、5 年も経てば世の中の通信環境やハードウェアのスペックが向上して、許容できるバイナリサイズも今とだいぶ変わってそう (本当に?) *8
    • あと Wasm はブラウザ側でのコンパイル速度や実行速度の観点で優れているので、そう簡単に比較できないという話もある
  • ところで今 Rust が Wasm をターゲットとする言語として注目されているけど、Rust はそもそもシステムプログラミング言語であって、JSer からしたら取っつきにくいという話がある
    • GC 無い / lifetime・借用辛い
    • もうちょっと JSer に馴染みがあって、GUI を書きやすくて、Wasm-first な言語がそのうち登場しても良さそう
    • こういう設計思想の言語があると良いんじゃないか
      • GC がある
        • 当然 stop the world になる時間が短くて、60fps 出せるようになっている
      • 最小限のランタイム
      • JS との binding が容易
        • JS の Object をその言語でも第一級で利用できる、とか
        • JS から触りやすい ABI が露出している、とか
        • Java <=> Scala が参考になったりしないかな
      • 宣言的 UI の構築が容易
        • 例えば JSX や algebraic effects が言語組み込みになっている、とか
        • algebraic effects を組み込みにしたら何が嬉しいのかは知らない
        • エラー時のスタックトレース見やすくなったり、他の言語機能と良い感じに連携取れたりするんじゃないかと思ってるけど、学術的な知識が無いので勘でものを言っている状態
        • 昔似たようなこと考えてた: https://twitter.com/mizdra/status/1347501080171565059
        • 宣言的 UI に bet して後悔しないかは考えもの
      • 標準ライブラリが WHATWG や W3C、ECMAScript で定義されている API の wrapper になっている
        • ブラウザに bundle されている API を利用するので、標準ライブラリのコードがランタイムを占める割合が少なくなる
        • 時刻 API が Temporal の wrapper になっている / ネットワークリクエストする API が fetch の wrapper になっている、とか
        • あと WASI
      • サーバサイドでも動く
        • クライアントサイドで GUI 記述するのに使い始めたら、絶対 SSR したいという需要が出てくるので
    • こういうの登場したら面白そう

*1:libc とか

*2:https://prev.rust-lang.org/ja-JP/faq.html#does-rust-have-a-runtime

*3:本当に思いの外大きくなるのかは知らない。仮にそうだとして、の話。

*4:これは僕ではなくサークル仲間のアイデア

*5:あってる?

*6:これは僕ではなくサークル仲間のアイデア

*7:懐かしい

*8:5年前と今を比較してどうだったか、を考えようとしたけど、5年前はネットワークの速度のことなんか考えていなかったので分からなかった

ポケットモンスター・ポケモン・Pokémon・は任天堂・クリーチャーズ・ゲームフリークの登録商標です.

当ブログは @mizdra 個人により運営されており, 株式会社ポケモン及びその関連会社とは一切関係ありません.