Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

esbuild 最適化芸人

esbuild 最適化芸人

TATSUNO Yasuhiro

May 23, 2024
Tweet

More Decks by TATSUNO Yasuhiro

Other Decks in Programming

Transcript

  1. bundler を組み込んだ身近な開発ツールの例 - 超主流・エコシステム充実 Webpack, 2012 - Next.js ← 今日の他の

    LT でも - Nuxt.js - Gatsby - 今回紹介 esbuild, 2018 - AWS Serverless Application Model - Vite の dev モード - 人気出てきた新顔 Vite, 2020 - Netlify - Remix - Vitest - VitePress 直接知らなくても きっと間接的に使ってる
  2. bundler がしてくれる主なこと - transpile - TypeScript を JavaScript に変換 -

    ES202x の新機能をサポートしてない環境(IE11、Safari など)でも動くコードに変換 - bundle - アプリが使うすべてのファイルを基本、1 つの巨大 JS ファイルにまとめる - optimize - 高速化:定数のインライン展開、など - 軽量化:tree shaking、デバッグコード削除、変数名短縮、空白文字全部消すなど - split - 1つの巨大 JS ファイルでなく、画面別など複数ファイルに分割し、必要に応じてロード 大量のファイルを読み込んで色々やるので 遅い……と思われてきた
  3. ぼくが esbuild を使っているところ - マーケテック製品のバックエンドを          AWS Lambda Node.js ランタイムで開発

    - TypeScript はもちろん動かないので、esbuild の利用は AWS 公式も推奨: esbuild による Node.js Lambda関数の 構築 - AWS Serverless Application Model
  4. 今回の最適化 - モチベーション - Lambda のコールドスタート(VMが起動してリクエストを受 け入れられるようになるまで)を、インフラコストかけずに 速くしたい! すばやくスケールアウトできるように - 大幅改修(TypeScript

    ⇨Rust や Go など)は今は避けたい - どうやって - esbuild の設定を調整してバンドルサイズを小さくし、コールドス タート時間の変化を見る - コールドスタートは https://github.com/exoego/cold-stat で計測
  5. 基準となる構成 - esbuild の全機能が使えるライブラリ版を使用(CLI はお手軽だが機能制限) - バンドルサイズ 14.5 MB、p99 コールドスタート

    3487 ms と大きめ const r = await esbuild.build({ entryPoints: [`./src/lambda.ts` ], outfile: `dist/index.mjs` , metafile: true, sourcemap: 'inline', platform: 'node', target: 'node20.9', bundle: true, }) writeFileSync (`meta.json`, JSON.stringify(r.metafile)) { "type": "module", "scripts": { "build": "node esbuild.mjs" … }, "devDependencies" : { "esbuild": "^0.20.0"… }, "dependencies" : { "@aws-sdk/client-s3" : "^3.565.0"… } } esbuild.mjs package.json
  6. 主な設定変更とその効果 esbuild 設定 基準 ① ② ③ ④ ⑤ sourceMap

    全体 node_modules を除外 sourcesContent true false mainfields デフォルト [`main`, `module`] module優先 [`module`, `main`] minify false true external デフォルト(除外なし) aws sdk 未使用機能 を除外 バンドルサイズ 14.5 MB 8.0 MB 5.8 MB 4.8 MB 3.2 MB 3.0 MB コールドスタート min 2732 ms 2499 ms 2355 ms 2179 ms 2138 ms 2086 ms p50 2921 ms 2687 ms 2543 ms 2424 ms 2333 ms 2278 ms p75 3238 ms 2878 ms 2706 ms 2638 ms 2495 ms 2463 ms p99 3487 ms 3053 ms 2890 ms 2852 ms 2700 ms 2681 ms max 3528 ms 3104 ms 2998 ms 2951 ms 2760 ms 2721 ms 今回紹介する5つの設定により p99 で 806 ms 短縮できた
  7. 主な設定変更とその効果 esbuild 設定 基準 ① ② ③ ④ ⑤ sourceMap

    全体 node_modules を除外 sourcesContent true false mainfields デフォルト [`main`, `module`] module優先 [`module`, `main`] minify false true external デフォルト(除外なし) aws sdk 未使用機能 を除外 バンドルサイズ 14.5 MB 8.0 MB 5.8 MB 4.8 MB 3.2 MB 3.0 MB コールドスタート min 2732 ms 2499 ms 2355 ms 2179 ms 2138 ms 2086 ms p50 2921 ms 2687 ms 2543 ms 2424 ms 2333 ms 2278 ms p75 3238 ms 2878 ms 2706 ms 2638 ms 2495 ms 2463 ms p99 3487 ms 3053 ms 2890 ms 2852 ms 2700 ms 2681 ms max 3528 ms 3104 ms 2998 ms 2951 ms 2760 ms 2721 ms SourceMap は遅いので削れ! ESM 版ライブラリを使え! 不要ファイルをバンドルから追い出せ! minify も効果あり! 個別具体の紹介はスライドの補足資料に
  8. まとめ - esbuild は TypeScript にも対応した超高速な bundler。 AWS Lambda や

    GitHub Action 向けの JS を生成するのに オススメ - esbuild の設定を詰めて、Node.js アプリの起動を速くする 手法を紹介しました。他の bundler でも参考になれば - バンドルサイズの変化を追える GitHub Action esbuild-bundle-analyzer を作りました
  9. 主な設定変更とその効果 esbuild 設定 基準 ① 2 3 4 5 sourceMap

    全体 node_modules を除外 sourcesContent true false mainfields デフォルト [`main`, `module`] module優先 [`module`, `main`] minify false true external デフォルト(除外なし) aws sdk 未使用機能 を除外 バンドルサイズ 14.5 MB 8.0 MB 5.8 MB 4.8 MB 3.2 MB 3.0 MB コールドスタート min 2732 ms 2499 ms 2355 ms 2179 ms 2138 ms 2086 ms p50 2921 ms 2687 ms 2543 ms 2424 ms 2333 ms 2278 ms p75 3238 ms 2878 ms 2706 ms 2638 ms 2495 ms 2463 ms p99 3487 ms 3053 ms 2890 ms 2852 ms 2700 ms 2681 ms max 3528 ms 3104 ms 2998 ms 2951 ms 2760 ms 2721 ms SourceMap は、元ソースと対応したス タックトレースを出したりしてくれる デバッグのための仕組み Node.js は SourceMap 有効にすると起 動が遅くなる リリースして間もないので問題調査用 に SourceMap は欲しいが、 node_modules までは過剰なので除外
  10. 前ページの続き:SourceMap から node_modules を除外 つまり自分のコードだけを SourceMap に含める const r =

    await esbuild.build({ entryPoints: [`./src/lambda.ts`], outfile: `dist/index.mjs`, metafile: true, sourcemap: 'inline', + plugins: [excludeNodeModulesFromSourceMap],   platform: 'node', target: 'node20.9', bundle: true, }) プラグインのソースは https://github.com/evanw/esbuild/issues/1685#issuecomment-944928069
  11. 主な設定変更とその効果 esbuild 設定 基準 ① ② ③ ④ ⑤ sourceMap

    全体 node_modules を除外 sourcesContent true false mainfields デフォルト [`main`, `module`] module優先 [`module`, `main`] minify false true external デフォルト(除外なし) aws sdk 未使用機能 を除外 バンドルサイズ 14.5 MB 8.0 MB 5.8 MB 4.8 MB 3.2 MB 3.0 MB コールドスタート min 2732 ms 2499 ms 2355 ms 2179 ms 2138 ms 2086 ms p50 2921 ms 2687 ms 2543 ms 2424 ms 2333 ms 2278 ms p75 3238 ms 2878 ms 2706 ms 2638 ms 2495 ms 2463 ms p99 3487 ms 3053 ms 2890 ms 2852 ms 2700 ms 2681 ms max 3528 ms 3104 ms 2998 ms 2951 ms 2760 ms 2721 ms デフォルトのSourceMap は行 番号などの位置情報に加え 「ソースコードそのもの」を 含んでいる。デバッガーで使 われる AWS Lambda のような FaaS ではリモートデバッガーは使 えないので不要 metafile: true, sourcemap: 'inline', + sourcesContent: false,
  12. 主な設定変更とその効果 esbuild 設定 基準 ① ② ③ ④ ⑤ sourceMap

    全体 node_modules を除外 sourcesContent true false mainfields デフォルト [`main`, `module`] module優先 [`module`, `main`] minify false true external デフォルト(除外なし) aws sdk 未使用機能 を除外 バンドルサイズ 14.5 MB 8.0 MB 5.8 MB 4.8 MB 3.2 MB 3.0 MB コールドスタート min 2732 ms 2499 ms 2355 ms 2179 ms 2138 ms 2086 ms p50 2921 ms 2687 ms 2543 ms 2424 ms 2333 ms 2278 ms p75 3238 ms 2878 ms 2706 ms 2638 ms 2495 ms 2463 ms p99 3487 ms 3053 ms 2890 ms 2852 ms 2700 ms 2681 ms max 3528 ms 3104 ms 2998 ms 2951 ms 2760 ms 2721 ms metafile: true, + mainFields: ['module', 'main'], sourcemap: 'inline', ESM(ES Module)版ライブラリを 使うと、不要なコードを削除し て、起動を短縮できる ライブラリの package.json で “main” が CommonJS 版のファイ ル、”module” が ESM 版を表す 歴史的事情から、esbuild はデフォ では main を優先する。そこで module(ESM)を優先する
  13. 主な設定変更とその効果 esbuild 設定 基準 ① ② ③ ④ ⑤ sourceMap

    全体 node_modules を除外 sourcesContent true false mainfields デフォルト [`main`, `module`] module優先 [`module`, `main`] minify false true external デフォルト(除外なし) aws sdk 未使用機能 を除外 バンドルサイズ 14.5 MB 8.0 MB 5.8 MB 4.8 MB 3.2 MB 3.0 MB コールドスタート min 2732 ms 2499 ms 2355 ms 2179 ms 2138 ms 2086 ms p50 2921 ms 2687 ms 2543 ms 2424 ms 2333 ms 2278 ms p75 3238 ms 2878 ms 2706 ms 2638 ms 2495 ms 2463 ms p99 3487 ms 3053 ms 2890 ms 2852 ms 2700 ms 2681 ms max 3528 ms 3104 ms 2998 ms 2951 ms 2760 ms 2721 ms metafile: true, + minify: true, minify は識別子短縮、短い構文への書換な どでファイルを軽量化する設定。DL 速度が 重要なモバイルで効果大 Lambda 起動の高速化にも効果が見られた ドキュメントによれば「性能向上につながる 手法の多くが未実装」今後にさらに期待 https://esbuild.github.io/api/#minify
  14. 主な設定変更とその効果 esbuild 設定 基準 ① ② ③ ④ ⑤ sourceMap

    全体 node_modules を除外 sourcesContent true false mainfields デフォルト [`main`, `module`] module優先 [`module`, `main`] minify false true external デフォルト(除外なし) aws sdk 未使用機能 を除外 バンドルサイズ 14.5 MB 8.0 MB 5.8 MB 4.8 MB 3.2 MB 3.0 MB コールドスタート min 2732 ms 2499 ms 2355 ms 2179 ms 2138 ms 2086 ms p50 2921 ms 2687 ms 2543 ms 2424 ms 2333 ms 2278 ms p75 3238 ms 2878 ms 2706 ms 2638 ms 2495 ms 2463 ms p99 3487 ms 3053 ms 2890 ms 2852 ms 2700 ms 2681 ms max 3528 ms 3104 ms 2998 ms 2951 ms 2760 ms 2721 ms AWS SDK の ESM 版を使っても、tree shaking で削除しきれない未使用機能がバン ドルに入ってしまう そこで「使っていない機能はバンドル外から 読み込みますよ」と宣言する(実際には使わ ないので読み込まれない)ことで、未使用機 能を追い出す。一定の改善が見られる
  15. Lambda 関数の環境変数に埋め込まれた トークンを SDK に渡して認証認可するので credential provider の仕組みは不要 bundle: true,

    + external: [ + '@aws-sdk/credential-provider-node', + '@smithy/credential-provider-imds', + ], 前ページの続き: 該当ファイルをバンドル外部 (external)と宣言することで、 未使用機能を追い出す