PrivacyLabという組織でプライバシーに配慮した広告配信手法の研究、開発をしている松崎(@ppmasa8)と申します。
まずは、私たちが開発している広告配信プロダクトについて説明します。
2018年のGDPRの施行を初めとして、近年欧米を中心にプライバシー保護の基準が厳しくなっています。これに伴い、Cookieに対する規制も厳格化されています。
iPhoneユーザーであればご存知かもしれませんが、Safariブラウザでは2020年3月からデフォルトでサードパーティクッキーをブロックする仕様となりました。Chromeブラウザにおいても、2024年後半から、すべてのユーザーのサードパーティクッキーを段階的に廃止する計画でした。
しかし、2024年7月にGoogleはサードパーティクッキーの利用をユーザーが選択する方針へと転換しました。この新たな方針は、https://blog.google/intl/ja-jp/products/android-chrome-play/privacysandbox/で詳しく説明されています。
この状況を踏まえ、GoogleはChromeブラウザを通じて、Cookieに代わるユーザーのプライバシー保護に配慮したAPIの開発を主導しています。この取り組みは「Privacy Sandbox」と呼ばれるプロジェクトとして進められており、多くの企業や組織が参画しています。
Privacy Sandboxの仕様と制約
PrivacySandboxの中には従来の広告配信のユースケースを置き換えることを目的としたAPIがあり、それを使って広告配信をする検証をしています。Privacy Sandbox を使った広告配信システムでは、以下のような広告配信フローになっています。
(参考:https://developers.google.com/authorized-buyers/rtb/protected-audience-api?hl=ja )
web広告ではwebサイトの広告枠を複数の広告主がオークション形式で購入します。このオークションはwebサイトと広告主のサーバー間でAPIを介して実行されます。また、Protected Audience APIは上記のサーバー間でのオークションと同様の売買をユーザーのブラウザ内で行う仕組みになっています。
一般に広告の入札ロジックは変更の頻度が高く複雑になる傾向があり、入札額の計算などお金に関わる部分でもあるため、高い生産性と品質が求められます。そのため、linterとformatterは不可欠です。
なぜ Biome を採用したか
PrettierやESLintもありますが、Biome を採用した理由には大きく分けて2つあり
- ビルド時間が速い
- 運用・設定コストが低い
ことが挙げられます。
Biome とは
まずはじめに Biome についてざっくり紹介します。
Biome は JavaScript や TypeScript のツールチェーンで、主な機能としてはフォーマッターやリンターが挙げられます。簡単に言うと Prettier と ESLint が1つになった感じです。
ビルド時間が速い
Biomeは、フォーマットだと Prettier の約 25 倍高速で、リンターだと ESLint よりも約 15 倍高速だと謳っています。
実際、正確な比較ではありませんが、自身が携わったプロダクトでのビルド時間の計測をしてみると以下のようになりました。(なお複数回計測をしましたが、以下の結果と大差はありませんでした。)
Format
- Biome
npx @biomejs/biome format . 0.47s user 0.12s system 136% cpu 0.426 total
- Prettier
npx prettier . --check 7.06s user 0.95s system 147% cpu 5.416 total
約13倍Biomeのほうが速い
Linter
- Biome
npx @biomejs/biome lint . 0.47s user 0.12s system 138% cpu 0.428 total
- ESLint
npx eslint . 3.13s user 0.22s system 189% cpu 1.769 total
約4倍Biomeのほうが速い
両方
- Biome
npx @biomejs/biome check . 0.48s user 0.13s system 140% cpu 0.435 total
- Prettier & ESLint
npx prettier . --check && npx eslint . 7.05s user 0.89s system 146% cpu 5.403 total
約13倍Biomeのほうが速い
こうしてみると、Biome の速さが圧倒的であることがわかると思います。
運用・設定コストが低い
Biome は Prettier の思想と同様に「Opinionated」であるために、オプションが少なく、一貫性のあるコードへフォーマットしてくれます。
これは、理想的にはコードをフォーマットする正しい方法は 1 つだけであるというように想定して、常にそのスタイルを強制することを意味しています。プロジェクトやセットアップに影響されずにフォーマットするため、Golang のような常に整頓された、常に同じようなコード形式になります。
リンターの方は ESLint との互換性を意識しているために大概の設定はプリセットに組み込まれており、基本的にはプリセットをそのまま使うことを想定しています。
したがって、チームごとまたはプロジェクトごとに空白やインデント、コード形式等の議論に時間を割くこと無く、新しくチームに入る人にも直感的にわかりやすいかつ、IDE のプラグインにもシームレスに対応していることから、スムーズに開発に着手することができ、ジュニア層からシニア層まで幅広く運用や設定のコストが低いメリットを享受することができます。
また、リンターの方は、大体のルールは recommended に含まれており、そのプリセットを使用する形となりますが、個別にルールを有効にしたり無効にしたりすることもできます。
ちなみに、BiomeチームやBiome organizationによってメンテされているプラグインは、VSCode,IntelliJ(JetBrains系)であり、サードパーティ製のプラグインとしてはneovim, helix, coc-biome, sublime textになります。
導入方法は、公式に載っていますので見てみてください。
既存の方法との違い
Prettier や ESLint との違い
Biome フォーマッタは Prettier とほぼほぼ(Biome 公式が言うには 96%程度)互換性があり、主に js, ts, jsx, tsx が対象となっています。
Prettier といくつか異なる点を上げるとすれば、
- ES5 において有効な識別子のみ引用符を外す
- 計算プロパティでの代入で一貫性のある挙動を示す
- 必要ない場合にアロー関数の型パラメータに末尾のカンマを追加しない
- カッコでくくった non-null アサーションを含んだオプショナルチェーンに対して一貫性のある挙動を示す
- 無効な構文を format しない
などの点が挙げられます。
詳しくは、https://biomejs.dev/ja/formatter/differences-with-prettier/に書いてありますので気になった方は参照してみてください。
Prettierとの出力結果の違いについてより具体的に詳しく知りたい場合は、https://biomejs.dev/playground/が公式から用意されているので、実際のコードベースで確認することができます。
また Biome リンターについて、ESLint 等でよく利用されている recommended ルールの 8 割以上は Biome でも実装されており、よほど大規模なプロジェクトかつ特殊な使い方をしていない限りは互換性があると言っても差し支えないため、ルールに関して気にしなくても良さそうです。
細かいルールに関しては、https://biomejs.dev/linter/rules-sources/にまとまっているので参考にしながら設定することができます。
一方でTypescriptを主に使用しているプロジェクトにおいては注意が必要で、ESLint + typescript-eslintが備えている機能である型チェックに関してBiomeはすべてのtypescript-eslintのルールをカバーしているわけではないです。
したがって、Typescriptを使っているプロジェクトではこのhttps://github.com/biomejs/biome/discussions/3#typescript-eslintを参考にして、どの型チェックが犠牲になるか検討する必要がありそうです。
なぜ高速か?
前述のように Prettier や ESLint と比較すると異次元の速さをしていますが、その理由としては、もちろんパーサやアナライザーなどが Rust で書かれているためそもそも実行速度が速いというのもありますが、パースの方法に違いが存在しています。
Prettier などの通常のパーサであれば、直接コードを AST に変換しますが、
Code -> Lexing -> Parse -> AST tree -> ...
Biome は Rowan という red-green tree という構文木の一種を扱うためのライブラリをフォークして使っており、Green tree と Red tree を間に咬ませることでインクリメンタルな再パースを可能にすることで高速化しています。
Code -> Lexing -> Parse -> Red-Green tree -> AST tree -> ...
Green tree は不変かつ再利用可能な構造をもっているため、ソースコードの一部が変更された場合でも変更されていない部分の木は再利用することができ、全体を再パースする必要がありません。
また、Red tree は Green tree に対するビューを提供しており、親子関係などの情報を保持しており、これによって従来の方法よりも特定のノードに対する操作が容易になっています。
Green tree と Red tree を使う方法は、高速化のみならず、エラーの捕捉のしやすさだったり、リカバリーがし易いというメリットもあります。
ちなみに、Green-Red treeの名称は、設計会議で使用したホワイトボードマーカーの色に由来するそうです。
思想の違い
Biome は OSS で作成されており、基本的に今後の開発方針がパブリックになっている Github discussions, Discord でディスカッションされています。また、プロジェクトの意図や決定を事前に周知するように努めているため、なぜこの変更が入ったのかが後から見てもわかりやすく、いきなり破壊的な変更がくることは少ないように思います。(多分)
いろいろな取り決めや事柄を明文化することに努めているっぽいことがhttps://biomejs.dev/internals/philosophy/からもわかります。
使用するにあたっての懸念点
前述の広告配信フローを見ると、advertiser側はオンデバイスオークションに参加するために、generateBidという関数を実装する必要があります。この関数は広告の入札価格と広告の情報を生成しますが、以下のようないくつかの制約が存在しています。
したがって、Module Script (ES Modules) という、js のモジュール機能を使うような現在の js で広く用いられており Biome にも採用されている考え方とgenerateBidを実装したい我々のユースケースは競合してしまいます。
チームでの使用例
Biome 等のリンターには、基本的に特定のルールに対して無効化するコメントを使ってその部分を無視してプロジェクト全体のルールを変更せず一貫性を保てるような仕組みや、そもそも該当のファイルを無視してリンターを掛ける機能が存在しているため、
例
// biome-ignore lint:
// biome-ignore lint/suspicious/noDebugger:
どうしても実装の制約とBiomeのルールが競合し、Biomeに変更してほしくない箇所についても、上記のような方法で対応することができます。
実際のチームでの使用例
- Biome.json
{
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
"organizeImports": {
"enabled": true
},
"files": {
"ignore": ["(省略)"]
},
"linter": {
"enabled": true,
"ignore": ["(省略)"],
"rules": {
"recommended": true,
"style": {
"noVar": "off"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 4
}
}
めちゃくちゃシンプル
最後に
IDEのプラグインが充実しているため、他のツールからの移行もスムーズです。また、高速なのでCIのビルド時間も短縮できました。またコミュニティが非常に活発で、Discordでは活発な議論が行われています。
Biome は、Vue とか Svelte とかのフレームワークにも対応しつつあり、css や html 等のファイルも意欲的に対応しようとしているので今後にとても期待しています。
(参照:https://biomejs.dev/internals/language-support/)
参考文献
- https://blog.google/intl/ja-jp/company-news/technology/chrome-cookie/
- https://privacysandbox.com/intl/ja_jp/open-web/
- https://wicg.github.io/turtledove/#script-runners
- https://biomejs.dev/guides/getting-started/
- https://biomejs.dev/blog/biome-wins-prettier-challenge/
- https://biomejs.dev/ja/formatter/differences-with-prettier/
- https://speakerdeck.com/unvalley/behind-biome
- https://learn.microsoft.com/en-us/archive/blogs/ericlippert/persistence-facades-and-roslyns-red-green-trees
- https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/syntax.md
- https://speakerdeck.com/nissydev/deep-dive-into-biome-in-jsconf-2023?slide=17
- https://gist.github.com/jkrems/b14894e0b8efde10aa10a28c652d3541