Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. Server Side JavaScript
    のための
    バンドル最適化
    @mizchi / Workers Tech Talks #1 2023/07/19

    View Slide

  2. 自己紹介
    @mizchi
    Frontend Ops / Frontend Performance
    Node.js + TypeScript
    最近は TypeScript
    の型解析器を使う Minify
    を作ってる

    View Slide

  3. 今日の前提知識
    bundle
    ESM/CommonJS
    で構成されるコードを単体ファイル+
    補助チャ
    ンクに結合する処理
    webpack, rollup, esbuild --bundle (vite), swcpack

    minify
    意味を変えずに短いコードに変形する処理
    terser, esbuild --minify, swc minify

    View Slide

  4. 今日の前提破壊

    View Slide

  5. サーバーサイドのバンドル処理について考える

    View Slide

  6. サーバーサイドでバンドルするメリット
    メリット
    起動の高速化
    :
    スピンアップ/
    オートスケール高速化
    CI:
    マルチステージビルドで node_modules
    を減らす
    セキュリティ
    : RSC / Remix Action
    のような Isomorophic
    環境で
    秘匿トークンが漏れ出ないようにする(
    嬉しいというかマスト)
    デメリット
    SourceMap
    でツールチェイン複雑化
    実行時相対パスに依存するせいでバンドル未対応のライブラリが
    ある(NestJS
    等)

    View Slide

  7. JS
    最適化の世界観
    (
    フロントエンドの経験から
    )
    使っているライブラリのサイズ
    >>>
    自分で書いたコード量
    JS
    パフォーマンス ≒ ビルドサイズ
    スクリプト評価時間(CPU
    ブロッキング)
    もビルドサイズに比例

    View Slide

  8. 余談
    : 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

    View Slide

  9. CF-Workers
    におけるバンドル処理

    View Slide

  10. CF-Workers
    の実行モデルのおさらい
    要約:V8:Isolate
    で 128MB
    のメモリを割り当ててスクリプトを実行
    https://developers.cloudflare.com/workers/learning/how-workers-
    works/

    View Slide

  11. CF-Workers
    のスクリプトサイズ制限
    wrangler

    esbulid --bundle
    相当の処理
    意識しにくいだけで 必ず
    bundle
    されている
    --no-bundle
    はビルド済みの時にesbuild
    をスキップする用
    スクリプトサイズの上限
    Free Plan: 1MB
    Bundle Plan: 10MB ($5/m)

    View Slide

  12. 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

    View Slide

  13. ビルドサイズによる
    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
    考察
    ビルドサイズによって、リリース直後やオートスケール時に低速
    化していそう
    ※ロングランではない雑なベンチです

    View Slide

  14. Node.js

    CF-Workers
    チューニングの方向性

    View Slide

  15. Node.js
    のチューニング例
    node.js
    のメトリクスの計測、ベンチマークの改善、Docker
    イメー
    ジの絞り方を勉強した - mizdev (3
    年前)
    よくある Node.js+Express+React
    をチューニング
    ベースイメージを node
    から alpine+apk: 1.4GB => 108MB
    webpack
    でビルドして npm(-install)
    ごと消す: 108MB => 39MB

    View Slide



  16. 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
    に増えた

    View Slide

  17. Node.js
    のチューニングから学べる教訓
    Docker
    イメージサイズ視点
    イメージサイズの前では
    JS
    バンドルサイズは誤差
    node_modules
    は制御しないとイメージサイズに響く
    ちゃんと (dev)dependencies
    書き分けてますか?
    CF-Workers
    視点
    ランタイムは固定(v8)
    バンドルサイズこそがチューニング対象
    (
    フロントエンドと同じ)
    共通:
    バンドルすることで初期化が(
    今回の例では) 10
    倍高速化

    View Slide

  18. もう少し実践的な
    CF-Workers
    をみていく

    View Slide

  19. mizchi/remix-d1-bullets
    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~)

    View Slide

  20. 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/

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

  23. パフォーマンスバジェットを考えたい

    View Slide

  24. パフォーマンスバジェット
    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)


    View Slide

  25. 自分の結論
    10MB
    は 普通の
    Node.js
    フルスタックサーバーを作る感覚だと超過
    しうる
    CF−Workers
    用のライブラリ選定は(
    戦術の通り Docker
    で霞むの
    で)
    バンドルサイズを考慮してないことが多く罠が多い
    例: @prisma/engine 35MB (
    ほぼ Rust
    バイナリ)
    CDN Edge
    で動かすパフォーマンスメリットのためにも やっぱり
    1MB
    をパフォーマンスバジェットとして設定したい

    View Slide

  26. まとめ
    : Server Side JS
    のためのバンドル最適化
    ≒ フロントエンド最適化

    View Slide

  27. おまけ
    :
    罠踏みがちなライブラリの例
    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
    効かないことが多い...

    View Slide

  28. おわり

    View Slide