Skip to content

Instantly share code, notes, and snippets.

@x7ddf74479jn5
Last active July 23, 2023 14:18
Show Gist options
  • Save x7ddf74479jn5/b641dc57e4188705df4722a2bc57cf7a to your computer and use it in GitHub Desktop.
Save x7ddf74479jn5/b641dc57e4188705df4722a2bc57cf7a to your computer and use it in GitHub Desktop.
バンドルサイズ計測法

バンドルサイズの計測法

モチベーション

依存パッケージのバンドルサイズを正確に計測したい。アプリケーションがある程度肥大化したあとでのチューニングより技術選定時点の参考情報にするため。実際にアプリケーションを作り始めたあとでは乗り換えることが困難であるため、正確な計測値がわかるなら事前に知っておきたいという需要に向けて。swr と@tanstack/react-query どっち使うかとか、recoil と jotai どっち使うかとか。

ツール

問題点

有名所が Bundlephobia だが、これらの解析ツールのほとんどはパッケージのインストールサイズをターゲットにしている。詳しくはpackage.jsonfilesに指定したものがインストールされるすべて。すなわち、未使用のモジュール、型定義ファイル、package.jsonREADME.mdなどを含んだ合計値を算出してしまう。たとえば、利用者への利便性に配慮しsrcを同梱した場合にもそのサイズ分膨れ上がってしまう。Dual Package 対応のライブラリでは ESM 用と CJS 用で単純に 2 倍になる。

より正確に計測するには

ビルド後のバンドルに含まれる Tree Shaking 後のパッケージをより正確に知りたいならミドルウェア系の Bundle Analyzer を使うのが確実だ。結局、実際にアプリケーションをバンドルしないとわからない。ただ我々は ESM の Tree Shaking を考慮した情報がほしいところ、mizchi さん(@mizchi)が同じ問題意識で作成した計測サイト(Shakerphobia)がある1。簡易にでも計測したいという需要は満たせそうだ。

Jotai vs. Recoil

Shakerphobia を使って実践的に計測していこう。まず、Bundlphobia と pkg-size の計測結果を見てみよう。

圧倒的に Recoil のバンドルサイズが大きいことがわかるだろう。続いて Tree Shaking 込みのバンドルサイズを見てみよう。

shakerphobia-jotai shakerphobia-recoil

Recoil のインポートする API を増やして再計測してみる。

import { atom, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";

Recoil+ | Shakerphobia shakerphobia-recoil+

Tree Shaking 後のバンドルサイズが微量しか増えていない。理由に挙げられそうなのは、內部の共通コアロジックが大きいか、ビルド形式が良くないか。後者を考えると、Recoil は単一のファイルにバンドルしている。このため、利用側のバンドラーによる解析が難しく Tree Shaking が効いてないのではないかと推測できる。バンドラーはモジュール単位の依存関係を解析し、DCE(dead code elimination)の参考情報にする。単一のファイルではそれ自体がエントリーポイントになるため、export しているものがすべて DCE の対象外になる可能性がある。(要検証)

Recoil/rollup.config.js at main · facebookexperimental/Recoil

  // ES
  {
    input: inputFile,
    output: {
      file: `es/recoil.js`,
      format: 'es',
      exports: 'named',
    },
    external: externalLibs,
    plugins: commonPlugins,
  },

Recoil/package.json at main · facebookexperimental/Recoil

{
  "name": "recoil",
  "main": "cjs/index.js",
  "module": "es/index.js",
  "react-native": "native/index.js",
  "unpkg": "umd/index.js",
  "files": ["umd", "es", "cjs", "native", "index.d.ts"]
}

Jotai の方も載せておく。最終的にはsrc/utils.tsdist/esm/utils.mjsに出力される。これはモジュール構造を維持したままビルドされることを表す。なので、Jotai はjotai/utilsのように、基本的な API 以外の API はプラガブルな形でインポートする。逆に、Recoil はオールインワン形式だ。

jotai/package.json at main · pmndrs/jotai

{
  "name": "jotai",
  "version": "2.2.2",
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "types": "./index.d.ts",
      "import": {
        "types": "./esm/index.d.mts",
        "default": "./esm/index.mjs"
      },
      "default": "./index.js"
    },
    "./*": {
      "types": "./*.d.ts",
      "import": {
        "types": "./esm/*.d.mts",
        "default": "./esm/*.mjs"
      },
      "default": "./*.js"
    }
  },
  "files": ["**"]
}

以上からわかるように、バンドルサイズが厳しい環境をターゲットにしているとか、ただReact.Contextを代替するような使い方がしたい場合なら、Jotai がシンプルに始められるので適しているといえる。Recoil の方の利点というと、複雑なことができるように API が豊富だ2。結論として、複雑なアプリケーションには Recoil を、小さなアプリケーションには Jotai を選ぶのが良さそうだ。

バンドラーフレンドリーなライブラリをどう作るか

  1. 単一ファイルにバンドルしない
  2. デフォルトエクスポートより名前付きエクスポート

単一ファイルにバンドルした場合(index.js にすべての公開するファイルをまとめる)、Tree Shaking が効かないので避ける。最小構成で使うときの単位にデフォルトエクスポートを使うのはいいと思うが、バンドラーの気持ちになると解析しやすい名前付きエクスポートが好ましい。

Footnotes

  1. ESM treeshake に対応したバンドルサイズを計算してくれる Shakerphobia を作った

  2. Getting Started | Recoil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment