2020年振り返り

はじめに

yosuke-furukawa.hatenablog.com

今年もちゃんと書きました。

マネジメントとシニアソフトウェアエンジニア

二足のわらじで4年目になりましたね。去年も書いたんですが、メンバーが優秀であるがゆえに二足のわらじができていると思っていて、それを4年目も継続できました。新しく何名か入ったおかげで非常に強力なフロントエンド体制ができてるなと思っています。

adventar.org

recruit-tech.co.jp

recruit-tech.co.jp

recruit-tech.co.jp

上記のブログは技術ブログの方に書いてもらったやつですが、他にも CodeZine@IT 等に記事にしてもらっています。

codezine.jp

www.atmarkit.co.jp

関連会社の技術顧問

また去年から新しくリクルートの関連会社のニジボックスの技術顧問として活動していますが、そこでもエンジニアコミュニティを作って活性化させようとしています。

www.wantedly.com

www.wantedly.com

speakerdeck.com

speakerdeck.com

speakerdeck.com

頼りになるメンバーが自組織の成長だけではなく、関連会社全体にも広げることができたので、これをより成長させていかないとなと思っています。

社内イベント

R-ISUCONを開催、スピードハッカソンも同時期に開催しました。ギリギリ COVID 19 が本格化する前だったのですが、どちらもオンラインではなくやりました。開催できてよかったですが、これから COVID-19 が本格化した影響でイベント系は予定がすべて一旦延期になりました。なので、開催できたのは2つだけです。

R-ISUCON 2020 を開催しました。

社内ISUCONも開催3度目で、今回はWebメールがお題でした。これがきっかけ(?)で、本家の ISUCON でも運営になる機会をもらえました。ISUCONについては後述します。

recruit-tech.co.jp

スピードハッカソンも開催しました。

recruit-tech.co.jp

イベント

コロナの影響で早々に JSConf は開催を断念しました。

yosuke-furukawa.hatenablog.com

しかし、 ISUCON は運営側としてオンラインで開催できました。オンライン開催する話も増えてるので来年は JSConf.jp やりたいです。

ISUCON 10 の予選に運営として参加した。

ISUCON 10 の予選を作成しました。予選の問題はある程度コンパクトでありかつチャレンジングな課題を設定できたと思っています。非常に好評だったのでよかったのですが、運営がはじめてということもあり、色んな人に迷惑をかけてしまったな、という反省は反省でありました。

isucon.net

ただ参加して本当に良かったと思っています。 941 さんとも色々話した結果はこちらに掲載されています。

zine.qiita.com

登壇系

AMPFest 2020 に英語で登壇した。

www.youtube.com

AMPFest 2020 に英語で登壇する機会をもらえました。この話をきっかけに英会話を学ぶようになり、英語を再学習するきっかけになったのと、 AMPFest 2020 という大きな舞台でオンラインとは言え話ができて非常に良かったです。

Chrome Advisory Board のメンバーとして LT を実施

docs.google.com

こちらも英語で登壇しました。年に2回も英語で発表するきっかけをもらえてよかったです。

継続して来年もどこかでやりたいですね。2019年のふりかえりで書いたことがこんなに早く叶うとは思ってなかったのですが、きっかけになってめっちゃ良かったです。

DevSumi 2020

デブサミ2020でクッキーの話ししました。ブラウザの中ではプライバシーが非常にホットなトピックでしたね。

speakerdeck.com

FEStudy 2

パフォーマンスチューニングの話をしました。 Web Vitals 周りの話だけだともうたくさんされているので、それをキープするために考えることや対処することをまとめました。

speakerdeck.com

PWA Study

Next.js と AMP のはなししました。 Next.js はほんとに今年かなり大きなプラットフォームになりましたね。

speakerdeck.com

競技プログラミング

全体的にイベントがそこまで多くなかったので、それを逆手に取ってインプットに回ることができました。インプットを全力にやった結果を以下のブログにまとめました。競技プログラミングを割と本格的に入門できました。今年は JavaScript で解いてしまったのですが、来年は Rust とか新しい言語でも挑戦してみます。

yosuke-furukawa.hatenablog.com

yosuke-furukawa.hatenablog.com

Node.js

あまり大きな話題は少なかったのですが、いくつかアウトプットしました。

Node.js v14/v15 のまとめ

www.codegrid.net

www.codegrid.net

新機能系

yosuke-furukawa.hatenablog.com

yosuke-furukawa.hatenablog.com

英語

ほぼ毎日英会話50分間してます。ただあまり語彙が増えてない気がしているので、ちょっと他のチャレンジも考えてみます。

f:id:yosuke_furukawa:20201231214857p:plain

数学

毎日 Youtube の問題といてました。今でも解いてるので続けます。

もう少しやらなきゃなぁと思ってたけどできなかったこと

今回ベーシックな競技プログラミングや英語や数学やってたらできなかったですね。もう少し来年は上記のところもバランス良くできるように頑張っていきます。

まとめ

今年もマネジメントにプログラミングにイベント開催に登壇にと色々やれた1年でした。非常に充実していたのですが、反省もありました。また来年も自分の反省を乗り越えて色々できるように精進していきます。今年お世話になった皆様ありがとうございました。

Advent of Code 2020 完答した。

memo.sugyan.com

すぎゃーんの宣伝によって参加したけど、「楽しかった!が八割、辛かった!が二割」って感じでした。

Advent of Code とは

adventofcode.com

所謂アルゴリズム系の問題が25日間のアドベントカレンダー形式ででてくるので、ひたすら解く感じのものです。 ランキングとかを狙おうとしなければそこまで厳しいものではなく、既に解答した人が Reddit だったり youtube とかに解法を上げてたりするので、あまりにも厳しい時はそれを見て回答するのもありです。

最初のほうが簡単なので、『あれ?これなら普通に解けるんじゃね?』と思わせておいてからの Day 17 あたりから急に牙を剥いてくる感じがありましたね。。。

前半の問題

入力値を正規表現だったり、 parse したりして、 扱いやすい形に変換したら問題の半分は解けたような問題が多かったです。

Day12 の船を目的地まで操縦させるようなゲームとか。

Day 12 - Advent of Code 2020

Day11 の飛行機の空いてる座席を探させるやつとかも面白かったですね。

Day 11 - Advent of Code 2020

最初の関門は中国の剰余定理を使わないといけない Day13 でしたね。なんとか数学の知識が役に立ってよかったです。

Day 13 - Advent of Code 2020

後半の問題

前半に比べて後半は格段に難しくなりましたね。特に問題文を噛み砕いて理解するまでが時間かかりました。 難しかったのは Day17 でした。問題が読み解けなかったのと、何言ってんだろっていうのを理解しきれず、タイムアップでしたね。

Day 17 - Advent of Code 2020

答えを見てみるとなんのことはなく、最初から例題の読解よりも書かれている事をある程度愚直に実装したら解けそうでした。

Day20 の part2 も歯が立ちませんでしたね。これはジグソーパズルを解かなきゃいけないのですが、解き方はわかってもパズルのピースをぐるぐる回したり、デバッグしてたりしてたら時間が全く足らなくて、諦めました。

Day 20 - Advent of Code 2020

part1 がジグソーパズルそのものを解かなくてもよかったので安心してたら part2 で格段に難しくなったのがビックリしましたね。

個人的には Day19 の part1 が好きでしたね。正規表現に変換すればいいやと思いついた後は楽でした。part2は解法があってたのか自信がないですが、答えを導くことはできました。

Day 19 - Advent of Code 2020

まとめ

AoC は楽しいです。多分来年もやるだろうなーとは思います。次は Rust とかで挑戦してみようかなと。周りで流行ってるし、最近 すぎゃーんが作ってくれた slack workspace があるので、そこで聞きながらやってみようかなーと思います。

Node.js で最近変わりそうな Permission Policy について

さてさて、 25日目の Node.js アドベントカレンダーです。もう年の瀬ですね。振り返りシーズンなんで色々書きたかったんですが、ネタを見つけているうちにこの日になってしまいました。

Permission Policy とは

Node.js に新しく Permission を提供しようという試みです。元々 Node.js では同じプロセス内で動いてしまえば どんなモジュールであろうと同じ権限で色々できますね。外部ネットワークにアクセスしたり、ファイルを読み書きしたり。

プロセスに元から許可されている権限は全てできてしまいます。これが今まででは普通でしたが、今後はもしかしたら変わるかも?という話です。

権限に関して制限をかけて、拒否させることが可能です。

以下のような要領で拒絶させることができるようになります。

$ node --policy-deny=net

上のオプションでプロセス内のネットワークアクセスを拒否させることが可能です。lint や formatter のときはネットワークアクセスとか不要なのでこのようなオプションで動かすほうが望ましいかも知れませんね。

元々 policy が去年入ってた のですが、それをエンハンスするような形ですね。

ちなみにまだ議論真っ最中です。

github.com

Options

下記のオプションを設定することが可能です (現時点での Permission の実装では)。

  • fs ファイル読み書き
  • fs.in ファイル読み込み
  • fs.out ファイル書き込み
  • net ネットワークアクセス
  • net.in ネットワーク入力
  • net.out ネットワーク出力
  • process 子プロセス起動
  • signal プロセスシグナル送信(プロセスの強制終了などを許可するか否か)
  • env 環境変数読み込み
  • worker ワーカースレッド起動
  • wasi WASI の可否
  • timing 高精度タイマーを拒否(おそらく process.hrtime が使えなくなる、サイドチャネル攻撃対策)
  • addon ネイティブアドオンの実行拒否 (native addon から何でもやられてしまうのを防ぐ目的)

などなど、どう使うのか使途がよくわからないものもありますが、 上記の処理に制限をかけることが可能です。

この他にも、 Permission Policy の機能にはそもそも policy.json で細かく設定できるように予定されているものもあり、例えば port 番号で制限するとかネットワーク接続先で制限するとかの今後の展望は用意されています。 ブラウザの Content-Security-Policy のような allow/deny list で管理するとかも検討が進めばできるようになるかもしれません。

内部実装

ここは内部実装向けなので、興味のある方だけで良いです。

Node.js が v8 を起動する際に permission が設定されます。Permissionチェックは runInPrivilegedScope 関数が用意され、その関数の中で実行した場合のみ、権限チェックが行われます。その関数のコールバックの外側では権限チェックがされないので、内部の fs, net などのコアパッケージでは権限チェックをするところと権限チェックが行われない特権実行の両方が行われます。これは Node.js 内部関数で全てを制限されてしまうと何もできなくなるため、特権を持った関数実行と特権を持たないユーザーが設定した Permission とを区別するために行われています。

function foo(a, b, c) {
  return a + b + c;
}

const privilegedFoo = runInPrivilegedScope.bind(this, foo); // この中でのみ権限チェックされる

console.log(privilegedFoo(1, 2, 3));

// この外では権限チェックされない。

これって deno の permission と一緒?

そう思った方は deno を追いかけてる方ですね!

FAQ に書いてありますが、その回答としては Yes でもあり No でもあるとのことです。

元々の発想は deno から来ていますが、実装は物凄くシンプルに作られていて、 Node.js の internal API 部分で制御しています。 deno はセキュリティモデルからして node とは違うので同じものとは考えにくいです。 deno は特権が必要なときに (OS がカーネルに伺いを立てるかのごとく) 特権を要求するように設計されています。そのタイミングで制限をかけるようになっています。またデフォルトでは制限されていて、 opt-in で権限を付ける所が deno のがセキュリティとしては強いものであると言えるでしょう。

node はデフォルトでは全ての実行が許可されています。制限を後からかけるように設計されています。後から追加した機能なので、ある程度徐々に制限を厳しくできるように設計されたものと言えるでしょう。

package 毎の制御はできるのか?

これを思った方は deno を追いかけてる(以下略

今はできないと思います。 deno もできないのですが、本来はより粒度を細かい単位で実現できるできないの制御をしたいですよね。一律プロセス全体で ネットワーク接続できる、できない、ではなく、このパッケージではできるけど、このパッケージでは許可しない、といったような制御がしたいですよね。

deno も検討中ですが、まだできてないですね。

ただし、面白そうな議論は既に上がっています。

github.com

permission フィールドを package.json に追加させるようにして、その単位で制限をかけられないか?という npm の RFC ですね。 npm のスクリプト実行時に net へのアクセスが必要ならそのタイミングで --grant=net のような許可を要求できるようにしたい、というものです。

まだ全然議論が始まったばかりですが、このあたりは非常に注目していくほうが良いでしょう。

Deno 化する Node.js / Node.js 化する Deno

Node.js に ESM が入り、 top-level await などのモダンな機能が追加されていき、パーミッションの実行までできるようになってくると徐々に deno との差別化ポイントは薄くなっていきます。

Deno は Deno で Node.js との完璧な互換性を求めていないものの、 std:node のような標準モジュールを用意し、 compatible にできるところは合わせていく流れもあったりします。

github.com

結局エコシステムがどういう機能を求めるか次第で JavaScript の世界の API や環境は変わっていくので、どちらもそこまでの差がなくなっていくようにも思えました。

筆者はよく「これからは Deno を使ったほうが良いのでしょうか?」という旨の質問を受けることがあります。本質問に対しての筆者の意見を書いておきます。

Deno も Node.js もどちらも Web コミュニティの中にあるものであり、 Web コミュニティの進化に合わせて機能が作られていくという意味では外側の API 面ではあまり変わらないものになっていくと思われます。 Hello World サーバは Deno でも Node.js でもほとんど同様の書き方で提供されています。

Deno

import { serve } from "https://deno.land/std@0.82.0/http/server.ts";
const s = serve({ port: 8000 });
for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}

Node.js

import { createServer } from 'http';
import { on } from 'events';
const reqs = on(
  createServer().listen(3000), 
  'request'
);
for await (const [_, res] of reqs) {
  res.end('Hello World\n');
}

外側に大きな差異がないのであれば、非機能要件での進化、パフォーマンス、セキュリティ、運用継続性などの観点で選べばよいと思っています。そのうちどちらも変わらない書き方で提供されるようになれば移行もそこまで難しくはなくなると思います。

開発者のタイミングで Deno を使うか Node.js を使うかを選べば良いのではないかと思っています。

この半年やったこと、継続していること

syohex.hatenablog.com

studio3104.hatenablog.com

あまりにも同じことをしていたので「せっかくなので」と思って筆を執ることにする。

@syohex さん@studio3104 さん も僕もだいたい同年代の人たちが同年代の sugyan や色々な方の影響を受けて同じことをしているというのはシンパシーを感じますね。

僕は今の仕事はフロントエンドエンジニアであることが多いのですが、「知識に垣根は作らない」をモットーにしているので、色々半年間挑戦してみました。

Leetcode

この半年で545問解きました。

github.com

sugyan が leetcode に取り組んでいたのも見てたのですが、僕の場合は自分の会社の面接でコード面接をやることがあり、コード面接の時に自分が知らないような事を問題として出すのは恥ずかしいな、という思いから勉強し始めたら楽しくてハマってしまったという経緯です。(もちろん同世代の sugyan がやれていることにも憧れはありました)

あとコロナ禍だったりでリモートになり、仕事と家の間の行ったり来たりが減ったことで、割と融通がきくようになったのでここは一つと思って競技プログラミングの勉強がてら、習慣として leetcode を毎日実施しています。

f:id:yosuke_furukawa:20201217021152p:plain

https://leetcode.com/yosuke-furukawa/

毎日 Leetcode を書いているおかげか、Easy, Medium の問題は全てではないですがなんとか解けます。Hard もそこまで難しい問題ではないときがあるので解けるときは解いてます。

ちなみにずっと JavaScript で解いてるのですが、こういう競技プログラミングには全く向きませんね。 Java とかであれば LinkedList が使えたり、 Heap の実装が既にあったりするのですが、 JS の場合はそれを自作するところからだったりします。

標準ライブラリを仕様に入れるという構想があるようなので、コレクションクラスの充実を望みます。

書き始めてから半年しか経過してないのでなんとも言えませんが、レビュー等での指摘で多少賢いやり方を教えられることが増えたようにも思うのと、 OSS でも基礎的なアルゴリズムを使ったようなものは多少すばやく書けるようになった気がします。

後最近は Advent of Code も毎日出るんで解いてます。

f:id:yosuke_furukawa:20201219013703p:plain
Advent Of Code 2020

個人的に好きな leetcode の問題

LRU Cache ですね。

leetcode.com

一度ライブラリで自作した時に JavaScript で実装するとこんなに面倒なのか、、、と思ったのですが、 leetcode でも出てきて「おおっ」となりました。ちなみに OSS で自分の自作した LRU Cache を使っているのですが、一時的な API の Cache として保持する時に使っています。有用だし、高速化されるし、作ってみると面白いと思います。

他には数独 Solver も好きです。

leetcode.com

Backtrackで解ける問題昔は苦手だったのですが Leetcode を経て楽しく解けるようになりました。

英語

DMM 英会話くらいしかやってませんが、半年で 4900 分間レッスンを受けました。

f:id:yosuke_furukawa:20201219011736p:plain

こっちは物理的にも時間的にも制約があるのと相性の良い先生とのレッスンを取ろうとすると相手の都合もあったりで、毎日コンスタントにやるのは難しいのですが、大体週5-6日は英語で50分間はしゃべっています。

これもコロナ禍の空き時間を使った形ですね。英語を再勉強するというきっかけになりました。あまり発音も良くないので、発音を矯正しようと思って本で勉強したりしています。

www.amazon.co.jp

あと今年は海外での登壇予定があったので、そのためにも気合を入れてやってました。

youtu.be

これまでもそれなりに話せたといえば話せたのですが、発音を気をつけてやってみたら agektmr さんからも褒められて嬉しかったです。

後最近は Youtuber の Daijiro さんの動画を笑いながら見てます。

www.youtube.com

数学

あまりちゃんとやれてないですが、数学も解いてます。 Youtube で数学の問題を必ず一日一問解説してる 鈴木貫太郎さんの動画があるので、それを見てます。

www.youtube.com

大学のノートみたいなのを買って朝の30分だけ解いたりしてます。朝起きた時に問題を見て、解いてみて、あってるか動画で見て、動画に自分がどうやって解答したかを書いてます。完全に知識が鈍ってるのでエレガントな方法をスッと思いつくのは苦手ですが。

数学ができると leetcode の問題でも数学の知識を問う問題があって解けるようになったりと色々付加的な効果がありました。

鈴木貫太郎先生が教えてくれる整数問題はパズルのようで楽しいです。ただやってると高校の時にもっと勉強しておけばよかったな、、、と思うことが多いですね。

競技プログラミングや英語と違い、数学は特に明確な目的があって始めたわけじゃなく、趣味みたいな感じです。

これから

こういう活動を会社の仕事や家の事があるので柔軟な時間にちょこちょことやってます。多分これからも同じことを続けていきたいと思いつつ、ある程度切りの良いところを見つけたら別な挑戦もしてみるかもしれません。新しいプログラミング言語を身につけるとか低レイヤをやってみるとか、ずっと漠然とした憧れがありながらもできていないので、コロナ禍のまだリモートワークを中心とした生活の中で会社の仕事とのバランスを取りながらやってみようかと思ってます。

yuroyoro.hatenablog.com

(ちなみに、この記事すごくいいですよね。やってみたくなります。)

assert.CallTracker と must-call

この記事はリクルートエンジニアアドベントカレンダーの2日目の記事です。過ぎてるかもしれませんが、メンバーから脅迫されて書いてます。

f:id:yosuke_furukawa:20201204210950p:plain

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 で書いた内容です。

www.codegrid.net

ここから蛇足と言うか多少細かすぎて伝わらない話をしようかと。

mustCall

この機能もともとどこから来たかと言うと、 Node.js のコアのテストでよく書かれてた common.mustCall っていう test utility 関数から来ています。

https://github.com/nodejs/node/tree/master/test/common#mustcallfn-exact

これを一般向けに公開したのがこの機能です。 Node.js の中では Event を使ったテストが多く、『イベントが呼ばれなかった時に検証が行われずそのままパスしてしまう』というのを防ぐモチベーションで作られていました。

今では実は jest とかでもやっちゃうときはやっちゃいますよね。

余談ですが、この機能はめっちゃ便利だなーと思っていたので昔 must-call っていうライブラリとして公開していました。

www.npmjs.com

f:id:yosuke_furukawa:20201204215906p:plain

今となっては 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 プロポーザルについて意見がほしいとのことです。 意見は以下の場所にポストできます。

docs.google.com

原文: 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のブログを読むことをオススメします。

maggiepint.com

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 が気合が入ってる。

tc39.es

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月初頭にやっていたので、聴講してきました。

web.dev

その中でも面白かったものについていくつか紹介します。

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つを指標としています。

web.dev

Core Web Vitals 指標
Core Web Vitals 指標

一日目はこの指標の説明とどうやったら良くなるかが非常にたくさん描かれていました。

What's new in speed tooling

www.youtube.com

じゃあ Core Web Vitals はいいとして、『どうやってツールを使ってそれらを把握していくのが良いのか?』というのが語られてました。

ほとんどのツールでもう既にCWVは取れる

上記画像の通り、基本的にはどんなツールでも 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違反になっている箇所をどうやって修正するかを確認できるので、便利そうでした。

DevTools 上でCLS違反が起きてる箇所がわかる。

また、2020年までは上述した threshold にするものの、一年おきに水準や方法は見直していくという事でした。

Core Web Vitals 2021
Core Web Vitals 2021

Optimize for Core Web Vitals

www.youtube.com

具体的にどうやって Core Web Vitals を上げていくかというテクニックめいた話でした。 実際に Chloe というファッションブランドのサイトを Core Web Vitals に基づき改善していった話をしてました。

CLS の改善

先程の説明の通り、 Cumulative Layout Shift (CLS) は初期表示の後にレイアウトが変わってしまう問題です。馴染みやすい問題として、「ガタンッ」問題 とも呼ばれているものです。ロード前のコンテンツがロードされることで高さが変わったり、幅が変わることでレイアウトが変わってしまう現象を指しています。

見た目にも悪いですし、何より押そうとしたらレイアウトがずれてリンクが押せなくなったりと実害が出る問題です。 このガタンッ問題をどうやって防ぐかという話を解説してくれていました。

最初に画像に関しては width, height を img タグに設定しておき、後から高さや幅が変わらないようにしておきます。 もしもできるなら、画像の背景色に合わせた類似色を表示エリアに事前に表示することも推奨されていました。

Chloe の例

背景色を画像に合わせて塗っておくことで、画像が表示される前であっても見た目やレイアウトが損なわれません。

また、後からコンテンツを表示された領域に挿入することは基本NGです。しかし、広告やプロモーション等でどうしても後から入るケースも考えられます。その時も高さに関しては事前に領域を確保しておきましょう。

広告の入る領域を事前に確保しておく

LCP の改善

Largest Contentful Paint は先程の説明の通り、『初期表示領域における、最大面積を持つコンテンツのロードにかかる時間』を指すのですが、対象は所謂ヒーローイメージであることが多いので、画像のチューニングが紹介されてました。

基本的に画像はCDNを利用し、webpをデフォルトで使ってほしい点、また本質的に必要なサイズの画像しか用意しない事で画像のサイズをカリカリに縮小した状態で表示させることで初期表示の改善に取り組んでいきました。

ヒーローイメージのチューニング

また、Server Push を使った改善も取り組んだことを紹介していました。 Naive な Server Push 自体は Cache Aware ではないため、 HTTP/2 の Server Push をそのまま活用したわけではなく、 Akamai の Server Push を使ってインテリジェントな Push を実現したという解説がされていました。

blogs.akamai.com

結果として大幅な LCP の改善ができたことを紹介していました。

Server Push Impact

Day 2

ほぼ PWA の話でしたが、筆者が興味を持ったのは以下の2つでした。

  • What’s new in V8
  • Zoom on Web

What's new in V8

www.youtube.com

ほぼほぼ知ってる話でしたが、一応 recap しておきます。

ES 新機能

  • Nullish Coalescing
  • Optional Chaining
  • WeakRef

が追加されました。

Optional Chaining

Nullish Coalescing

WeakRef

それぞれ説明するまでもないかな、と思ったので解説は割愛します。WeakRefのユースケースをイベントリスナーの廃棄に使うっていうケースが紹介されてて、そこだけ面白かったです。

Memory Improvements

メモリの消費量を抑える新しい仕組みについて 2 点解説されていました。

  • Pointer Compression
  • JIT-less mode (V8 Lite)

Pointer Compression

イデア自体は凄くシンプルで、64bit アドレスでメモリアロケート先のポインターを持つのではなく、base address は64bitのアドレスにしつつ、そこからのoffsetを32bitアドレスで持つことで全体的にアドレスの管理をするメモリの消費量を減らすというものです。

Pointer Compression

内容自体は凄くシンプルですが、結果としては、平均で40%程度削減されている結果が見込めるという事でした。

Pointer Compression memory reduction

JIT less mode

V8 は多くのメモリ確保をしていますが、その内訳を見ると、JITに必要なメモリが含まれていることがわかります。

v8 memory の内訳

JIT の機能をオフにしてしまえば、いくつかのメモリを持たなくて済むため、メモリ消費量を抑えることができます。

JIT less memory

まだこれは試験的な機能ですが、JIT less にすることでメモリ消費量は10-25%程度の削減が見込めるということでした。

jit-less result

Zoom on Web

www.youtube.com

Zoom の動画チャット機能を Web で提供するため、 Chrome と Zoom で組んで色々な機能のリリースやトライアルをやっているという話でした。

WebAssembly on SIMD, WebTransport, WebCodec の3つの技術を紹介し、それぞれを組み合わせていく話が紹介されていました。

WebTransport は UDP レイヤでのデータ通信を実現し、それを使って動画を転送しつつ、WebCodec で動画のデコードとエンコードを行い描画を行い、 WebAssembly SIMD を使って背景の透過などの動画エフェクトを掛けるという紹介がされていました。

wasm simd

Day 3

Privacy Sandbox セキュリティ 周りの話がほとんどでした。

www.chromium.org

そこまで目新しい話はありませんでしたが、 COOP, COEP の話は抑えておいて良いと思います。

また、 Signin Form Best Practice の話は割と面白かったです。

Cross Origin Opener Policy, Cross Origin Embedder Policy

www.youtube.com

Cross Origin 、つまりクロスドメインにおいて、オリジンの情報をいかに守るか、という話です。

Cross Origin Opener Policy

COOP は通常 window.opener から開き元になる origin の DOM が参照変更できることを避け、 window.opener がただの null になります。

COEP は Cross Origin Resource Policy が設定されていないリソースの読み込みをさせないようにリソースの読み込みポリシーを見て表示可否を制御できるポリシーです。

設定すると許可されていないリソースの読み込みをblockさせることができます。

COEP

Signin form best practice

www.youtube.com

おそらく Web エンジニアが幾度となく作ってきた サインインフォームについての話でした。

web.dev

  • 意味のあるHTMLを使って div とかだけで表現するな。
  • button は button として使うこと、 button を "送信" みたいなボタンにせず、 "Sign-in" などの意味のあるラベルを用意すること
  • 登録用パスワードを2個書かせたり、登録用メールアドレスを2つ書かせるような double up form にするな
  • autofocus を使ってどっから書くか明確にせよ
  • password の type は使うが、見たい時にはマスクが外れるようにせよ
  • input type は email や tel など入力フォームに合わせてちゃんと設定せよ
  • inputmode の利用も考慮すること
  • Sign in button が入力キーボードで見えなくなることを避けよ(押せなくなるから)

sign in ボタンがかぶってる例

  • Sign up のときやパスワード再入力時に name を new-password や current-password でわけて表現すること

などなど、ものすごくたくさん紹介されていた。

password のマスクを外すときは typeをtextにしつつ、aria labelもちゃんと変更する例

Recap

「Web Vitals で初期表示の改善の話がされ、 V8 や WASM の話でアプリケーションの能力を広げ、 Privacy をきちんと保護する」というなんとなく全体的に今推してる機能がわかる話だった。