marp | theme |
---|---|
true |
gaia |
https://speakerdeck.com/mizchi/server-side-javascript-notamenobandoruzui-shi-hua のスライドのMarp のソースコード。 PDF だとリンククリックできなくて不便だったので
@mizchi / Workers Tech Talks #1 2023/07/19
- @mizchi
- Frontend Ops / Frontend Performance
- Node.js + TypeScript
- 最近は TypeScript の型解析器を使う Minify を作ってる
- bundle
- ESM/CommonJS で構成されるコードを単体ファイル+補助チャンクに結合する処理
- webpack, rollup, esbuild --bundle (vite), swcpack 等
- minify
- 意味を変えずに短いコードに変形する処理
- terser, esbuild --minify, swc minify 等
- メリット
- 起動の高速化: スピンアップ/オートスケール高速化
- CI: マルチステージビルドで node_modules を減らす
- セキュリティ: RSC / Remix Action のような Isomorophic 環境で秘匿トークンが漏れ出ないようにする(嬉しいというかマスト)
- デメリット
- SourceMap でツールチェイン複雑化
- 実行時相対パスに依存するせいでバンドル未対応のライブラリがある(NestJS等)
- 使っているライブラリのサイズ >>> 自分で書いたコード量
- JSパフォーマンス ≒ ビルドサイズ
- スクリプト評価時間(CPUブロッキング)もビルドサイズに比例
- Deno Blog CommonJS is hurting JavaScript
- 要約: CommonJS はすべてが動的で、静的解析が難しい
- Bun Blog CommonJS is not going away
- 要約: ESM ではimport/export 両方に静的解析が必要なので初期ロードが遅い (bun では
@babel/core
で 2.4x 遅い)
- 要約: ESM ではimport/export 両方に静的解析が必要なので初期ロードが遅い (bun では
- => JS の bundle は一種の AOT Compile
要約:V8:Isolate で 128MBのメモリを割り当ててスクリプトを実行 https://developers.cloudflare.com/workers/learning/how-workers-works/
- wrangler が
esbulid --bundle
相当の処理- 意識しにくいだけで 必ず bundle されている
--no-bundle
はビルド済みの時にesbuildをスキップする用
- スクリプトサイズの上限
- Free Plan: 1MB
- Bundle Plan: 10MB ($5/m)
gzip 後に 1MB を超えていると警告
$ pnpm wrangler deploy --dry-run --outdir dist
--dry-run: exiting now.
Total Upload: 8030.32 KiB / gzip: 1296.12 KiB
▲ [WARNING] We recommend keeping your script less than 1MiB (1024 KiB) after gzip.
Exceeding past this can affect cold start time
--minify
$ pnpm wrangler deploy --dry-run --outdir dist --minify
--dry-run: exiting now.
Total Upload: 2932.68 KiB / gzip: 844.72 KiB
- https://zenn.dev/mizchi/scraps/adc4938e203451
- 0.13kb と 1.2MB で同等のワーカーを作って比較(ほぼ dead code)
- 結果
- 0.13kb: だいたい
710~730req/s
- 1.2MB: デプロイ直後に
473req/s
. 2回目以降670~690req/s
- 0.13kb: だいたい
- 考察
- ビルドサイズによって、リリース直後やオートスケール時に低速化していそう
※ロングランではない雑なベンチです
- node.js のメトリクスの計測、ベンチマークの改善、Docker イメージの絞り方を勉強した - mizdev (3年前)
- よくある Node.js+Express+React をチューニング
- ベースイメージを node から alpine+apk: 1.4GB => 108MB
- webpackでビルドして npm(-install) ごと消す: 108MB => 39MB
- https://github.com/mizchi/nodejs-benchmark-20230716
- express を hono (
@hono/node-server
) で置き換えて 685K => 99K - バンドル前後で HTTP listen するまでの初期化時間の比較
- no-bundle(
node lib/index.cjs
): 25ms - bundled(
node dist/index.js
): 2.4ms
- no-bundle(
- ついでに Docker イメージも修正してみたが...
- 最近のプラクティスに従って
alpine
からgcr.io/distroless/node
にしたら39MB
=>160MB
に増えた
- 最近のプラクティスに従って
- Docker イメージサイズ視点
- イメージサイズの前では JS バンドルサイズは誤差
- node_modules は制御しないとイメージサイズに響く
- ちゃんと (dev)dependencies 書き分けてますか?
- CF-Workers視点
- ランタイムは固定(v8)
- バンドルサイズこそがチューニング対象(フロントエンドと同じ)
- 共通:
- バンドルすることで初期化が(今回の例では) 10 倍高速化
- https://github.com/mizchi/remix-d1-bullets
- @remix-run/cloudflare-pages: 49.9k
- remix-auth: 3.7k
- remix-validated-form: 46.6k
- zod: 57k
- dirzzle-orm: 24.2k
- remix: ?
- radix-ui: ?
- panda-css: ? (10k~)
$ pnpm install
$ pnpm build:prod
# worker のビルドサイズ
$ la functions/
total 9416
621K [[path]].js
4.0M [[path]].js.map
# node_modules 以下の合計
$ du -hs node_modules/
690M node_modules/
$ npm create svelte@latest svelte-cf-worker
# SvelteKit demo app を選択
$ npm i -D @sveltejs/adapter-cloudflare
# ...svelte.config.js で adapter-cloudflare を使うように編集
$ npm run build
...
$ la .svelte-kit/cloudflare/_worker.js
337K .svelte-kit/cloudflare/_worker.js
https://kit.svelte.jp/docs/adapter-cloudflare
Rust で動かす CF-Workers
$ npx wrangler generate hello-world-rust \
https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust
$ npx wrangler deploy --dry-run --minify
$ la build/worker/
343K index.wasm
12K shim.mjs
- これはほぼ最小の例で、例えば regex crate 入れると +700k https://zenn.dev/mizchi/scraps/413cd989324fc7
https://addyosmani.com/blog/performance-budgets/
A performance budget is a limit for pages which the team is not allowed to exceed. It could be a max JavaScript bundle size, total image weight, a specific load time (e.g Time-to-Interactive in under 5s on 3G/4G) or threshold on any number of other metrics.
パフォーマンス予算とは、チームが超過することを許されないページの制限のことです。JavaScriptの最大バンドルサイズ、画像の総重量、特定のロード時間(例:3G/4GでTime-to-Interactiveが5秒以下)、または他の指標のしきい値などです。 (Translated by DeepL)
- 10MB は 普通のNode.jsフルスタックサーバーを作る感覚だと超過しうる
- CF−Workers 用のライブラリ選定は(戦術の通り Docker で霞むので)バンドルサイズを考慮してないことが多く罠が多い
- 例:
@prisma/engine
35MB (ほぼ Rust バイナリ)
- 例:
- CDN Edge で動かすパフォーマンスメリットのためにも やっぱり 1MB をパフォーマンスバジェットとして設定したい
core-js
: 229.2kB@js-temporal/polyfill
: 226.1kB@chakra-ui/react
: 711kBelement-plus
: 1.3MBtypescript
: 2.8MB@prisma/engine
: JS 1.5M + Binary 33M (Darwin)
※ https://bundlephobia.com の minify(not gzip)
コンポーネントライブラリが treeshake 効かないことが多い...