mizdra's blog

ぽよぐらみんぐ

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

この記事はゆゆ式 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 氏の記事 が出典です。

ポケモン乱数調整Wikiという取り組みについて

この記事はPokémon RNG Advent Calendar 2020 1日目の記事です。

adventar.org

皆さんお元気でしょうか。楽しみにされていた方は大変お待たせしました。Pokémon RNG Advent Calendar、2年振りの開催となります。去年はとある問題により開催を渋々見送らさせて貰っていたのですが、今年は何とか開催に漕ぎ着けることができました。めでたい!!!また様々なメンバーによる、ユニークなコンテンツを見れると思うと楽しみですね。クリスマスまで楽しんでいきましょう。

さて、今年のオープニングは「ポケモン乱数調整Wiki」についてです。

背景

乱数調整は、個体値のポケモンを出したい、色違いポケモンを狙って出したい、珍しいアイテムを手に入れたい、などなど誰もが抱くであろう願いを実現するために編み出されました。そしてその有用性や応用性が明らかになるにつれ、乱数調整の手法も発展していきました。今日では単に珍しいポケモンやアイテムを入手するための手段としてだけでなく、乱数調整を行うこと自体に楽しさを見出し、面白おかしな乱数調整を探求する人も現れるなど、乱数調整は多くの人に、様々な角度から楽しまれています。インターネット上ではそうした人々によるコミュニティが形成され、コミュニティから日々乱数調整に関する記事やツールが多く公開されています。

しかしながら、乱数調整について何も知らない状態から乱数調整に挑戦するのは非常に難しい、という話題があります。これは単に乱数調整に関する入門資料がない、というだけでなく、インターネット上に存在する記事の多くで誤解を招く説明や正確でない説明がなされている、というのも一因として挙げられます。よく考えてみるとこれは自然なことで、乱数調整というものはコンピュータの性質 *1 を逆手に取って成り立っているので、どうしても丁寧に説明しようとすると計算機科学などへの理解が求められます。これを理解するのは多少計算機の知識がある人ならまだしも、そうでない人からしたら本当に大変なことです。加えて正確で明瞭な用語を選んで、できるだけ誤解されないような説明をするのも、高度なスキルを要求される非常に難しい作業です。そうしたことを踏まえると、インターネット上に乱数調整の入門の取っ掛かりとなる良い資料が無い、というのは仕方のないこと言えるでしょう。

とはいえ仕方がないといっても乱数調整に入門したい人からしたら困ったままです。加えて、これは乱数調整に慣れている人にとっても深刻な問題です。多少知識があったとしても、不正確な表現がどれであるのかを見抜き、自信を持ってこれは違う!と言える人は僅かです。誤った理解をしてしまい、それを元に誤った記事を書き、また誤った理解が伝播していく…というように誤解はどんどん伝播していきます。事実、インターネット上ではこうした記事が溢れてしまっているのが現状です。

どうするか

さてこれは困った、ということでコミュニティ内でも話し合いが行われ、様々な意見やアイデアが出ました。その中で良さそうなのでやってみよう、ということで動き出しているのが「ポケモン乱数調整Wiki」という取り組みです。

ポケモン乱数調整Wiki

これはポケモンの乱数調整に関する情報を集約した、コミュニティベースによって運営されるwikiを作る、という取り組みです。様々な工夫があるのですが、ここがポイントというのをいくつか書いてみます。

  • 乱数調整に関する情報を集約する
    • 従来はコミュニティメンバーそれぞれの個人ブログなどに別個に書かれていた情報を1箇所にまとめることで、ここさえ見ておけば基本的な情報は手に入る、という状態を目指します
    • 集約することで、説明に登場する用語や表現が統一され、より理解しやすい資料になることを期待しています
  • Wiki内の各記事は査読を通す
    • 記事を公開する前にその記事の内容が妥当かどうか、不正確な表現がないか、誤解を生む説明になっていないかをコミュニティメンバーがチェック・記事の修正をフォローする「査読」という仕組みを導入します
      • 識者が記事のレビューをしてくれるシステムだと思ってもらえれば良いです
    • これにより、Wiki内のコンテンツの品質がある程度保証されることを期待しています
  • 入門者向けのガイドを整備する
  • 無理なくWikiが運営できるように
    • 記事の執筆や査読などをコミュニティメンバーで賄えるよう、無理のない範囲で運用する仕組みを整えます
      • 執筆者向けのガイドを用意して簡単に記事を寄稿できるようにする、査読フローを一部自動化する、みたいなことを考えてます
      • 査読についても特定の人物に負担が集中しないよう、上手く複数人に分散されたり、何かしらの策を検討してます
      • 査読やったことないけど興味ある人に声掛けして査読者になってもらって査読者自体の人数を増やせないか、みたいなことも考えてます
    • これには記事の執筆をしやすくする、といった取り組みも含まれます
      • 査読者サイドなどで記事の書き方などをサポートすることで、誰でも無理なく記事を寄稿できるようなフローを整える予定です

今の所査読者をやってもらえそうな人や記事を書いてくれそうな人に声掛けしたり、Wikiの基盤を作ったり…という感じでまだ1コンテンツもない状態なのですが、少しずつ進めております…!Wikiを立ち上げて、査読のフローを整備して、お試しでお手本となる記事を用意して、一般のコミュニティメンバーに記事を寄稿してもらえる体制を作って…というようにまだまだやることは沢山あるのですが、今暫くお待ち下さい。また続報があればブログ等でお知らせしようと思います。その時にまたWikiを見てフィードバックを頂けたり、Wikiの立ち上げにご協力いただけると助かります。あ、もちろん記事の執筆者や査読者も募集中です!我こそはという方は是非お声がけ下さい。お待ちしてます :)

おわりに

という訳でポケモン乱数調整Wikiという取り組みについてご紹介しました。本当は今頃には皆さんから記事の寄稿を受け付けられる状態にしたかったのですが、全然作業が進んでいなくて申し訳ない!すみませんすみません… 中々まとまって作業する時間取れなくて今暫くお待ち下さい。

Pokémon RNG Advent Calendarですが2日目は担当者不在なので、次は3日目の @sub_827 さんかな? 楽しみですね。あ、もちろん2日目書きたい人がいらしたら今すぐ投稿されても良いんですよ…?

今年もPokémon RNG Advent Calendar楽しんでいきましょう!

adventar.org

target=''_blank" な <a> タグに noopener だけでなく noreferrer も付けるべきか

追記

  • 2020/10/29: モダンブラウザによって noreferrer が自動的に付与されつつある、という説明をしていましたが、正しくは noopener の間違いでした。失礼しました。既に記事の方は修正済みです。

追記終わり。


  • target="_blank" は新しいタブでリンク先を開くためのオプションです
  • しかし、これにはリンク先のページからリンク元のページの window オブジェクトにアクセスできるという、ちょっと癖のある挙動があります
  • この挙動自体は脆弱性ではありませんが、 フィッシング詐欺などに悪用される可能性がある、危険な挙動であることが知られています
  • リンクのへの rel=noopener 付与による Tabnabbing 対策 | blog.jxck.io

noopener

  • そこで一般には、target="_blank"<a> タグには noopener を指定することが推奨されています
  • また、このオプションは Spectre 対策としても利用されます
    • Site Isolation 及び Web のセキュリティモデルの更新 | blog.jxck.io
    • 今まではリンク先のページからリンク元のページへの参照があるために、互いにメモリを共有する必要があり、どうしても同じプロセスで実行しなければならず、結果として Spectre の影響を受ける可能性がありました *1
    • noopener を付けると互いのページが別々のプロセスで実行されるようになり、リンク先のページからリンク元のページのメモリ上のデータを推測することが不可能になります
  • つまり「リンク元のページの window オブジェクトの保護」 + 「リンク元のページのメモリ上に展開されているデータの保護」が noopener の役割

余談: どういう時に noopener を付けるか

  • じゃあ target="_blank"<a> タグ全てに問答無用で付けなければならないのか、という疑問が当然出てくると思いますが、実はそうでもないです
  • リンク元のページの window オブジェクトの保護や、リンク元のページのメモリ上に展開されているデータの保護が不要な時はなくても問題ありません
  • 例えば以下のようなケースでは付けなくても問題ないです
    • リンク先が same-origin なページの場合
      • 悪意のある JS から window.opener を参照されるのは困るけど、same-origin なら所有者は我々であり、悪意のあるコードはないはずなので、 window.opener を参照されても問題ない、という発想
      • 同様の理由でメモリを覗き込まれても問題ないはず
    • リンク先から window.opener をどうしても参照したい時
      • window.opener 使いたいんだったら noopener 付けたら駄目でしょ、という当たり前の話
      • けどこれからの Web は Site Isolation をどんどん推し進めていく方向に発展すると思われるので、可能な限り window.opener を使わないほうが良いです
      • 代わりに MessageChannel という代替技術を使うのが推奨されています
  • とはいえいちいち same-origin かどうかを判定するのは面倒
    • なので基本的には target="_blank"<a> タグ全てに noopener を付けておく、という規約を導入するというのが個人的にはオススメです
      • 別に付けても何ら問題ではない / 付けておくだけ得 / 思考の手間が無くなる
      • うっかり noopener 付いていない <a> タグをコピペしてきて、 フィッシング詐欺に発展してしまう、みたいなことも防げる
    • あとは COOP を有効にするのも手

余談: 自動的な noopener の付与

noreferrer

  • 同じく target="_blank"<a> タグに指定できるオプションとして noreferrer というものがあり、これも noopener と合わせて使われることがあります
  • noreferrernoopener と一緒に使われるのは、古いブラウザで window.openerundefined にするため
    • というのも、window.opener が問題視され始めた当時は、noopener をサポートしていたブラウザには限りがあったため
    • 一方 noreferrernoopener よりも古くからあるオプションで、 IE11 でもサポートされている (ただし win10 の IE11 のみ / win7〜8.1 の IE11 ではサポートしてない)
    • そのため、noopener の fallback として、noreferrer が使われることが多かったはず (はず、というのは僕が当時の Web の状況に詳しくないから)
  • しかし noreferrer には副作用があります
    • Referer header が無くなるので、リンク先からどこからユーザが流入してきたのか追えなくなってしまう
    • アクセス分析をする時に困るかも
  • 一応回避策はあることにはある
    • ?utm_campaign=... みたいなクエリを URL につけておくとか
    • けどこういうので全部カバーしきれるかは分かっていない
    • アクセス分析が重要なサービスでは安全側 (ユーザではなくアクセス分析する我々にとっての安全側) に倒して付けないほうが良いかも?
      • ユーザの安全と我々の安全を天秤にかける、みたいな

今も noreferrer を付けるべきなのか

  • アクセス分析をガリガリやっていきたいとかなら付けないほうが良さそう?
    • けど Referer header があることでどれくらいできることが広がるのか、というのはよく分かっていない
    • noreferrer を付けないと IE11 ユーザを危険な状態に晒すことになるので、慎重に検討しましょう
  • リンク元の URL が外部に流出してほしくないなら付けたほうが良さそう
    • 社内限定のサービスとか
    • YouTube の限定公開 URL とか
  • 単に noopener の fallback として使いたい場合...
    • これは結構悩ましい。サービスによって判断すると良いのではと思ってます
    • IE11 以外のモダンブラウザでは noopener サポートされているので、IE11 サポートしていないサービスでは noreferrer 付けなくて良いはず
    • 一方 IE11 をサポートするブラウザでは noreferrer を付けておくと安全
      • ただしアクセス分析をする時に困る可能性があるので、そこは頑張って天秤にかける、という形になりそう
      • 悩ましい!!!
    • この辺詳しい人に聞いてみたい

*1:では noopener 付けていないページは今危険な状態に晒されているのか、というとそうでもないです。というのも、Spectre を成立させるにはメモリを覗きたいページが同一プロセスに載っていること、高精度のタイマー API が利用可能であること、という 2 つの条件がどちらも成り立っていなければなりません。Spectre が発見されてすぐにブラウザはこの後者の API を JS で無効にしていて、原理的に Spectre を成立しないようにしています。じゃあ Spectre 成立しないなら noopener 付けなくて良いじゃん、というとそうでもなくて、window.opener があることで高精度のタイマー API をブラウザが提供することができなくなってしまいます。他にも、window.opener のセキュリティ上の懸念で実装できない API が今後出てくるかもしれません。そうした API をブロックしてしまわないよう、noopener を付けていく / window.opener を無効にする、というのが今後ますます重要になってきます。要は付けないと Web の進化に置いてかれます。

150B で動く strictly-typed な Event Emitter を作った

超軽量で strictly-typed な (強く型付けされた) Event Emitter を作りました。

github.com

背景

従来 Event Emitter は ブラウザでは EventTarget として、Node.js では EventEmitter として、それぞれ独自に実装されていました。これらは機能的には変わりがありませんが、インターフェイスの互換性がありません。そのため、両方の環境で同じ API で動作する universal な Event Emitter が欲しければ、eventemitter3 のようなライブラリを利用する他ありませんでした。しかし、最近になってブラウザで実装されている EventTarget が Node.js に実装され始め *1*2EventTarget が Universal な Event Emitter としての地位を確立しつつあります。

EventTarget はネイティブな API であるため、eventemitter3 のような 3rd-party ライブラリと比較してサイズが小さい *3 という利点があります。しかし、一方で EventTargeteventemitter3 のように dispatch するイベントの種類を制限したり、addEventListener に渡したイベント名からリスナーで受け取れるイベントの値の型を推論することができません。そのため、予期せぬ種類のイベントが dispatch される可能性を考慮してコードを記述する必要があったり、明示的に型チェックを行わなければならないといった問題がありました。

@mizdra/strictly-typed-event-target の紹介

そこで、@mizdra/strictly-typed-event-target というライブラリを作りました。EventTargetCustomEvent をベースに作られているため、とても軽量です。ES Module 形式と UMD 形式の2つのビルドが用意されていて、この内 ES Module 形式はなんとたったの 150 B (厳密にいうと 152 B) で構成されています。

使い方

FooEventMap のようなインターフェイスにイベント名とイベントの値の型のリストを記述し、createSTEventTarget に型パラメータとして渡すと、強く型付けされた CustomEventEventTarget が返り値で得られます。簡単ですね。eventemitter3 を触ったことのある人なら分かると思うのですが、EventMap インターフェイスを渡して型付けする部分は eventemitter3 を真似てます。

import { createSTEventTarget } from '@mizdra/strictly-typed-event-target';

interface FooEventMap {
  onmessage: string;
  onerror: Error;
  oninstall: undefined;
}

const [FooCustomEvent, FooEventTarget] = createSTEventTarget<FooEventMap>();

FooCustomEvent / FooEventTarget はネイティブのものと同じ API が生えていて、全く同じように使えます。ただし、FooCustomEventdetail フィールドが EventMap に応じて型付けされていたり、EventMap で定義されていないイベントの dispatch が禁止されていたりと、より強く型付けされた APIとなっています。

const fooEventTarget = new FooEventTarget();

/** `addEventListener` */

// error: Argument of type '"invalid-event"' is not assignable
// to parameter of type '"onmessage" | "onerror" | "oninstall"'.
fooEventTarget.addEventListener('invalid-event', () => {});
fooEventTarget.addEventListener('onmessage', (event) => {
  // `event.detail` is infered `string` type.
});
fooEventTarget.addEventListener('onerror', (event) => {
  // `event.detail` is infered `Error` type.
});

/** `dispatchEvent` */

fooEventTarget.dispatchEvent(
  new FooCustomEvent('onmessage', { detail: 'hello' })
);
// error: Type 'Error' is not assignable to type 'string'.
fooEventTarget.dispatchEvent(
  new FooCustomEvent('onmessage', { detail: new Error() }),
);
fooEventTarget.dispatchEvent(new FooCustomEvent('oninstall'));

152 B のトリック

実装をよく見てもらえると分かるのですが、ライブラリの大部分は型情報で、JS にトランスパイルされた時に残る部分は createSTEventTarget の実装部分だけです。createSTEventTarget も本当に素朴なことしかしていなくて、ネイティブの CustomEvent / EventTarget をただ strictly-typed な CustomEvent / EventTarget へと type assertion しているだけです。ズルいですね。

export function createSTEventTarget<EventMap extends {}>() {
  const STCustomEvent = (CustomEvent as unknown) as STCustomEvent<EventMap>;
  const STEventTarget = EventTarget as STEventTarget<EventMap>;
  return [STCustomEvent, STEventTarget] as const;
}

そしてこれをトランスパイルし、ES Modules 形式で出力したものが以下になります。これで丁度 152 B です。

export function createSTEventTarget() {
    var STCustomEvent = CustomEvent;
    var STEventTarget = EventTarget;
    return [STCustomEvent, STEventTarget];
}
//# sourceMappingURL=index.js.map

ちなみにより踏み込んだ話をするなら、minify すればもっと小さくなりますし、何なら以下のように型定義だけ使えば、トランスパイル時に型定義が削除されて 0 B になります。まあたった 152 B を嫌って長ったらしい型注釈を書きたい人は居ないはずなので、ただの面白テクニックという感じですが。

import {
  STCustomEvent,
  STEventTarget,
} from '@mizdra/strictly-typed-event-target';

interface FooEventMap {
  onmessage: string;
  onerror: Error;
  oninstall: undefined;
}
const FooCustomEvent = (CustomEvent as unknown) as STCustomEvent<FooEventMap>;
const FooEventTarget = EventTarget as STEventTarget<FooEventMap>;

変更履歴

  • 2020/09/21
    • 本ライブラリが他のライブラリと比較して高速であるという旨の説明をしていましたが、実際には割と遅いほうだということが判明したため、速度に関する表記を削除しました (参考1, 参考2)
    • 当初既存の EventTarget が型安全ではないという説明がなされていましたが、これは誤りでした。実際には EventTarget は型安全であり、本ライブラリはそれをより強く型付けしたライブラリという位置づけです。そこでそれを反映すべくライブラリを strictly-typed-event-target から strictly-typed-event-target に rename しました。
    • サイズがgzip後のものを指していたため、gzip前のサイズになるよう本文や記事タイトルを書き換えました (151 B => 158 B)
    • 合わせて関数名の rename を行った結果、サイズが少し減りました (158 B => 152 B)

*1:2020/09 現在ではまだ一般ユーザに公開されていない、internal で実験的な機能です。

*2:IE11及びSafariでは一部のAPIがサポートされていません。

*3:むしろ一切バンドル不要なので、実質 0 B

趣味で創作する時は常に何かしら新しいことに挑戦する

  • 普段趣味プログラミングで何か作る時、何かしら新しいことに挑戦するということを意識している
    • 例えば何か触ったことのない技術を導入してみるとか、採用したことのない開発手法を取り入れてみるとか
    • より具体的に言うとHeroku導入してみるとか、TDDで開発してみるとか
  • 折角何か創作活動をするので、ついでに新たなことに挑戦し、新たな学びやスキルの向上へと繋げようという狙い
  • ここまではよくある話だと思うけど、mizdraの場合は更に踏み込んで、「新しいことは数を絞って注力できるようにする」ということも意識している
    • 新しいことに挑戦するのは多くの場合、非常に負荷が掛かる
      • 例えばHeroku導入するにしても、HerokuのCLIやダッシュボードの使い方を学ぶ必要があるし、PaaSを触ったことが無ければそもそもPaaSとは、一体何が出来てどこまで面倒を見てくれるのか、ということから学ぶ必要がある
    • 挑戦する数が多いと、それだけ1つあたりに避ける学習時間が減ることになる
      • 学習時間が減って新しいことを上手くモノに出来ない、となるのは勿体ない
      • 折角挑戦するので、1つあたりの学習時間を確保し、より多くのことをモノにできるようにすると良い
    • そこでmizdraがプログラミングする時は新しいことは基本1〜2個、多くて4〜5個になるように努めている
      • 少なければ少ないほど1つあたりに割く学習時間が増え、より深い学びへと繋げられる
    • 学ばなければならないことが多すぎてこれに挑戦するのは大変そうだなと思ったら、無理して挑戦せず、フレームワークなどを導入して楽をすると良い
      • 分からないことには蓋をする
    • 回数を積み重ねれば、どんどん見識が広がっていく
      • 無理ないペースで段階的に学んでいくことができる
      • 見識が広がっていけば、最初はフレームワークを使って見てみぬフリをしてきた部分にもいつしか学習の目を向けられるようになるはず
      • mizdraの話だと、以前はWebフロントエンドに閉じこもり、バックエンド触るの回避し続けてたけど、サークルの雑務や業務などを通じて少しずつ知識が備わってきて、最近バックエンドを触り始めている
      • SQLはまだよく分からないのでORMで蓋をしている
  • また、もうちょっと踏み込んで「新しいことには丁寧に取り組む」「時間を割くことに躊躇しない」ということも意識している
    • もう少し別の言い方をすると「力を入れる」とか
    • 折角挑戦するのでより深く学び、使いこなせるようになると良い
    • 例えばあるツールのオプションが存在して、ググって出てきた記事にオススメですと書かれていたので真似して利用する、ではなく、オプションを付けると何が起こるのか、どういう時に使われることを想定しているのか、オプションが実装された背景は何か、今回利用するのは本来の用途的にはふさわしいのか、といったことを必要に応じて徹底的に調べていく
    • 例: React 副作用分割パターン - mizdra
      • React Hooksを触った際に「React Hooksはどう使うのか」から更に深く思考して、「そもそもReact Hooksって副作用を分割するものだよね」、「そういえば副作用を分割する技術には継承・mixin・HOC・render propsと過去に色々発明されてきたよね」、「それと違うところはなんだろう」、「既に副作用を分割する技術があるのに発明が続いたということはそれぞれに欠点があってそれを改善しようとした意図があるはずだよね。だって技術は螺旋だから。その意図はなんだろう。」「そもそもそれぞれの技術の利点・欠点や生まれた背景は?」「それらを踏まえてReact Hooksでは何が良くなった?」とどんどん深堀りしていった
      • 深堀りした結果、React Hooksが任意の副作用の発生する場で使えることが分かったり、過去の発明の利点や欠点が分かったり、React Hooksの優れている点が分かったり、React Hooksは偉大な発明ですねと自信を持って言えるようになった
    • とはいえ全てを深堀りしていっては時間が足りなくなる
      • これも数を絞って狙い撃ちすると良い
      • mizdraは興味のある場所や、深堀りすると勉強になりそうな場所、ググって出てきた記事で分かったようで分からないような解説がなされている場所などを狙い撃ちして深堀りしています
    • 新しいことを学ぶ機会はとにかく大切にする
  • 「趣味で創作する時は常に何かしら新しいことに挑戦する」という姿勢はプログラミングだけでなく執筆活動でも意識している
    • 「普段は動機の項を雑に書くけど今回は丁寧に書こう」、「普段は生真面目な記事を書いているけど今回は緩い感じで笑ってもらえるような記事を書こう」、「本書いたことないので本書こう」、「『はじめに』を丁寧に書いて読者が釘付けになるようにしよう」「普段はプログラマー向けの記事を書くけど、今回は対象読者を広げてデザイナーなどにも読んでもらおう。そのためにそうした人々にとって分かりやすい表現を心がけよう。」 などなど
    • プログラミングや執筆活動に限らず、色々な場面で適用できるはず

過去の実例たち

ポートフォリオに載っけられるよう、学んだ知見はできるだけ何らかの形にしてオープンインターネットに放流しているので色々ある。何が言いたいかと言うと挑戦した結果をついでにアウトプットしておくと武器になってお得ということです。

  • 乱数調整 入門 - mizdra's blog
    • Advent Calendar参加したことないので参加してみた (そもそも初参加 & 初主催だった)
    • 長文ブログ書いたことないので書いてみた
    • 教材的なものを書いたことがないので書いてみた
  • Headless Chrome を使って自動車学校の技能教習の予約が空いたら通知するスクリプトを書いた - mizdra's blog
    • puppeteer出たばかりだったので触ってみようということで触ってみた
  • Emtimerの紹介 - mizdra's blog
    • 今まで開発動機やプログラムの説明を丁寧に書いたことなかったので、今回は丁寧に書いてみようということで書いてみた
  • 遅刻可視化ツールの紹介 - mizdra's blog
    • リッチなUIのWebアプリ作ったことなかったので作ってみた
    • あと裏テーマとして、今まで雑に技術選定してきたので、今回はそれぞれのフレームワークやライブラリを比較・検討し、ちゃんと良し悪しに自信を持って採用しましょう、という挑戦もしていた
  • WebAssembly 開発環境構築の本を公開しました - mizdra's blog
    • wasm初導入/電子書籍初執筆/VuePress初導入など色々挑戦した
    • あと裏テーマとして宣伝方法を工夫して、ブクマを集めてみましょうという挑戦もしていた
      • ブログで丁寧な紹介エントリも書きましょうとか、タイトルは「執筆しました」ではなく無料で読めることが分かるよう「公開しました」にしましょうとか気をつけていた
  • Google Play Music 向けの #NowPlaying 拡張機能を公開しました - mizdra's blog
    • ブラウザ拡張初開発/WebExtensions API導入
  • WebAssemblyを使って乱数調整ツールをWebに移植した話 - mizdra's blog
    • 初めてのwasmを使ったアプリケーション
    • 真面目にアプリケーションの性能評価したことがなかったので、この際wams導入で速くなりました、ではなくどれくらい速くなったのかをグラフを使って分析した
    • あとはプログラムのアーキテクチャ図を書いたことがなかったので、drawioを使って頑張って書いてみた
  • 日向縁さんの誕生日をお祝いしてゲームを作った話 - mizdra's blog
    • ゲーム作ったことなかったのでゲーム作った
    • 当時React Hooks登場したばかりだったのでReact Hooks導入してみた
  • React 副作用分割パターン - mizdra
    • ゆかりスロットを実装した際の知見をアウトプットしたもの
    • 過去の技術を比較してどう良くなったのか、というのを丁寧に分析したことがなかったので、丁寧に分析してみた
    • scrapboxでちゃんとした記事を書いたことがなかったので、使用感を把握するためscrapboxで記事を書いてみた
    • 鋭い洞察を連続して繰り出すことで、長文だけど読者が飽きずに最後まで読み進められるよう心がけた
      • ネタに走らず読者の関心を保つ、という1つの挑戦
      • 結果として多くのブクマを集められた
  • mizdra on Twitter: "ゆゆ式 Advent Calendar 2019 21日目の「今日のゆゆ式」の動画です #ゆゆ式ac https://t.co/tezNbs9JTK… "
    • ハードウェアプログラミングっぽいことしたことないなと思って、レシートプリンタ触ってみた
      • PDFやPostScriptのこと、ESC/POSのこと、ネットワーク越しにプリンタを操作する方法、lpコマンド、cups、ドライバの仕組みなどについて学ぶことができた
      • その後知見はTM-T88IV 接続メモ - mizdraにまとめた
    • Raspberry Pi触ったのも初めてだった
    • 当然4コマ漫画裁断して画像処理するのも初めてだった
  • ebith/Switch-Fightstick - mizdra
    • 工作したことがなかったので工作した
    • ハードウェアプログラミングの素振りはレシートプリンタでしていたので、結構すんなりできた
      • 見識の広がりによって段階的に学ぶことができた
  • iTerm2 で `cat /dev/urandom` すると印刷ダイアログが出ることがある - mizdra's blog
    • 面白技術ネタ書いたことがなかったので書いてみた
    • 文体も普段の生真面目なものから趣向を変えて、陽気な人間を意識して書いてみた
      • 普段は「20へえ」とか書かない
      • これも文章による表現方法を増やすという、1つの挑戦です
  • 画像による Layout Shift が無くなる Web がやって来る - mizdra's blog
    • 『はじめに』を丁寧に書いて読者の注意を引くことで、スクロールバーから文章量を察して読者が離れてしまわないよう心がけた
    • 論文執筆で学んだ姿勢をブログでも活かしてみようと思って活かしてみた
    • 普段はプログラマー向けの記事を書くけど、対象読者を広げてデザイナーなどにも読んでもらおうと思って丁寧に書いた
    • jxckさんっぽい技術記事書いてみたいなと思って真似してみた
    • React 副作用分割パターン - mizdraの時と同様に、鋭い洞察を連続して繰り出すことで、長文だけど読者が飽きずに最後まで読み進められるよう心がけている
      • 身につけたスキルを活用し、段階的に学んでいっている
    • React 副作用分割パターン - mizdraではReactという関心を集めやすい技術を扱っていて、対してこの記事ではそうではないものをテーマとしてブクマを集めるという挑戦にもなっていた

Scrapboxのページ内に埋め込まれているアイコンをsuggestして挿入できるUserScript作った

Scrapboxで会議の議事録を取っていると、誰の発言かを記録するために他人のアイコンを挿入したい、という場面が出てくる。Scrapboxでは ctrl+i と入力すれば簡単に自分のアイコンは挿入できるけど、他人のアイコンを簡単に挿入する方法は意外にない。一応以下のような手順で任意のページのアイコンを挿入できるというテクニックもあるけど、キーボード上を結構ダイナミックに指を動かす必要があってお手軽ではない & そもそもアイコンが無いページもポップアップに出てくるのでノイズが多い。

  1. [ キーを押す
  2. scrap と入力
  3. scrap にマッチするページ一覧がポップアップで出てくるので、Tabキーを数回押して scrapbox にカーソルを合わせる
  4. ctrl+i[scrapbox.icon] を挿入

そこでもっとお手軽に他人のアイコンを挿入できるUserScriptを作った。

gyazo.com

ctrl+l と押すとsuggest-boxが表示されて、ページ内に埋め込まれているアイコンがポップアップで表示される。boxにキーワードを入力すると、そのキーワードにマッチするアイコンが絞り込まれる。Scrapboxネイティブのポップアップと同じように、Tabキーでカーソルを移動し、Enterで確定できる。

パフォーマンス上の制約から、初めにsuggest-boxを押したタイミングでページ内を解析し、ヒットしたアイコン一覧がページごとにキャッシュするようになっている。そのため、一度suggest-boxを表示した後そのページに無かった種類のアイコンを挿入すると、そのアイコンはsuggest-boxには出てこない。もし後から追加したアイコンもsuggest-boxに出したければブラウザリロードするか、suggest-boxの表示中に ctrl+r を押すと良い。キャッシュが破棄・更新され、アイコンが出るようになる。

導入方法

UserScriptを有効化した上で自分のページに以下のスクリプトを追加する.

// ref: https://scrapbox.io/mizdra/icon-suggestion
import { registerIconSuggestion } from '/api/code/mizdra/icon-suggestion/script.js';
registerIconSuggestion();

おまけ

icon-suggestionを作る過程でいくつか有用なコード片が生まれたので、ライブラリとして切り出して公開しておきました。良ければお使い下さい。

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

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