assert.CallTracker と must-call
この記事はリクルートエンジニアアドベントカレンダーの2日目の記事です。過ぎてるかもしれませんが、メンバーから脅迫されて書いてます。
assert.CallTracker
Node.js で experimental な API として assert.CallTracker
がv14 で追加されました。
この機能は呼び出された回数を検証するという機能を持った新しい assert 関数です。
コールバック関数をテストする際に使える便利関数です。機能としては地味ですが、使いこなせると便利なので紹介します。
const assert = require('assert'); const tracker = new assert.CallTracker(); function func() {} // 一度だけ呼ばれることを期待 const callsfunc = tracker.calls(func, 1); // ここで呼び出す。 callsfunc(); // tracker.verify を呼ぶことで何度呼ばれたかを検証する。 // これはプログラムが終わるときに1度だけ呼ばれてることを検証する。 process.on('exit', () => { tracker.verify(); });
これだけ見ていると何が便利かわからないかも知れませんが、テストを書いた時にコード内の不具合で assert が呼ばれずに終わってしまう状況を防ぐことができます。
const assert = require('assert'); const tracker = new assert.CallTracker(); const http = require('http'); const server = http.createServer(); // 一回だけリクエストが来ることを期待 server.on("request", tracker.calls((req, res) => { assert.strictEqual("/", req.url); res.end("Hello World!"); }, 1)); server.listen(3000); process.on("SIGINT", () => { // もしも一度もリクエストが来なかったらここで検証失敗が出る tracker.verify(); process.exit(); });
とまぁ、ここまでが CodeGrid で書いた内容です。
ここから蛇足と言うか多少細かすぎて伝わらない話をしようかと。
mustCall
この機能もともとどこから来たかと言うと、 Node.js のコアのテストでよく書かれてた common.mustCall っていう test utility 関数から来ています。
https://github.com/nodejs/node/tree/master/test/common#mustcallfn-exact
これを一般向けに公開したのがこの機能です。 Node.js の中では Event を使ったテストが多く、『イベントが呼ばれなかった時に検証が行われずそのままパスしてしまう』というのを防ぐモチベーションで作られていました。
今では実は jest とかでもやっちゃうときはやっちゃいますよね。
余談ですが、この機能はめっちゃ便利だなーと思っていたので昔 must-call っていうライブラリとして公開していました。
今となっては assert.CallTracker で置き換えるか、 npm モジュールにしなくても数行で書けると思うので、役目を終えたライブラリになりました。
他にも test common な utility にある便利な関数はあると思うので、意外とこの辺りは公開すると便利かもしれませんね。
https://github.com/nodejs/node/tree/master/test/common
急遽書いたので内容薄いですが、もう少し濃いやつはまた次回。
【翻訳】Date and Times in JavaScript
この記事は littledan から依頼を受けて、翻訳しています。広く Date and Times の JS プロポーザルについて意見がほしいとのことです。 意見は以下の場所にポストできます。
原文: blogs.igalia.com
tl;dr:
Temporal のプロポーザルについてフィードバックを求めています。 Polyfill を試したら、サーベイの回答を送ってください、ただしまだ本番環境では使わないでもらいたいです。
JavaScript の Date クラスは壊れています、しかし Breaking the Web を起こさずに修正する方法はありません。風のうわさでは、 Date クラスは 10 日で作られた JS Engine のhackに含まれたもので、 java.util.Date
に基づいたものと言われています。しかも java.util.Date
は摩訶不思議なAPIだったため、 1997 年には deprecated になっていて、今ではより良い API のものが使われています。 Java が歩んできた歴史はそのまま JavaScript も同じ歴史を辿ろうとしています。 built-in のDateクラスはとても使いにくいものとして残り続けることでしょう。
2, 3年前から、とある proposal
が開発され始めました。その proposal
は新しくグローバルに Temporal
という JavaScript のオブジェクトを生やすものでした。 Temporal
は robust で modern な API 設計で、日付、時刻、タイムスタンプを扱えるものになっています。さらに Date
では不可能だったり困難だったこと下記のことが簡単にできるようになっています。
Date
では不可能だったり困難だったこと:
- Timezonesをまたいだ日付変更
- 夏時間を考慮した上での時刻の加算、減算処理
- 日付だけ、時刻だけでの処理
- non-Gregorian カレンダーの時間を扱うこと
さらに Temporal
は "just works (うまく動く) " なデフォルトの動作を持っていますが、コーナーケース(オーバーフロー、曖昧な時刻の解釈、などなど)に対応するために opt-in での制御ができるように設計されています。この proposal の歴史と「なぜ Date そのものを直せないか」をさらに知りたければ Maggie Pintのブログを読むことをオススメします。
Temporal
の能力についていくつか例を挙げます。cookbookを見てみてください。 これらの例の多くは legacy Date クラスには困難なものが多いです。特に timezone の取り扱いに関しては Date では難しいものが多いです。 (ただし、このポストでは例を上げてますが、コードに関しては変更される可能性が大いにあります。)
この proposal
は現時点では TC39 のプロセス内で Stage 2 になります。我々 Temporal Interest Group はすぐに Stage 3 に持っていこうとしてます。これまで Temporal の機能セットやAPIに関して長い間活動してきました。そしてついに、我々は 必要十分な機能が揃っており、 API の妥当性を信じる所まで来ています。もちろんホワイトボード上の議論だけで良いAPIの設計はできません。 JavaScript 開発者コミュニティに試してもらい、実際に要望を満たしているかを確認する必要があります。
まだまだ初期フェーズなので、 drastic な変更も必要になったらする可能性があります。ただしそれはフィードバックに基づいたものです。だから、どうか Temporal を試した上で我々にフィードバックを送ってください。
Temporal の試し方
カジュアルに試すのであれば、インタラクティブなpromptを使えば簡単です!ブラウザを開いて、 API ドキュメントページに行ってください。APIドキュメントもしくは cookbook のページのどこでも、ブラウザコンソールから Temporal をロードされた状態で試すことが可能です。 Example のコードを実際に実行して試すことが可能です。もしくは RunKit 上でも試すことが可能です。
もしくは、あなたは Temporal を使った小さなテストプロジェクトを構築するような踏み込んだ評価に興味があるのかもしれません。あなた方の貴重なプロジェクトの時間を奪うようなことになってしまうかもしれませんが、その方法が最も価値のあるフィードバックになるでしょう。踏み込んだ評価をしてくれることに深く感謝します! npm 上に既に Temporal API の polyfill をリリースしています。 npm install --save proposal-temporal
をあなたのプロジェクトの中で実行すれば、 Temporal をあなたのプロジェクトの中に取り入れることができます (const { Temporal } = require('proposal-temporal');
) 。
しかし、アプリケーションの本番環境では polyfill は使わないでください! proposal はまだ Stage 2 です。さらに polyfill は 0.x version です。それは API が変更される可能性が高いことを示しています。我々はあなたがたからのフィードバックを受けたらそれに基づいてAPIを変更し続けていこうとしています。
フィードバックの送り方
再三申し上げましたが、Temporal を使った上で、あなたからの意見を聞きたいです!一度でもトライしたら、短いサーベイに記入してください。もしよければ、あなたへのコンタクト情報を教えて下さい。教えていただければ、質問にはフォローアップして回答を差し上げます。
我々の issue tracker も気軽に開いてください! 我々はあなたがサーベイに記入したかどうかに関わらず提案を喜んで受け入れます。 issue 内に書かれたフィードバックは閲覧できます、また賛成意見には thumbs-up を送ったり、反対意見には thumbs-down を送ることもできます。
参加していただけるのであれば、謝辞を述べます。私達が受け取った全てのフィードバックは proposal を Stage 3 に上げるため、またブラウザ内で Temporal を利用できるようにするための正しい決定をするのに役に立てていきます。
番外編 (筆者が試したところ)
というわけで、 Temporal
を試してみた。まずすごいと思ったのは公式が用意している cookbook が気合が入ってる。
9種類の大きなユースケースを紹介した上で、それぞれのサンプルコードが書かれている。
const future = Temporal.Date.from("2020-08-28") const today = Temporal.now.date() const days = future.difference(today).days; // 31
これだけで、日付からの差分が出せるところは嬉しい。あとは細かく、 Calendar形式で1日追加するとかの細かい操作や timezone の変更などの操作が直感的にできる。 <input type=date>
な field にも直接入れることができたりと細かいニーズに対応できている。さすが次世代 Date といったところか。
const datePicker = document.getElementById('calendar-input'); // カレンダーのDate Picker const today = Temporal.now.date(); datePicker.max = today; // 現在日付をmaxにして、過去の日付しか選べないようにする。 datePicker.value = today;
若干使いにくそうだな、と思った所も上げる。
使いにくいと思った所その1, epochtime を取る所
// epochsecond を取る時は Date.now() / 1000 だったが、今回からはこうなる。 const now = Temporal.now.absolute(); now.getEpochSeconds();
まず、長い。長いけどAPIはわかりやすい。一方で手軽さがないので、 Date.now() / 1000
を手癖で覚えてるとそっち使ってしまいそう。ただDate.now()/1000
と比較すると、小数点のケアをしなくていいので、楽。これからは Temporal の方法を覚えていきたい。
一方で absolute instance を一度作ってしまうと次から getEpochSeconds()
を呼び出しても同じ値になってしまう。一回はこれ絡みの不具合が起きそうな気がする。
const now = Temporal.now.absolute(); setInterval(() => { console.log(now.getEpochSeconds()); // 毎回同じ数 }, 1000);
最新の値を更新したければ毎回 absolute から取るしか無さそう。
setInterval(() => { const now = Temporal.now.absolute(); console.log(now.getEpochSeconds()); // 毎回違う数 }, 1000);
epoch seconds を取るときはロギングする時とかが多い気がするが、それを毎回 Temporal instance を作っては捨てるようなことしてもパフォーマンス的に問題ないのかとかは一瞬気になったが、実装次第だろう。個人的にはinstanceを作らなくても epochtime くらいならサクッと取れるようにしてほしい。 Temporal.getEpochSeconds()
のように。
Temporal.xxx.from でパースできない文字列を食わせると例外がthrowされる。
from
関数は本当に便利。日付だけで扱いたければ Temporal.Date.from(2020-01-01)
で日付のみの Temporal instance を作ってくれる。また、時刻だけで扱いたければ Temporal.Time.from("12:10")
で 12時10分 のTimeにしてくれる。また、 日付のオブジェクトを渡せばそれに合わせて instance を作ってくれる。
ただし、 ISODate 形式(YYYY-MM-DDTHH:mm)に反する文字列だと例外が throw される。また、ありえない日付(13月32日のような)ものを食わせると、object形式とstring形式で動きが異なる。
Temporal.DateTime.from("2019-12-10T12:10") // from 関数は文字列を parse して Temporal instance にしてくれる。便利。 Temporal.DateTime.from({ year: 2019, month: 12, day: 1 }) // こっちもわかりやすい、object としてpropertyつけて渡せば良い。 Temporal.DateTime.from("2019-12-10a12:10") // もしも invalid な文字列だと RangeError: invalid ISO 8601 string: 2019-12-10a12:10 Temporal.DateTime.from("2019-13-32T12:10") // 文字列にありえない月や日が入ってても RangeError がthrowされる。 Temporal.DateTime.from({ year: 2019, month: 13, day: 32 }) // object で渡したときは throw されない、 2019-12-31 などの一番近い日付に補正される。 Temporal.DateTime.from({ year: 2019, month: 13, day: 32 }, {disambiguation: "reject"}) // disambiguation を reject にすれば RangeError が throw される。
ここは、若干動きがわかりにくいかも。。。文字列で渡したときは disambiguation
が必ず "reject" のようになるが、object で渡したときは disambiguation
が "constrain" になる。 polyfill の問題かな。
ちなみに、これまでの Date だと invalid Date
という number で言う NaN
のような扱いになっており、例外は throw されなかった。
new Date("2012-12-11T12:20") // 2012-12-11T03:20:00.000Z new Date("2012-12-11a12:20") // Invalid Date
どっちが良いかは諸説あると思うが、いままで手軽に扱っていた Date のマインドセットのままでは使えない。ちなみに moment
とかも同様にパースできなくても例外を投げたりしない、 NaN 扱いになる。パースできない時に NaN 扱いだった Date ときちんと例外を処理しないといけない Temporal のどちらが良いかは筆者には判断付かないが、 JavaScript はこれまでの歴史上、例外の取り扱いが難しいので、この辺の議論は是非参加してみたい。
まだまだありそうだけど、翻訳を先に出したかったので、ここまで。後は読者の方々も試してみてほしいです。試してみたら、サーベイに回答しましょう。
web.dev live 2020 を聴講した
今年はコロナの影響でいろんなイベントがオンラインになったり、中止になったりしてますが、 web.dev live 2020 が7月初頭にやっていたので、聴講してきました。
その中でも面白かったものについていくつか紹介します。
Day 1
ほぼ Core Web Vitals についての話でした。
以下のトークが面白かったです。
- What's new in speed tooling
- Optimize for Core Web Vitals
Core Web Vitals についてはもう既にたくさん資料があると思いますが、一応解説しておきます。
Core Web Vitals
初期表示の新しい指標です。去年くらいからずっと Chrome Dev Summit とかでは言われていて、既にLighthouse をはじめとして、色々なツールでサポートされています。Largest Contentful Paint(初期表示範囲内で一番面積の大きいコンテンツが描画されるまでの時間), First Input Delay(最初に入力可能になるまでの時間), Cumulative Layout Shift (初期表示された後にレイアウトがずれた時のズレ幅)の3つを指標としています。
一日目はこの指標の説明とどうやったら良くなるかが非常にたくさん描かれていました。
What's new in speed tooling
じゃあ Core Web Vitals はいいとして、『どうやってツールを使ってそれらを把握していくのが良いのか?』というのが語られてました。
上記画像の通り、基本的にはどんなツールでも Core Web Vitals は取得できます。一方で、それぞれ特徴があるので、使い分け方についても解説します。
- 本番環境では Search Console で警告が来るのでそれのレポートを見て、
- Page Speed Insights で問題を深掘って発見し、
- Lighthouse/DevTools でローカルの高速化をはかり、
- web.devのガイダンスを読み
- Lighthouse CI でレグレッションを防ぎ、
- Chrome User Experience Report でフィールドデータも確認する
という流れを継続してやるのが CWV のやり方と流れです。
便利だな、と思ったのは、 Dev Tools 上で Cumulative Layout Shift 違反が起きている箇所を特定できるところですね。これがあれば、CLS違反になっている箇所をどうやって修正するかを確認できるので、便利そうでした。
また、2020年までは上述した threshold にするものの、一年おきに水準や方法は見直していくという事でした。
Optimize for Core Web Vitals
具体的にどうやって Core Web Vitals を上げていくかというテクニックめいた話でした。 実際に Chloe というファッションブランドのサイトを Core Web Vitals に基づき改善していった話をしてました。
CLS の改善
先程の説明の通り、 Cumulative Layout Shift (CLS) は初期表示の後にレイアウトが変わってしまう問題です。馴染みやすい問題として、「ガタンッ」問題 とも呼ばれているものです。ロード前のコンテンツがロードされることで高さが変わったり、幅が変わることでレイアウトが変わってしまう現象を指しています。
見た目にも悪いですし、何より押そうとしたらレイアウトがずれてリンクが押せなくなったりと実害が出る問題です。 このガタンッ問題をどうやって防ぐかという話を解説してくれていました。
最初に画像に関しては width, height を img タグに設定しておき、後から高さや幅が変わらないようにしておきます。 もしもできるなら、画像の背景色に合わせた類似色を表示エリアに事前に表示することも推奨されていました。
背景色を画像に合わせて塗っておくことで、画像が表示される前であっても見た目やレイアウトが損なわれません。
また、後からコンテンツを表示された領域に挿入することは基本NGです。しかし、広告やプロモーション等でどうしても後から入るケースも考えられます。その時も高さに関しては事前に領域を確保しておきましょう。
LCP の改善
Largest Contentful Paint は先程の説明の通り、『初期表示領域における、最大面積を持つコンテンツのロードにかかる時間』を指すのですが、対象は所謂ヒーローイメージであることが多いので、画像のチューニングが紹介されてました。
基本的に画像はCDNを利用し、webpをデフォルトで使ってほしい点、また本質的に必要なサイズの画像しか用意しない事で画像のサイズをカリカリに縮小した状態で表示させることで初期表示の改善に取り組んでいきました。
また、Server Push を使った改善も取り組んだことを紹介していました。 Naive な Server Push 自体は Cache Aware ではないため、 HTTP/2 の Server Push をそのまま活用したわけではなく、 Akamai の Server Push を使ってインテリジェントな Push を実現したという解説がされていました。
結果として大幅な LCP の改善ができたことを紹介していました。
Day 2
ほぼ PWA の話でしたが、筆者が興味を持ったのは以下の2つでした。
- What’s new in V8
- Zoom on Web
What's new in V8
ほぼほぼ知ってる話でしたが、一応 recap しておきます。
ES 新機能
- Nullish Coalescing
- Optional Chaining
- WeakRef
が追加されました。
それぞれ説明するまでもないかな、と思ったので解説は割愛します。WeakRefのユースケースをイベントリスナーの廃棄に使うっていうケースが紹介されてて、そこだけ面白かったです。
Memory Improvements
メモリの消費量を抑える新しい仕組みについて 2 点解説されていました。
- Pointer Compression
- JIT-less mode (V8 Lite)
Pointer Compression
アイデア自体は凄くシンプルで、64bit アドレスでメモリアロケート先のポインターを持つのではなく、base address は64bitのアドレスにしつつ、そこからのoffsetを32bitアドレスで持つことで全体的にアドレスの管理をするメモリの消費量を減らすというものです。
内容自体は凄くシンプルですが、結果としては、平均で40%程度削減されている結果が見込めるという事でした。
JIT less mode
V8 は多くのメモリ確保をしていますが、その内訳を見ると、JITに必要なメモリが含まれていることがわかります。
JIT の機能をオフにしてしまえば、いくつかのメモリを持たなくて済むため、メモリ消費量を抑えることができます。
まだこれは試験的な機能ですが、JIT less にすることでメモリ消費量は10-25%程度の削減が見込めるということでした。
Zoom on Web
Zoom の動画チャット機能を Web で提供するため、 Chrome と Zoom で組んで色々な機能のリリースやトライアルをやっているという話でした。
WebAssembly on SIMD, WebTransport, WebCodec の3つの技術を紹介し、それぞれを組み合わせていく話が紹介されていました。
WebTransport は UDP レイヤでのデータ通信を実現し、それを使って動画を転送しつつ、WebCodec で動画のデコードとエンコードを行い描画を行い、 WebAssembly SIMD を使って背景の透過などの動画エフェクトを掛けるという紹介がされていました。
Day 3
Privacy Sandbox セキュリティ 周りの話がほとんどでした。
そこまで目新しい話はありませんでしたが、 COOP, COEP の話は抑えておいて良いと思います。
また、 Signin Form Best Practice の話は割と面白かったです。
Cross Origin Opener Policy, Cross Origin Embedder Policy
Cross Origin 、つまりクロスドメインにおいて、オリジンの情報をいかに守るか、という話です。
COOP は通常 window.opener から開き元になる origin の DOM が参照変更できることを避け、 window.opener がただの null になります。
COEP は Cross Origin Resource Policy が設定されていないリソースの読み込みをさせないようにリソースの読み込みポリシーを見て表示可否を制御できるポリシーです。
設定すると許可されていないリソースの読み込みをblockさせることができます。
Signin form best practice
おそらく Web エンジニアが幾度となく作ってきた サインインフォームについての話でした。
- 意味のあるHTMLを使って div とかだけで表現するな。
- button は button として使うこと、 button を "送信" みたいなボタンにせず、 "Sign-in" などの意味のあるラベルを用意すること
- 登録用パスワードを2個書かせたり、登録用メールアドレスを2つ書かせるような double up form にするな
- autofocus を使ってどっから書くか明確にせよ
- password の type は使うが、見たい時にはマスクが外れるようにせよ
- input type は email や tel など入力フォームに合わせてちゃんと設定せよ
- inputmode の利用も考慮すること
- Sign in button が入力キーボードで見えなくなることを避けよ(押せなくなるから)
- Sign up のときやパスワード再入力時に name を new-password や current-password でわけて表現すること
などなど、ものすごくたくさん紹介されていた。
Recap
「Web Vitals で初期表示の改善の話がされ、 V8 や WASM の話でアプリケーションの能力を広げ、 Privacy をきちんと保護する」というなんとなく全体的に今推してる機能がわかる話だった。
2020年の JSConf.jp は中止にします
表題の通り、 2020年の JSConf.jp は中止にします。2021年の開催を目指します。
TC39とのコラボレーションとかも検討していたのですが、楽しみにしていた方々、大変申し訳ございません。
いろいろな葛藤はありましたが、 JSConf.jp の前身になった Node 学園祭のときから「豪華なスピーカーに会えること」「Node.jsやTC39の中にいる人と直接交流できること」「知見が共有できる場所」というのを大事にしてきたのですが、オンライン開催ではその意味が半分以上失われてしまうと思っており、 JSConf.jp は開催しません。
ただし、少し形を変えて、オンラインで開催する小さなカンファレンスは開催するかもしれません(名前は TBD です、 JSHome とかにするかも?)。
よろしくおねがいいたします。
2019年振り返り
はじめに
なんかよく見たら 2年前の自分がちゃんと書いてるくせに 1年前の自分は書いてなくて、進化してるのか退化してるのかわからなかったので、今年からはちゃんと書こうと思いました。
yosuke-furukawa.hatenablog.com
会社
マネジメント
マネージャーも3年目ですね。なんだかんだ、メンバーにも恵まれてるなと思うことが多いです。 メンバーがアウトプットを率先してやってるので、エンジニアコミュニティとして大きく形成されているなと思います。エキスパート同士が互いを教え合い、また競い合いながら目的を達成していくのは見てて気持ちが良いです。
engineer.recruit-lifestyle.co.jp
背中を黙って預けておけるメンバーなので、特にマネジメントの仕事と言ってもキャリアの相談や仕事の相談に乗って一緒に考えることはしてますが、やってることはその程度といえばその程度で、細かい事は任せてます。「『お前はどうしたいんだ』ってよく言うのか」、と聞かれますが、「メンバーがどうなりたいか」と「メンバーにどうなっててほしいか」というwill と期待の話はよくします。逆に言うと日々のマネジメントはそれくらいしかしてないです。
R-ISUCON で 1 位に
エンジニアコミュニティといえば社内でやってるイベントの R-ISUCON で 1位取ったりしました。
嬉しかったのは R-ISUCON やってたら ISUCON の予選を3位で通過したことです。自分が通過したことももちろん嬉しかったのですが、 R-ISUCON をやっていた他のグループも通過していてレベルが上ってることを感じました。
yosuke-furukawa.hatenablog.com
関連会社の技術顧問になった
ニジボックスという弊社の関連会社の技術顧問も新規でやることにしました。
会社の技術的な質を上げるには、技術部分の底上げをすることとトップレベルを引き上げることの2つが重要だと思っています。
トップレベルを引き上げることは自組織のマネジメントで行っていますが、縁あって底上げをする役目も一緒に行うことになりました。といってもまだ10月からなったばかりなので、 JavaScript の研修を教えたり、そもそもコードを書く時に意識することを教えたりといった基礎的な所と意識的な所を教えています。
ニジボックスメンバー向けJavaScript Bootcamp2019 参加中! 技術顧問 古川陽介さん #Yosuke_furukawa を講師に丸一日プログラミングを学べるとあって、参加者の熱気もたかまっています🔥#ニジボックス #JavaScript pic.twitter.com/v3ScCBsSBO
— ニジボックス_UXデザイン (@nijibox_jp) 2019年12月23日
Chrome Advisory Board になった
Chrome にアドバイスする諮問委員会、 Chrome Advisory Board のメンバーになりました(正確には2018年から)。現時点では日本に 3 人の諮問委員会メンバーがいて、リクルートの他だと Yahoo! と Cyber Agent のメンバーがいます。活動としてはいち早く新しい Chrome の仕様を教えてもらって試して、使った結果をフィードバックするというのが主な仕事です。
AMP をやったり、 Next.js 使ったりしてるのはその辺があります。 Next.js に最近送った PR も AMP 系ですね。
そんな使い方があるのかーという、仕様の勉強にもなります。 Google のAMPの中の人達にこの手の活動を通じて気軽に質問できるようになったのは大きいですね。
next.js に出してた PR がマージされた。
— Yosuke FURUKAWA (@yosuke_furukawa) 2019年11月26日
Chrome Dev Summit で直接 Next.js の tim と AMP の Sebastian と話せたのほんと大きかったな。
この他にも Paul Irish に気軽に lighthouse の中身を聞いたり、実際にサービスを見てアドバイスもらったりできて良かったです。
Japan Node.js Association の活動
会社でやったこと以外にも自分が持ってる法人の活動も書いておきます。
JSConf.JP 開催
今年はなんといっても JSConf.JP 開催できたのが大きいです。
yosuke-furukawa.hatenablog.com
今年の前半からずっと JSConf をやってたし、日本で JSConf が開催できたのは日本の JavaScript コミュニティ全体にとって良かったと思います。トークの内容もJSコミュニティを代表するにふさわしい内容だったと自負しています。
ただこれまで JSConf EU とかで登壇依頼してたのですが、来年はやらないので新しい場所に行く必要があるかも。。
あと、来年はなんと言ってもオリンピックがあるので、前回みたいな外部の会場を抑えるのは難しいかもしれませんね。。。 今7, 8, 9月にやる予定だったイベントが軒並み9月後半、10月、11月にずれてきてるという話なので、会場の検索中です。
ただ間違いなく2020年も JSConf はやります。
個人活動
会社、自分の法人以外の活動も書いておきます。
登壇系
技術的な登壇はもとより、マネージャーや組織づくり系の登壇、エンジニアキャリア的な登壇に呼ばれましたね。
- Global CFP Day
- Developers Summit
- EOF 2019
- React Meetup
- Node学園
この辺に呼ばれた(自分が話した)感じですね。自分の中で新しいなと思ったのは Global CFP Day と EOF 2019 ですね。
Global CFP Day は「海外登壇にチャレンジするにはどうしたらいいか」、というテーマでめっちゃ面白いカンファレンスでした。
このカンファレンスで紹介された「Present! A Techie's Guide to Public Speaking」という本は 海外カンファレンスのテックトークとはどういうものなのかを紹介した本です。かなり面白いのでマジでおすすめです。
EOF 2019 は自分がマネジメントでやってたことをちゃんと言語化した形になりました。
あまりちゃんとした形で表明してなったけど、自分は勉強会と同じ考え方でマネジメントしてるんだなという発見になりました。
執筆系
以下の場所に記事を寄稿しました。
- Software Design
- CodeGrid
マイクロサービスのつなげ方の章で BFF の話を解説させていただきました。 また、 CodeGrid は Node.js v12 - v13 で変わることの話をさせていただきました。
後半のマジで忙しい時に2連続で執筆依頼が来たのですが、これを断っていたら今回執筆ゼロだったので受けてよかったです。
OSS 系
最近ちゃんとコードも書いていこうという精神で、 Write Code Everyday に挑戦しています。まだ4ヶ月が終わろうとしてるところですね。マネージャーになったけど、コード書いていないっていうのは嫌だったので、コードも書くし、マネジメントもやるし、っていう精神でやっています。
OSS で言うとリクルートのライブラリで、 Specter を作ったりしていました。
yosuke-furukawa.hatenablog.com
結構中身がわかってきたし、不具合も見えてきたので、 Next.js へのコントリビューションをもう少し本格的にやろうと思います。
Node.js core contributor としては活動が少し落ち着いてしまったので、もう少し復活させようと思います。
Podcast
fukabori.fm に出させていただきました。結構この活動がきっかけで周辺会社とも絡む機会を増やせると良いなと思っています。内容は社内ISUCONですが、かなりエンジニアコミュニティづくりに寄った話が多かった気がします。
Blog
この辺が盛り上がった気がしますね。もっと書きます。。。
yosuke-furukawa.hatenablog.com
yosuke-furukawa.hatenablog.com
来年に向けて
明らかにブログが減ってしまっているのでまずはペースを2017年に戻したいのと、2017年は海外登壇も多くやっていたので、それも復活したいです。 Node.js は QUIC や ES Modules を起点にまたコントリビューションできる所見つけてやっていきます。
そろそろ新しい言語をまた覚え直すとかやりたいし、その一方で他の人達が新しいチャレンジしてるのも見てて面白そうだなと思うので、好奇心を原動力に2020年もまたがんばります。
実践 Off the main thread
実践 Off the main thread
実際に Off the main thread をやりつつ、パフォーマンスチューニングをする際にどこに気をつけるべきかを今やっているので、それについて話します。
Off the main thread とは
JavaScript の処理は基本的にメインスレッドで実施します。JavaScriptの実行処理以外にも記述された内容を解釈するためのパース処理やGC処理もメインスレッドをブロックします。メインスレッドの処理が多いとUI jankと呼ばれるガタツキ、チラツキ、画面の固まりの原因になります。
UI jankが発生していると、ユーザーがクリックしたり、text入力をしようとしてから反応するまでの時間(Input Latency)が即時ではなくなります。
このUI jankを無くすために、なるべくメインスレッドを阻害する要因を減らすことが Off the main thread と呼ばれるトピックです。
Input Latency
ユーザが入力してから反応するまでの時間を指しています。 Off the main thread は前述の通り、このInput Latencyを少なくするための試みです。 メインスレッドでLong Taskを実行している間、特にレンダリングなどの重たい処理を実行している間は入力があっても即座に反応しません。
この場合、inputの処理はキューイングされ、後回しになります。
このレイテンシを最小限にしつつ、スムーズに動作しているように見せないと、固まっているかのように見えます。
「どれくらいで固まっているように見えるか」ですが、Google が出している、RAIL と呼ばれる原則では、100ms 以内に反応がないと、ユーザーは遅れているように見えるという指標があり、少なくともそれを満たす必要があります。 ただし、 lighthouse では 50ms 以内にしないと警告が出ます(アプリが表示し切るまでにさらに 50ms かかると推定されており、トータルで 100ms が Input に対しての Response になるため)。つまり、 Input Latency は表示し始めてから 50ms 、 入力が始まってから 100ms 以内に反応する必要があります。
何が Input Latency の遅延原因になっているか
Input Latency の遅延原因となっているのは主に JavaScript の実行だというレポート結果があります。
2300以上のサイトを調査し、最初の30秒間に入力処理をした際の処理の内訳を精査したところ、V8.Execute という JavaScript 実行中の処理で25% ~ 70%の時間が発生しています。
これを Web Worker
など、 Worker Thread で行えるようにし、 Input Latency を改善するというのが Chrome での topic の1つです。ただし、実際にやってみると、それ以前の問題が多く、実際にやればやるほど「Worker 以前に処理するべき問題」のが多く見つかります。
Worker 以前に処理するべき問題を放置して Worker Thread にしても Input Latency は改善できても処理そのものが重たいので、結局もっさりした動きになります。
実践 Off the main thread
今回の話はこの input latency を改善するため、 UI Jank をどうやって見つけるかとそれを改善するときのやり方、また実際にReact などの SPA を改善する時に見つけたありがちな問題について解説します。
UI Jank の見つけ方
Chrome だと DevTools の Performance タブから比較的簡単に見つけられます。 Performance タブで赤くなっている箇所では、 Jank が起きています。
この Jank を見つけたら、そこから中で何をやっているか見つけに行きます。この JSConf.JP のサイトでは hydration 処理と呼ばれる SSR と CSR の状態を同期する処理で Jank が起きています。
このケースで言うと、 Hydration と呼ばれる SSR と CSR の同期処理が走った後、React の props が変わり、 React の render 処理が中で走っています。結果として hydration 後に render が走ることで React の state が SSR 時のものと同期されることになります。一方で、 Input Latency は落ちる事になります。
ただし、『 UI Jank がある == 不具合』ではないので、これを必ずしも直さないといけないわけではありません。現時点ではどうしようもない処理もあります。 Hydration はその典型です。
よくある Jank のパターン
この hydration 以外にも発生するケーススタディがありますので紹介しておきます。
スクロールごとに重たい計算をしてしまうパターン
スクロールするたびにガタつくケースはだいたいコレですね。よくあるのは、スクロールしたタイミングで要素を変更する lazyload や 無限スクロールのような処理があるケースで、実装がまずいパターンです。
実装がまずい、と一口に書きましたが、「重たいオブジェクトを毎回生成する」、「scrollのたびに現在のviewportに要素が入っているかを毎回計算する」といった処理を指しています。
前者はオブジェクトのキャッシュかインスタンス生成を減らす事を検討し、後者は Intersection Observer
などの API で再実装が求められます。
また、 scroll イベントを毎回発生させる必要がないなら、 throttle, debounce といったイベントを間引くことで処理そのものが発生する回数を減らすことができます。
ちなみにスクロールに限らず、『頻度高く発生するイベントをトリガーに重たい処理をしている』事がそもそも問題になります。
他のケースとしては、 moment
のような時刻計算をするためのオブジェクトを表示されるたびに計算し、その計算のたびにインスタンスを作ってから時間の差分を計算しているケースもありました。
不要な props を渡してしまうパターン
ここからは主に React の話ですが、React に限らず、 SPA の view libraryではよく発生すると思います。
<Foo {...props} />
のように prop を全て展開して渡しているパターンや、 実際には使わないけど渡しているパターンですね。Reactの SPA の場合、UI Jank の8割方はこれです。この不要な props を渡しているところのバリエーションが多いです。
不要に Reconciliation といった差分検出処理が走ったり、 render が走ってしまい、 Jank が発生しやすくなります。
shouldComponentUpdate を書いて除外するか、きちんと渡す時に精査してから渡してあげれば発生しません。また、型を真面目に書いていればある程度防げたりもするでしょう。
関数をそのまま handler に渡してしまうパターン
こちらもよく見ます。以下のような状況ですね。
<Foo onClick={(e) => { ... }} />
これも、ある種の不要な props なのですが、趣が若干異なります。アロー関数に限らず、関数をその場で定義した場合、propsを渡す際に毎回 function オブジェクトが生成されて React に渡されます。こうなると関数オブジェクトそのものが毎回変わっているため、差分検出処理時に必ず差分がある事になります。
useCallback 等で callback をmemo化した状態で渡すか、 class componentにするなら、関数定義を constructor で定義するか static 関数にするかして、関数を再定義しない方法で handler に渡す必要があります。
どうにもならないときの処理として Worker を使う
処理をダイエットしたけど、どうしても減らない、とにかく重い計算処理を走らせる必要がある、という時に Worker を使いましょう。 Worker は Comlink 経由で使うと利用しやすいです。
実際に利用したときのシーンは AirSHIFT のブログに掲載されています。
// Cost計算する処理、 worker を comlink で promisified している。 import React from 'react'; import { proxy } from 'comlink'; // import the workerlized calc function with comlink const WorkerlizedCostCalc = proxy(new Worker('./WorkerlizedCostCalc.js')); export default function Cost({ userInfo }) { // execute the calculation in the worker const instance = await new WorkerlizedCostCalc(); const cost = await instance.calc(userInfo); return <p>{cost}</p>; }
ただ Worker にしただけで速くなるわけではありません。 Worker にすることで Main Thread を逼迫することは減りますが、そもそも無駄な処理がないかを地道に特定し、逼迫する時間を減らすことが一番重要です。
まとめ
Off the main thread だからといって、なんでも worker でやれば OK という話ではありません。自分のアプリ、サイトが遅い要因を特定し、ある程度性能を改善してからどうしようもない時に Worker を使いましょう。そうすると Input Latency だけではなく、処理全体が軽くなります。
Worker は速くなると言っても、 Main Thread の逼迫を軽減するためのものであり、使ったからといって手放しで高速になるわけではありません。一方で、使いこなせれば非常に強力な武器になります。 Comlink などのツールは使いこなせると良いでしょう。
また、仕様側でもよりよい API 設計を考えている話が去年から出ています。この辺も追っておくと良いでしょう。
また、 『UI Jank』の原因は JavaScript のメインスレッド逼迫だけではありません。 CSS や Layout 計算などの要因でも数多く発生します。
Jank の原因をまとめたサイトやチェックリスト等もあるので参考にしてください。
2020年の Node.js, 2025年の Node.js (Web Standard編)
この記事は Node.js Advent Calendar の 25 日目の記事です。
Node.js の 2020 年はどうなるのか 2025 年にはどうなっているのかを予想していこうと思います。 ちなみに、あくまで筆者の予想にすぎないです。こうなるという与太話みたいなものだと思っていてください。
Node.js のこれまでと今後
Node.js は進化を続けていますが、 2018 年に語った通り、その進化の方向は以下のような方向に流れています。
- Web Standard
- Performance
- Security
- Stability
今回は主に Web Standard の部分に限定して、これまでとこれからと更にその先を予測してみようと思います。
Web Standard 2020 / 2025
Node.jsは Web Standard に追従するという旨の発信はずっとしています。全ての Web Standard を follow するといっても実際サーバサイドには不要なものもあります。取捨選択をしながらなるべく追従をしていくという形になるでしょう。
ES Modules
ES Modules が Node.js v13.2.0 より、 ES Modules が experimental フラグが取れ experimental オプション無しで動きます(ただし、API status は experimental のまま)。相互運用性の方法もほぼ確定になりました。
Node の ES Modules 対応は 2020 年から徐々にその割合を伸ばし、 2025 年には利用されているモジュールが ES Modules : CommonJS が 3 : 7位の比率になる と予想します。 徐々に .mjs
や esm
に対応してほしいみたいな issue 増えてきましたね。
100% のモジュールが ES Modules になることは考えにくいですが、新しいモジュールや使われてるモジュールを始めとして、徐々に対応するような段階的にアップデートが行われるのではないかと思っています。詳しくは以下のエントリに書いたので詳細をご確認ください。
yosuke-furukawa.hatenablog.com
QUIC
QUIC の initial implementation の PR が作られました。
まだ ngtcp2 を持ってきて binding を作った段階で、 http3 の実装などは現時点ではまだです。 Node.js の QUIC 及び、 HTTP3 の対応は 2020 年で初期実装が完遂し、experimental
付きでリリース、 2025 年には quic, http3 ともに experimental flag
が取れる と予測します。
一方で http2 はどうなるんでしょうね。クライアントとしては生き残る気がしつつも、毎回クライアント側で http1.1, http2, http3 を呼びわけたくないので、統一的なAPIからコールされるようにならないと厳しいですね。
fetch
http1.1, http2, http3 のクライアント側の話で言うと、それらを統一的に呼び分けるために fetch
を入れようという話もあります。今だと require('http')
、 require('http2')
のようにロードするモジュールから分ける必要があります。
このPRはただの既存の npm にある node-fetch を core に入れてみようとしただけの議論用のPRですが、 ES Modules のような大物が終わった後は fetch の実装を進めるのかなと予測しています。
2020年には、、、議論は進んで fetch working group ができる、 2025年には http3 とともに fetch も入る と予測します。
Streams
fetch
とくると、気になるのは Stream
ですよね。 WHATWG Stream と既存 Stream は違うものなので別物として実装はされると思いますが、今の所進捗が見えません。リポジトリはあります。ただ議論が止まってますね。
新しくモチベーションがある人が引き継ぐ必要がありそうな気もします。 fetch
の議論を進めていく上で一緒に進んでほしいと思います。やる気があったら実装してみます。
ただこうなると、Joyee が書いたこのスライドにもありますが、 fetch
の完璧に compatible な実装を用意は難しいですね。また、 Browser API と完璧に同じにする必要も無いので、 Node.js なりに互換はあるが微妙に別物な fetch になりそうですね。
2020年はわかりません、おそらく今の感じだと進捗はないでしょう fetch working group
ができたらそこで議論再開ではないでしょうか。2025年には一旦 WHATWG Stream 無しで実装が進んでリリースされてそうだと予測します。
ちなみに Stream ですが、 新しく BoB Stream という提案がされています。これは今までの Push と Pull 型の混在した Stream ではなく、完全に新しい API で再検討された Pull 型の Stream です。
WHATWG Stream のものとはAPIも概念も異なります。こっちのが議論は進んでいるので入るのは WHATWG Stream よりは早そうですね。 2025 年には決着付いてるでしょう。
Web Assembly
Web Assembly 対応は v8 中心に進んでいる上に、 WASI 対応も v13.3.0 で experimental
flag 付きでリリースされました。
つまり、 Web Assembly 対応したモジュールを読み込むことができるのはもちろん、 WASI に対応した形でFile, Network IOを行うモジュールも使えるようになります。
ただまだまだ大本の WASM, WASI の仕様が unstable なのでどう転ぶかはわかりません。筆者がそこまで追いきれてない領域です。 2020年には徐々に今の native module が wasm/wasi を使って書かれるようになってくると予測します。ただ、大本の団体がまだ安定版を作れていないので、2025年でもまだ experimental
なままな気がしてます。
先日公開された TensorFlow.js が wasm を backend にするというニュースが流れてきて、徐々にモジュールのレイヤでは使われ始めてるという実感を感じます。
その他 Web Platform API はどうなるのか
crypto は Web Crypto を Node.js API に採用する議論は進められています。
crypto
と Web Crypto
も別物です。 Promise をベースとした Web Crypto でこちらはユーザーの API の利便性を考えられているものの、既存の crypto は性能をベースに考えられているので趣きが若干異なりますね。今の tls
モジュールが Web Crypto をベースに変わるとかはちょっと想像ができません。こちらももう少し議論が進めば入る可能性はあります。 2020 年には決着がつきそうですね。入るかどうかは判断が難しいですが、2020年に experimental リリースされそうな気はします。
一方で、 WebTransport, Web Codec等の対応はまだ議論すらされていません。ちなみにWebSocketsも core に入れるかどうかは忘れられてる気もします。。。
Web Platform の API を入れるかどうかは、「ユースケースが Node.js にとって妥当かどうか」、「ユーザーランドで実装が困難かどうか」、「既存の API との整合性」で決まるので、それぞれの判断をする議論が無い限り話は進みません。
まとめ
- ES Modules は 2020 年には増え続けて、 2025年には ESM : CJS で 3 : 7 程度の比率になる
- QUIC は来年 experimental でリリース (v14) HTTP3 はそこから議論が始まって RFC がでたら2025年にはリリースされてる
- fetch は来年一旦 working group ができる、2025年にはリリースされてる
- stream は多分来年も何も進まないが、 fetch working group で一緒に議論はされる、 2025 年には一旦 whatwg stream 無しでリリースされる
- WASM は既に初期リリースはされているので後は仕様側が安定したら徐々に進むはず、ただまだ仕様が unstable なままだと experimental flag は取れない。2025年にもまだ experimental な気がする。
- その他: Web Crypto が入りそう、ほかはまだまだ、ユースケースから募集中。
書いてて思いましたが、 2025 年って v24 とかですね。。。こう書くと全然予測ができない気もしてきました。やっぱり予測するものではなく、自分たちで予測を超えていく動きをしたいですね。
来年も Node.js に JSConf にがんばります。