mizdra's blog

ぽよぐらみんぐ

rule ごとに高速に eslint --fix できるツールを作った

大量のエラーに対して rule ごとに高速に eslint --fix できるツール 「eslint-interactive」 を作ったので、その紹介です。

動機

ESLint のデフォルトの出力はエラーの発生源や修正のためのヒントなど、開発者に役立つ多くの情報を含みます。これは多く場合機能しますが、膨大な量のエラーが報告される状況ではあまり機能しません (例えばプロジェクトに ESLint を導入する時や、プロジェクトの .eslintrc に大幅な変更を加える時など)。そうした状況では ESLint の出力が膨大になってしまい、開発者による出力の分析が困難になってしまいます。また、多くの種類のエラーがごちゃまぜになって出力されているため、エラーを修正するのも困難です。

そのため、このような多くのエラー発生する状況では、以下の 2 つの事柄が重要であると考えています。

  • 全てのエラーをまとめた概要を表示し、簡単に全体を把握できること
    • エラーごとの詳細を表示しても、かえって開発者を混乱させてしまいます
  • 大量のエラーを効率よく修正する手段が提供されていること
    • eslint --fix はエラーを効率よく修正する優れた手段の 1 つですが、一括で全ての rule のエラーを auto-fix してしまいます
    • rule によってはコードの挙動に影響を与える auto-fix を行うため、auto-fix は慎重に実施する必要があります
    • そのため eslint --fix よりも小さい単位で auto-fix できる手段が提供されていることが望ましいです

eslint-interactive の紹介

そこで eslint-interactive という ESLint を wrap したツールを作成しました。このツールでは全てのエラーを rule ごとにグルーピングし、rule あたりのエラー数を整形して出力します。rule ごとの warningerror の内訳に加え、fixable なエラーの数など、開発者がエラーを修正するためのヒントも出力されます。また、いくつかの rule を指定して、生の ESLint のエラーメッセージを表示したり、auto-fix を実行することができます。

github.com

こんな感じに rule ごとのエラー総数を眺めつつ、rule ごとに auto-fix していけます。

www.youtube.com

インストール方法や使い方は README を見てもらえれば分かると思うので、先程貼ったリポジトリから辿って下さい。

eslint-nibble との違い

eslint-interactive とよく似たツールに eslint-nibble があります。どちらも同じ問題を解決するツールですが、eslint-interactive には eslint-nibble にないいくつかの機能があります。例えばeslint-interactive はruleごとのfixableなエラー数を出力しますが、eslint-nibble は出力しません。また、eslint-interactive はruleごとのauto-fixのサイクルを高速で回すための様々な工夫 *1 がありますが、eslint-nibble は一度 auto-fix を行うと毎回プロセスが終了するため、eslint-interactive ほど高速にauto-fixのサイクルを回すことはできません。

どちらも同じ問題を解決できるツールではあるのですが、これらの機能が自分にとって非常に重要だったので、新規にeslint-interactive として実装してみました。

おわりに

元々とあるプロジェクトの.eslintrc を大改修する機会があって、その作業を円滑に進めるために用意したツールでした。実際に eslint-interactive を何度か使って作業してみていますが、最初に semi などコードの挙動を変えない rule の auto-fix を適用 => 次に import/order などコードの挙動を変えうる rule の auto-fix を適用、といったように段階的に安全に作業を進めることができて、快適に過ごせてます。皆さんも同じような状況に遭遇したらぜひ eslint-interactive を試してみて下さい。

*1:Lint結果をキャッシュする機能や、プロセスを維持したまま連続で auto-fix できる機能が備わってます。

ブラウザにおけるメモリリークを解決するために読んでおけると良い資料

最近趣味や仕事の Web アプリケーションでメモリリークに遭遇して、頑張ってメモリリークの原因を突き止めて修正する、ということがあった。その過程でメモリリークについて色々調べて知見が溜まったので、学習資料の紹介という形でアウトプットしてみる *1

前置き

  • 紹介する記事がかなり偏っていることに注意
    • 冒頭で触れたメモリリークを解決するために読んだ記事をまとめただけなので、内容にそれなりの偏りがある
    • 例えば id:mizdra が遭遇したメモリリークは全てブラウザ上で発生していたものだったので、これから紹介する内容も主にブラウザにおけるメモリリークに焦点を当てたものになる
  • GC がどうメモリをどう解放しているか、何故メモリリークが発生するのかは全てカット
    • 調べれば色々な記事が出てくるので、必要に応じて読んでください

基本的な知識を抑える

  • まずメモリリークとメモリ撹拌の違いを学ぼう
    • どちらも同じメモリに起因する問題だけど、アプリケーションの性質によって深刻度が異なったり、対処方法も変わるのでちゃんと区別しておけると良い
    • こういうのは世の中の事例を見ると分かりやすい
    • https://www.html5rocks.com/ja/tutorials/memory/effectivemanagement/
      • Gmail におけるメモリリークの事例
      • 内容は結構古いけど、書いてあることのほとんどは現代でも通用する
    • https://www.html5rocks.com/ja/tutorials/speed/static-mem-pools/
      • メモリ撹拌 (メモリリークとは違う!) の事例
      • メモリ撹拌自体は GC が実行されればメモリ使用量は元に戻る
      • 頻繁に GC が発生することで GC によるメインスレッドの停止が発生するので、常に 60fps 維持したいアプリケーション(FPS ゲームとか)などでは問題になる
      • 「メモリ撹拌にはオブジェクトプールが有効だよ」みたいな話題も書かれている
        • 逆にここからメモリリークに対してはオブジェクトプールはそれほど有効ではないことも分かるはず
  • Chrome devtools を使ったメモリ問題の特定方法を学ぼう

ここまで読めば、一通りメモリの問題に対処できる力がつくはず。

その他メモリにまつわる技術資料

  • https://v8.dev/features/weak-references
    • WeakRef / FinalizationRegistry を使うと特定のメモリリークを解決できるケースがあるよ、という話
  • https://web.dev/monitor-total-page-memory-usage/
    • メモリ使用量を測定する API にperformance.memoryというものがあるけど、API 実行直前に GC が発生していたか、そうでないかによって測定結果が大きく変わって使い物にならない
      • GC 発生直後ならメモリ使用量が少なくなり、発生から十分時間が経過している場合はゴミが残っているのでメモリ使用量が大きくなってしまうはず
    • それを受け、この記事ではperformance.memoryの代わりに事前に GC を実行してからメモリ使用量を測定してくれるperformance.measureMemoryを使いましょう、という話が紹介されている
  • GC の挙動をもっと知りたい場合
  • GC の停止時間がアプリケーションの価値に直結する場合 (FPS ゲームなど) は GC の挙動も知っておけると良い
  • V8 チームが出している記事を読むのがオススメ
    • まず GC の基礎を説明して、それから Chrome 固有の実装の詳細について説明するというスタイルになっていて、比較的読みやすい
    • Chrome が現代のモダンブラウザの中では一番イケているはずなので、とりあえず Chrome だけ抑えておければ良い
    • いわゆる「リファレンス実装」として利用する
  • https://v8.dev/blog/trash-talk
  • https://v8.dev/blog/concurrent-marking
    • Concurrent Marking に関する詳細
  • https://v8.dev/blog/high-performance-cpp-gc
    • Blink(Chrome のレンダリングエンジン)で利用されている GC「Oilpan」の仕組みの話
    • DOM などは Orinoco ではなく Oilpan によって管理されるので、こっちも読んでおけると良いはず

*1:元々社内向けに書いた資料で、それを公開できる形に調整した。

スナップショットテストの向き不向きについて考えてみる

ふとスナップショットテストってなんだろう、どういう場面で向いていて、どういう場面には向いていないんだろうと考える機会があって色々調べてました。丁寧な記事にしようとしたのですが、上手くまとまらなくて挫折してしまった… とはいえこのまま手元に置き続けておくのも勿体ないので、下書き段階のものを公開して供養します。

スナップショットテストとは

スナップショットテストとは、あるプログラムの出力を以前の出力と比較し、両者に差分があるかをテストする手法のことです。予め以前のバージョンのプログラムの出力 (スナップショット) のどこかに保存しておき、新しいバージョンのプログラムの出力と比較し、差分があったら fail させます。これにより、プログラムの出力内容が予期せぬうちに変わってしまっていた場合に気づくことができます。

例: React コンポーネントのテストへの適用

代表的な利用例が Jest を使った React コンポーネントのテストです。

// Jestのドキュメントから引用
// from: https://deltice.github.io/jest/docs/ja/snapshot-testing.html
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer.create(<Link page="http://www.facebook.com">Facebook</Link>).toJSON();
  expect(tree).toMatchSnapshot();
});

この例では、<Link>コンポーネントの render 結果をシリアライズし (const tree = renderer.create(...))、その結果をスナップショットとして保存・比較するよう Jest に指示しています (expect(tree).toMatchSnapshot())。このテストを初めて実行すると、ファイルシステムに以下のようなスナップショットファイルが作成されます。

exports[`renders correctly 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;

2 回目以降テストを実行する際は、このスナップショットファイルとの差分が計算されます。もし<Link>コンポーネントの実装にミスがあり、予期せぬうちに render 結果が変わってしまった場合はスナップショットに差分が発生し、テストが fail するようになります。

スナップショットテストに対する一般的な理解

これは個人的な意見ですが、スナップショットテストが React コンポーネント以外にどんな場面へ適用可能なのか、というのはあまり知られていないように思います (僕も含めて)。スナップショットテストは非常に優れた性質を持っており、様々な場面へ適用できる有用なテスト手法です。しかしスナップショットテストがどういった特性を持っていて、どういう向き不向きがあるのか、といったことはほとんど知られていません。ここぞという時に使えるよう、使い時くらいは分かっていると便利ですよね。

どういう場面では向いているか

という訳でここからは、スナップショットテストがどういった場面で有用なのかが分かるよう、スナップショットテストの向き不向きについて考えてみます。ざっとスナップショットテストが向いている場面について書き出すと、こんな感じでしょうか。

  • 出力に差分が出たことを検知したい時
    • 差分があった時に fail して警告ほしい、どういう差分が出たのか教えてほしい、みたいなケースを想定
  • 有意な出力を出せる時
    • もうちょっと噛み砕くと、出力にテストしたいことが埋め込まれていて、かつ人間の目に読みやすい形になっている時
    • 例えばコンパイラが出力するコンパイルエラーのメッセージにて、エラーの発生行数が期待通りに印字されているかをテストしたい、みたいなケースを考えます
      • この場合はターミナルに出力されたエラーメッセージに発生行数がerror: index.ts:10:3みたいな形式で埋め込まれているので、ターミナルへ出力されたテキストををそのままスナップショットとして取れれば良いです
      • 逆にエラーメッセージが何件出たかをこれでテストするのは難しいです
        • 沢山エラーメッセージが出ると巨大なスナップショットが生成されてしまいます
        • 人間の目で数えることになって大変
    • 素朴にexpect(typeCheck(program).errors.length).toEqual(100)のような assertion を書くくらいで十分なはず
  • テスト対象がテスタブルなインターフェイスを公開してくれていない時
    • Virtual DOM など
      • <TodoList items={items} />が render された結果に items がitems.length個含まれているかをテストしたい、みたいなケースを想定
      • jsdom をインストールして DOM API を使ってテストしようと思えばできなくはないです (expect(document.querySelectorAll('.item').length).toEqual(5);)
      • けど Virtual DOM をそのままシリアライズしてそのスナップショットを取ったほうがずっと簡単にテストできます
    • スナップショットテストであれば出力さえ取れればテストできるので、テスタブルなインターフェイスが無くても何とかなる可能性があります
  • テストがない朽ちたコードを触る時
    • 触ろうにもテストが無いのでまずはテストを追加したい / けどテストを追加しようにもユニットテストを差し込めるようなインターフェイスになっていない…みたいな状況を想像してください
    • こういう時スナップショットテストが役に立ちます
    • ロガーを用意して、そのロガーを使ってアプリケーションのあらゆる場所でデバッグ出力をして、その出力をスナップショットテストする、というもの
      • 何かの拍子でログ出力の順番が変わったり、出力の内容が変わった時に気づくことができます
      • アプリケーションコードにはログ出力するコードを差し込むだけで、インターフェイスを全く変更する必要が無いので安全かつ簡単に導入できます
    • Golden Master と呼ばれている手法らしいです

どういう場面では向いていないか

逆にどういう場面では向いていないかというと…

  • 上記のメリットを殆ど享受できない場合、スナップショットテストは向いてません
    • そんなの当たり前では、と思うかもしれないけど、この観点は意外と重要だったりします
    • というのもこういうケースの場合、他のテスト手法を採用したほうが適切な場合が多いため
    • 例えば 2 つの整数を受け取ってその和を返すadd関数のテストがしたくて、以下のようなテストを書いたとします
    • さて何が問題でしょうか
    • まず答えが何になるべきなのかスナップショットテストからは分かりません
      • パッと見では3になるべきですが、正確なことはスナップショットファイルを見に行かなければ分かりません
    • また、スナップショットテストにしてしまうと、正解のデータを書き換えるには一度テストを走らせる必要があります
      • expect(add(1, 2)).toEqual(3)ならexpect(add(1, 3)).toEqual(4)と書き換えるだけで良いはずでした
    • ...というようにテストとしては動いているけど、なんか過剰な感じがしてしまう
    • こういう場合は素朴に数値として assert したほうが、テストが落ちた時にデバッグしやすいし、テストが読みやすいし、スナップショットファイルも要らなくなってシンプルになります
    • 小学校レベルの知識で解けるのであれば、大学レベルの知識を使って解くのではなくてそっちを使って解きましょう、みたいな
    • ゴブリンを倒すのであれば大量に MP を消費する大魔法を使わずとも、錆びた剣を振りかざすので十分なはず
    • 乱用することなく、状況に応じて道具を使い分けるという話
  • 出力の差分が巨大になる場合、スナップショットテストは向いてません
    • 例: 2 京行の差分が出る時
    • 例: Visual Regression Test で、5GB の画像が出力される時
    • テストフレームワークが処理するのに時間が掛かるし、最悪の場合クラッシュします
  • 出力の差分を見て、その妥当性を判断するのが困難な場合、スナップショットテストは向いていません
    • 例: 5 万行の差分が出るテスト
    • 1 分で 50 行見たとしても見終わるのに 1000 分掛かります
      • 9 時に出社して差分を見始めて、お昼過ぎてもまだまだで、定時過ぎてもまだまだで、終電が無くなった頃にようやく見終わる、みたいなスケール
      • 機械より人間が頑張るスタイル
    • ただし差分の内容を見ずに変わったことだけ分かれば良い場合や、最初の 100 行だけ見て妥当ですねと判断できる場合であればこの限りではないです
  • 出力の変化が激しい場合
    • 例: 現在時刻を表示するクロックコンポーネントのテスト
      • テストを実行する時刻によって出力が変わって毎回テストが fail します
      • 毎回人の目でチェックしないといけません
      • 機械より人間が頑張るスタイル 2
      • ただし何らかの手段で出力を固定できれば、問題を回避できる場合があります
        • クロックコンポーネントの例では時間を司る API をモックして時刻を固定すれば良い
        • Visual Regression Test なら変化する部分を黒塗りしてから比較すれば良い
        • このような出力の固定化が困難な場合はスナップショットテスト以外のテスト手法を検討しましょう
    • 例: アプリケーションのソースコードそのもののスナップショットテスト
      • ソースコードを 1 文字書き換えたらテストが fail する、みたいな
      • 誰もそんなテスト書かないと思いますが…
      • 良い具体例が思いつかなかった!本当はちょっとどこかを弄ったら毎回 fail するのは良くないよね、みたいなことを言いたかった。

事例

今までで見つけた利用例などを書いてみます。

その他使えそうなところ

以上の情報を元に他にどういう場面で使えそうか、というのを考えてみます。

  • nginx が返すレスポンスのテスト
    • ある URL にリクエストを飛ばした時に、期待されるヘッダや body から構成されるレスポンスが返ってくるかをテストしたい、みたいな
    • ちゃんと期待するヘッダが付いてるレスポンスが返ってくるかなどをテストできるはず

自分を鼓舞する言葉を思い出しながら文章を書く

ここ一番は頑張りたいだとか、ここは力を入れてやりたいとか、そういった場面に遭遇する時が時々ある。最近だとイベントで発表する資料を作ったりだとか、卒業論文書いたりだとか、1つの技術をひたすら深堀りしていく記事を書いたりだとか。けどそういったものを成し遂げるのは本当に大変で、途中で何度も挫けそうになる。そこで挫けずにモチベーションを保ったまま進めるように、自分を鼓舞する言葉というのを集めていて、それを執筆中に思い出すようにしている。文章書いてる時に思い出す言葉を中心にリストアップしたけど、いくつかは普遍的な言葉なので、文章書く時以外でも使えるはず。

折角やるんだからより良くやろう/丁寧にやろう

折角文章書いたり発表するのだから全力でやろう。折角取り組むチャンスを得たのだから、その機会を大切にしよう、という考え。

これだと勿体ないよ

自分がしたことがちゃんと伝わるように書こう、折角面白いことしたのにそれが伝わらないのは勿体ないよ、という考え。元々学部の時に居た研究室の指導教官がよく言っていて、良い言葉だなと思ったので研究室を離れた後もずっと使っている。

言葉はごまんとあるのだから、今よりも良い表現がきっとあるはず

文章を推敲する時に思い出す言葉。何かイマイチな文章があるけど、どうやって書き換えたら良くなるのか全然思いつかない時に使う。

答えが見えていないものを推敲するのは多くの場合勇気が要る。いざ推敲にチャレンジしても対して良い文章にならないかもしれないし、もしかしたら悪化するかもしれない。どれだけ考えても良い表現を見つけられず、時間を浪費するだけ (そして締切までに残された時間が短くなるだけ) かもしれない。

そういう時によくこの言葉を思い出して、きっとより良い表現はあるので、恐れずにチャレンジしようと言い聞かせている *1

ここは重要なところなので丁寧に書こう

ここを丁寧に書けば読者の理解がより深まって、後続の文章がより効果的に理解されるようになってお得、という考え。とりあえずコスパが良い箇所なので最優先で取り組もう、という気持ちになれる。

これちょっと捻ってみませんか => もうちょっと捻ってみませんか => もう1回くらい捻りませんか => ラストもう1回

捻れば捻るほど面白くなるのでどんどん捻っていこうという考え。辛い作業なのでゲーム感覚で口ずさんでいくと気が楽になって良い。


自分を鼓舞する言葉に込めた本意

一見するとこれらの言葉は自分をキツく縛り付けるような厳しい言葉のように聞こえて、ついつい無理をしなければ!と考えてしまいがちだけど、個人的には是非その真逆の捉え方をしてほしいと思っている。

重要なことは、これらの言葉を使うのは苦しい思いをして良い文章を書くためのものではなく、気楽に良い文章を書くためのものであるということ。だから言葉も厳しいものというよりは、ポジティブで背中を押してくれるような表現から構成するようにしている。執筆作業の辛さを少しでも和らげるようなまじないを掛けることで、無理なくより良い文章を書くことができる。それがこれらの言葉に込めている本意です。

皆さんもオススメのまじないがあったら教えて下さい。

*1:実際に歯を食いしばって推敲するとより良い文章になるので、ただのまじないではないと思っている。

2020年ブックマークランキング

年間ブックマークランキングジェネレーター で作ってみた。手軽に1年を振り返れて便利。8位は去年に投稿した記事だけど何故かランキングに入っている。まあ気にしないことにします。

mizdra's blogの2020年ブックマークランキングベスト8(累計600ブックマーク)

# タイトル
1位 画像による Layout Shift が無くなる Web がやって来る - mizdra's blog
2位 polyfill を深堀りする - mizdra's blog
3位 趣味で創作する時は常に何かしら新しいことに挑戦する - mizdra's blog
4位 target=''_blank" な <a> タグに noopener だけでなく noreferrer も付けるべきか - mizdra's blog
5位 150B で動く strictly-typed な Event Emitter を作った - mizdra's blog
6位 Scrapboxのページ内に埋め込まれているアイコンをsuggestして挿入できるUserScript作った - mizdra's blog
7位 日向縁さんの笑い声について思いを馳せる - mizdra's blog
8位 OK Google, 今日のゆゆ式 - mizdra's blog

generated by 年間ブックマークランキングジェネレーター

日向縁さんの笑い声について思いを馳せる

この記事はゆゆ式 Advent Calendar 2020 13日目の記事です。12日目はセレーノ (@haidorenzia) さんの尻尾ファンタジアでした。

adventar.org

私事ですが最近COMIC FUZできららを定期購読するようになりました。以前は一気に読んだときの充実感が味わえない点、いちいち書店に足を運ぶのが面倒な点が嫌で単行本派だったのですが、意外と不満なく過ごせています。何よりゆゆ式が月に1回読めるのが最高です。ゆゆ式は話単位で読んでもちゃんと充実感がある。後々刊行される単行本を読んだ時に、本誌で既に読んでいたことで充実感が味わえなくなってしまうのではないか、という不安があったのですが、全然そんな事なかったです。単行本めっちゃ充実感あった… ゆゆ式の圧倒的面白さが不安を吹き飛ばしてくれました。月イチでゆゆ式が楽しめて、単行本でまた楽しめる。定期購読して本当によかった。


さて今年のゆゆ式 Advent Calendarのテーマは「日向縁さんの笑い声」です。ゆゆ式の登場人物の中でも日向縁さんは本当によく笑います。見ているとほんわかします。日向縁さんはゆゆ式の登場人物の中でも屈指の癒やしキャラですが、笑いがその癒やしを成り立たせていると言っても過言ではないと思っています。

そしてその笑い声も多彩です。「アハハッ」と普通に笑うこともあれば「アッハハハハハ!!!」と大笑いすることもあるし、「クフフッ…」みたいな笑い声もある… 本当に色々な笑い声をされるので、それを見ているだけでも楽しめます。僕は「おっ、ここではこういう笑い声なのか〜」と眺めてはなるほどと言っています。多分僕だけじゃないはず。

という訳で今回は僕が好きな日向縁さんの笑い声10選を紹介します。ちなみに訳あって集計対象は1〜10巻までです。

4巻 61ページ 左側 4コマ目

初めは最もメジャーな笑い方、大笑いです。よくゆずこのボケを受けてこの大笑いをしてくれます。 f:id:mizdra:20201213130705j:plain

4巻 102ページ 右側 4コマ目

こっちはちょっと堪えつつ大笑い。「酒のみてえ」の回です。唯さんが「そんなに…」というくらいの元気な笑いです。 f:id:mizdra:20201213130726j:plain

5巻 11ページ 左側 2コマ目 / 5巻 11ページ 左側 4コマ目

なま言ってんじゃねえの回から2コマセットで紹介。笑いを堪える縁さんに対し、ゆずこがトドメを指すシーンです。堪えてからの笑いっぷりがお気に入りです。ちゃんと縁さんの顔もいかにも「ぷはっ」と笑っていそうな感じで素敵ですよね。三上小又先生は笑い顔を書くのが上手いなあとつくづく思います。 f:id:mizdra:20201213130736j:plain f:id:mizdra:20201213132741j:plain

9巻 112ページ 左側 3コマ目

超巨大スペイン回から。頑張って体全体でうずくまって笑いを堪えているシーンです。かわいい。

f:id:mizdra:20201213132803j:plain
スキャンに失敗して黄色い線が入ってますが気にしないで下さい

4巻 70ページ 左側 4コマ目

海回の1コマです。堪え笑い…というよりは笑いすぎて死にそうになっている様子です。大変元気でよろしい。 f:id:mizdra:20201213130717j:plain

10巻 53ページ 右側 4コマ目

お米だいすき回より「ひぃ〜〜〜...」です。涙が出るくらいの笑い、こちらも良い笑いっぷりですね。 f:id:mizdra:20201213132848j:plain

5巻 105ページ 右側 1コマ目

どっちが先に寝るか選手権より1コマ。これはもっと大胆な笑いっぷりで、床で笑い転げています。あの日向縁さんなのだから床で笑い転げるくらい得意技だろう、と思っていたのですが、意外にもこのコマしかないんですよね。つまりレアです。 f:id:mizdra:20201213132751j:plain

2巻 15ページ 右側 4コマ目

こちらは2巻、初期のゆゆ式の1コマです。実は日向縁さんが声を出して笑うのは2巻のこのコマが初めてなんですよね。意外にも1巻では笑顔になったりニンマリすることはあれど、声を出して笑うことはないのです。皆さんもこういった普段と異なる視点でゆゆ式を読み返して見ると面白いかもしれません。 f:id:mizdra:20201213130624j:plain

10巻 83ページ 左側 7コマ目

最後はポンコツ料理ロボの回の1コマです。同じく堪え笑いでも、ネタを振りながらの堪え笑いです。笑いつつもちゃんとネタを振っててえらい。ゆゆ式の中でもかなり好きなコマの1つです。 f:id:mizdra:20201213132855j:plain

おわりに

かなりマニアックな話題だったと思うのですが、いかがでしたでしょうか。あ〜これこれと言いながら眺めてもらえていたら嬉しいです。他にももっと素敵な笑い声あるよ!こういう笑い声もオススメだよ!とかあれば是非教えて下さい。

f:id:mizdra:20201213155229j:plain
最後は縁さんの笑いっぷりに対するゆずこからの一言でお別れです

余談ですが好きな笑い声のコマを集める作業は、日向縁さんが登場するコマ一覧を眺めるアプリを自作してやっていました。自炊した単行本のデータを id:esuji5 さんの yonkoma2data に与えて作りました。id:esuji5 さん、いつもありがとうございます *1。お世話になりすぎているので何かしらの形でフィードバックしたい…!

私はこれを「ゆかりビューワー」と名付けました

明日の担当は id:esuji5 さんです!

adventar.org

*1:去年もゆゆ式プリンターを作る際にお世話になりました。

polyfill を深堀りする

この記事ははてなエンジニア Advent Calendar 2020 5日目の記事です。4日目は id:syou6162 さんで、数字のバラ付きを考慮して意思決定する技術でした。

qiita.com

developer.hatenastaff.com

こんにちは、id:mizdra です。今年新卒としてはてなに入社し、WebアプリケーションエンジニアとしてGigaViewerというマンガビューワーを作っています。

最近のはてな社内では「tech-future」という、様々な技術を見つめ直すワーキンググループを運営しています。この会では、ある技術についての要点をまとめるだけでなく、その技術にまつわる歴史を紐解いて整理し、その上で全体を俯瞰して将来その技術がどういう方向に向かうのかを議論し、未来を予測する手がかりを作る、といった挑戦的な取り組みをしています。既に弊社のエンジニアから「tech-future」の取り組みの一部が公開されてますので、是非読んでみて下さい。

developer.hatenastaff.com

developer.hatenastaff.com

developer.hatenastaff.com

この記事では「(Web) Frontend-Ops」をテーマとして開催された回の内、polyfill に関する説明を切り出したものです。 最初は Frontend-Ops 全体をテーマとしてまとめて記事にしようと考えたのですが、polyfill だけでも色々と面白い話が出てきたので、今回は polyfill をピックアップして紹介しようと思います。

polyfillとは何か

polyfill とは、仕様で策定されている機能や Proposal *1 を、それがサポートされていない環境でエミュレートするためのライブラリです。これにより、モダンブラウザでしか実装されてない機能が古いブラウザ(IE11など)でも動かせるようになります。例えば String#startsWithのpolyfillは以下のようになります。

// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith#Polyfill より
if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        value: function(search, rawPos) {
            var pos = rawPos > 0 ? rawPos|0 : 0;
            return this.substring(pos, pos + search.length) === search;
        }
    });
}

このように、その環境にあるAPIを組み合わせて、実装されていないAPIと互換性のあるものを作ることで、古いブラウザでも String#startsWithを呼び出せるようになります。

polyfill の由来

実は polyfill という言葉は元々あったものではなく、2009年に Remy Sharp 氏 (nodemonの作者) によって生み出された造語です。いくつもの(poly)テクニックを使用して足りない穴を埋める(fill)ことから「polyfill」と命名したとのことです。まさにピッタリの語ですね。元々 Introducing HTML5 という書籍を書くにあたって生み出された造語で、書籍を通じて次第に世の中に広まっていったとされています。当時の経緯については氏のブログで紹介されています。

remysharp.com

トランスパイラとの違い

polyfill と同じように、仕様で策定されている機能やProposalをエミュレートする技術に「トランスパイラ」があります。一見すると polyfill と同じように見えますが、ちゃんと違いがあります。

  • polyfillは関数、クラスなどのビルトインオブジェクトのエミュレートを行うもの
    • その環境にあるAPIを組み合わせて、実装されていないAPIと互換性のあるものを作る、というアプローチを取る
    • 全て実行時に行われる
    • ライブラリとして提供される
  • 一方トランスパイラは構文 (syntax) のエミュレートを行うもの
    • a ?? b(a !== undefined && a !== null) ? a : bに変換したり
    • 実行時にできないようなことはこっちでやるイメージ
    • ツールとして提供される

具体例を挙げると requestidlecallbackcore-js が polyfill、babel や tsc (TypeScript のコンパイラ) がトランスパイラに相当します。

shimとの違い

また同様に polyfill と同じように使われる言葉として「shim」というのがありますが、これもまたpolyfillとは違いがあります *2

  • polyfill はエミュレーション対象の仕様と似たようなインターフェイスを持っていて、同じように扱えるもののことを指す
    • 新しいブラウザ向けにstr.startsWith('prefix')と書いたら、それが古いブラウザでも同じように動く、みたいな
    • str.startsWithByPolyfill('prefix')みたいにpolyfillのことを意識して書く必要がない
  • shimはエミュレーション対象の仕様と同じ機能を持っているものの、インターフェイスが異なるものを指す
    • 同じことを実現できるけど、古いブラウザ向けと新しいブラウザでは別々のAPIを使ってそれを実現する、とか
    • 最近だと lazysizes が shim の良い例
      • 画像の遅延ロードをしてくれるライブラリ
      • 新しいブラウザでは<img src"..." loading="lazy">が使えるけど、古いブラウザだとJSで動的にスクロール位置を判定して実現しないといけない
      • これを良い感じにラップして<img data-src"..." class="lazyload">で遅延ロードできるようにするというのが lazysizes の仕事
      • 古いブラウザでも遅延ロードを実現する都合上、<img src"..." loading="lazy">から微妙にAPIが変わってしまっている
      • こういうのを shim と呼んでいる

polyfill の限界

requestIdleCallbackの polyfill である requestidlecallback を例に挙げてみます。requestIdleCallbackは CPU の Idle time に渡されたコールバックを実行するための API ですが、この API は Safari や IE11 では実装されていません。そのため、そうしたブラウザでもrequestIdleCallbackを使ったアプリケーションを実行するには、requestidlecallback を読み込む必要があります。しかし、この polyfill は完全にrequestIdleCallbackの仕様に準拠している訳ではありません。というのも、polyfill というものは実行環境にあるAPIを組み合わせて、実装されていないAPIと互換性のあるものを作る、というアプローチを取っています。そのため、実行環境のAPIで表現しきれないような挙動を実現することができません。例えば、requestIdleCallbackではCPUが idle になっているかを知るAPIがSafariやIE11に存在しないため、requestIdleCallbackの仕様通りに動くpolyfillを作ることができません。そこでこうしたpolyfillでは多くの場合、可能な範囲で互換性を保ち、それっぽく動くよう設計されます。requestidlecallback ではsetTimeoutを用いてコールバックが非同期で実行されるという性質は維持しつつ、idle time でコールバックが実行されるという性質は諦めるような設計となっています。

このようにpolyfillが完全に仕様に準拠しているかは、そのpolyfillがエミュレーションしようとしている機能と、ターゲットとしている実行環境によって変わってきます。polyfillがあるからと言って必ずしも全てのブラウザで同じような挙動をする訳ではない、という点を抑えておきましょう。

polyfillの歴史

現在

今後

昔と比べてブラウザの自動アップデートが当たり前になり、仕様の標準化が進んでブラウザの差異は解消されつつありますが、全てのユーザに最新のバージョンのブラウザの利用を強制するのが困難なこと、またChrome/Safari/Firefoxのようにブラウザが複数存在し、それぞれの実装に差異があることを考えると、今後もcore-jsを使っていくことになるでしょう。以前と変わらず、必要に応じてcore-jsでサポートされていない部分のpolyfillを入れていくようにしましょう。

おまけ: バンドルサイズの削減

近年 Web Vitals などの文脈で、Webでもパフォーマンスが重視されつつありますが、この話は polyfill にも関わってきます。というのも、polyfill はバンドルに含めてユーザに配信するため、polyfill を導入することでバンドルサイズが増えてしまうからです。IE11でしか使われないpolyfillがChromeやFirefoxなどでもダウンロードされてしまう、といったように、本来polyfillを必要としていないブラウザで、余計なオーバヘッドが掛かってしまう恐れがあります。

そうした問題を解決するため、次のような対策が取られることがあります。

  • browserlist+useBuiltInsで必要なpolyfillのみがbundleされるように
  • <script type="module"> / <script nomodule>でエントリポイントを分け、nomodule側にのみpolyfillを仕込む
    • これでIE11だけでpolyfillがfetchされるようになる
    • <script type="module">をサポートするブラウザの中で更に分岐させてpolyfillを出し分ける、といったことまではできない
  • polyfill.io
    • UAを見てそのブラウザに必要なpolyfillだけを返してくれる

おまけ: Proposalにおけるpolyfill

  • まだ仕様化されていない機能をお試しするためにpolyfillが用意される場合もある

おわりに

polyfill の概要の説明から始まり、 閑話休題も挟みつつ、polyfill の今と昔、それから未来について見てきました。polyfill という小さなトピックを取り上げましたが、色々な話が出てきて面白かったですね。そのうち Hatena Developper Blog でも Frontend-Ops をテーマとした記事が色々と出てくると思うので、良かったらそちらもご覧になって下さい。

そうそう、面白い話というと近々弊社のイベントで登壇して、チームにおけるフロントエンドの属人化を頑張って解消していく話をする予定です。近々告知が出る予定ですので、良かったらそちらもご覧になって下さい。 => 告知でました! (2020/12/12 追記)

developer.hatenastaff.com

明日は id:stefafafan さんです。

qiita.com

*1:標準化される前の提案段階の機能のこと。代表的なものとして、ECMAScript の top-level await などがあります: https://github.com/tc39/proposal-top-level-await

*2:こちらも先程紹介した Remy Sharp 氏の記事 が出典です。

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

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