mizdra's blog

ぽよぐらみんぐ

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 を見ていたら目に入ったので、調べて書いてみました。

*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年前はネットワークの速度のことなんか考えていなかったので分からなかった

GitHub Packages を npm install するための手段あれこれ

概要

  • 社内向けの npm packages を publish するのに GitHub Packages が便利
    • GitHub 内で完結してお手軽 & Actions を使って自動リリースフローを作りやすい
  • しかし GitHub Packages に上がっている npm packages を npm install するには少々手間がかかる
  • 従って、GitHub Packages に上がっている npm packages を使うプロジェクトでは、npm install をするあらゆる場面で、PAT による認証を挟んでおかなければならない
    • local で docker-compose up する時
    • actions/jenkins 上で Node.js の CI を走らせる時
    • リリース用の docker image を CI 上で作る時
    • ...などなど
  • この時 PAT をどう管理するか、というのが意外と悩ましい (と個人的に思っている)
    • 何か楽な方法がないか色々考えてみる

案1: 社内の共有GitHubアカウントで発行した PAT をリポジトリの .npmrc に埋め込んでおく

共有トークンをリポジトリにベタ書きし、リポジトリに直接 commit してしまう、という案。

.npmrc:

//npm.pkg.github.com/:_authToken=<社内の共有GitHubアカウントで発行したトークンをここに貼る>
@example-corp:registry=https://npm.pkg.github.com/
  • メリット
    • npm は npm install 時に自動でカレントディレクトリにある .npmrc を見て認証してくれるので、これさえ書けば local だろうと actions だろうと jenkins だろうと、全ての場所で動く
  • デメリット
    • リポジトリにトークンが commit されてしまう

案2: プロジェクトを動かしたい人ごとに PAT を発行して、.npmrc に直書きしてもらう & GitHub Secrets などの仕組みを使う

共有トークンを commit するのを避けて、個人で PAT を発行して、それを使ってもらうという案。

.env などに個人で発行した PAT を埋め込んでおき、dotenv などと組み合わせて以下のように .npmrc から読み込む。

.npmrc:

//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
@example-corp:registry=https://npm.pkg.github.com/

これだけだと local でしか PAT が設定されず、actions や jenkins 上では認証ができないので、適時 GitHub Secrets や Jenkins の Credentials を使って PAT を環境変数 NPM_TOKEN に設定するようにしておく。

サードパーティツールの変数およびパスの設定 - Cloud 暗号化されたシークレット - GitHub Docs *1

  • メリット
    • トークンがリポジトリに commit されない
  • デメリット
    • プロジェクトを使いたい個人ごとにトークンを発行しないといけない
      • 一度セットアップすれば良いとはいえちょっと手間

package が公開可能な場合

社内向けだけど OSS にして良いとか、公開可能な場合は他にも取れる手段がある。

案3: npmjs.com に公開する

  • npmjs.com に public で package を publish する方法
  • デメリット
    • 公開可能な package でないと利用できない
    • npmjs.com のアカウントを作るのがちょっと手間
      • まあ一度作ってしまえば良いのだけど

案4: git 形式で npm install する

  • 予め GitHub リポジトリを公開しておき、 npm install git://github.com/example-corp/xxx.git#v1.0.0 でインストールする方法
  • デメリット
    • 公開可能な package でないと利用できない
    • 事前ビルドが必要な package との相性が悪い
      • というのも、通常 Git リポジトリにはビルドの成果物が commit されていない & git 形式の npm install は単に git に commit されているファイルをコピーして node_modules 配下に置くだけで、npm run build などはしてくれないため
    • npm update / yarn upgrade や renovate でアップデートできない *2
      • データソースが npm repository でないため、標準的なツールを使ったアップデートができない

おまけ: @example-corp:registry=<URL> の注意点

.npmrc に記載する @example-corp:registry=<URL>@example-corp をスコープとするパッケージ全てを <URL> から取得する使用になっています。つまり @example-corp/package-a, @example-corp/package-b, ... は全て <URL> から取得されます *3。その挙動の影響で「GitHub Packages にホストされている @example-corp スコープのパッケージ」と「npmjs.com にホストされている @example-corp スコープのパッケージ」を共存させることができないという既知の問題があります。public package は npmjs.com、private package は GitHub Packages、みたいな運用をしているとハマります。もしそういう使い方をする予定であれば、GitHub Packages or npmjs.com のどちらかに寄せる、という対応を別途検討したほうが良いでしょう *4


どの方式も一長一短あって難しい。皆さんはどうしてますか?

*1:Organization Secrets というやつがオススメです

*2:npm update / yarn upgrade は手元で試して確認した。renovate も https://github.com/mizdra-sandbox/git-npm-package-test/issues/1 で試して確認した。dependabot は試してないので知りません。

*3:URL に無かったら npmjs.com から取得するといった fallback も一切ないので、URL にないパッケージのインストールは失敗する。

*4:GitHub Packages と npmjs.com でスコープ名を変えるという素朴な workaround もある

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

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