$30 off During Our Annual Pro Sale. View Details »

Server Side JavaScript のためのバンドル最適化

Server Side JavaScript のためのバンドル最適化

Workers Tech Talks #1

リンククリックできなくて不便だったので、別途 marp のソースコードをアップロードしました
https://gist.github.com/mizchi/78aeed1947f87eded74b20ad8d9cb8b3

Koutarou Chikuba

July 19, 2023
Tweet

More Decks by Koutarou Chikuba

Other Decks in Technology

Transcript

  1. 自己紹介 @mizchi Frontend Ops / Frontend Performance Node.js + TypeScript

    最近は TypeScript の型解析器を使う Minify を作ってる
  2. 今日の前提知識 bundle ESM/CommonJS で構成されるコードを単体ファイル+ 補助チャ ンクに結合する処理 webpack, rollup, esbuild --bundle

    (vite), swcpack 等 minify 意味を変えずに短いコードに変形する処理 terser, esbuild --minify, swc minify 等
  3. サーバーサイドでバンドルするメリット メリット 起動の高速化 : スピンアップ/ オートスケール高速化 CI: マルチステージビルドで node_modules を減らす

    セキュリティ : RSC / Remix Action のような Isomorophic 環境で 秘匿トークンが漏れ出ないようにする( 嬉しいというかマスト) デメリット SourceMap でツールチェイン複雑化 実行時相対パスに依存するせいでバンドル未対応のライブラリが ある(NestJS 等)
  4. 余談 : ESM vs CommonJS Deno Blog CommonJS is hurting

    JavaScript 要約: CommonJS はすべてが動的で、静的解析が難しい Bun Blog CommonJS is not going away 要約: ESM ではimport/export 両方に静的解析が必要なので初期 ロードが遅い (bun では @babel/core で 2.4x 遅い) => JS の bundle は一種の AOT Compile
  5. CF-Workers のスクリプトサイズ制限 wrangler が esbulid --bundle 相当の処理 意識しにくいだけで 必ず bundle

    されている --no-bundle はビルド済みの時にesbuild をスキップする用 スクリプトサイズの上限 Free Plan: 1MB Bundle Plan: 10MB ($5/m)
  6. wrangler: bundle & minify 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
  7. ビルドサイズによる CF-Workers 簡易ベンチ 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 考察 ビルドサイズによって、リリース直後やオートスケール時に低速 化していそう ※ロングランではない雑なベンチです
  8. Node.js のチューニング例 node.js のメトリクスの計測、ベンチマークの改善、Docker イメー ジの絞り方を勉強した - mizdev (3 年前)

    よくある Node.js+Express+React をチューニング ベースイメージを node から alpine+apk: 1.4GB => 108MB webpack でビルドして npm(-install) ごと消す: 108MB => 39MB
  9. ↑ を CF-Workers 視点で再チューニング 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 ついでに Docker イメージも修正してみたが... 最近のプラクティスに従って alpine から gcr.io/distroless/node にしたら 39MB => 160MB に増えた
  10. Node.js のチューニングから学べる教訓 Docker イメージサイズ視点 イメージサイズの前では JS バンドルサイズは誤差 node_modules は制御しないとイメージサイズに響く ちゃんと

    (dev)dependencies 書き分けてますか? CF-Workers 視点 ランタイムは固定(v8) バンドルサイズこそがチューニング対象 ( フロントエンドと同じ) 共通: バンドルすることで初期化が( 今回の例では) 10 倍高速化
  11. mizchi/remix-d1-bullets のビルドサイズ $ 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/
  12. svelte-kit のビルドサイズ $ 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
  13. workers-rs のビルドサイズ 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
  14. パフォーマンスバジェット 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) “ “
  15. 自分の結論 10MB は 普通の Node.js フルスタックサーバーを作る感覚だと超過 しうる CF−Workers 用のライブラリ選定は( 戦術の通り

    Docker で霞むの で) バンドルサイズを考慮してないことが多く罠が多い 例: @prisma/engine 35MB ( ほぼ Rust バイナリ) CDN Edge で動かすパフォーマンスメリットのためにも やっぱり 1MB をパフォーマンスバジェットとして設定したい
  16. おまけ : 罠踏みがちなライブラリの例 core-js : 229.2kB @js-temporal/polyfill : 226.1kB @chakra-ui/react

    : 711kB element-plus : 1.3MB typescript : 2.8MB @prisma/engine : JS 1.5M + Binary 33M (Darwin) ※ https://bundlephobia.com の minify(not gzip) コンポーネントライブラリが treeshake 効かないことが多い...