[最適化] lodash Tree Shakingでバンドルサイズを82%削減:547KBから97KBへ

2026-02-28 hit count image

モノレポ環境でlodashのimport方式を変更し、バンドルサイズを547KBから97KBへ約82% 削減した経験を共有します。バンドル分析環境の構築からESLintルールによる再発防止までの プロセスを紹介します。

web

はじめに

フロントエンドアプリケーションにおいて、バンドルサイズはユーザー体験に直接的な影響を与えます。バンドルが大きいと初期ロード時間が長くなり、特にモバイル環境ではその差がより顕著になります。この記事では、モノレポ環境でlodashのimport方式を変更し、バンドルサイズを547KBから97KBへ約82%削減した経験を共有します。

問題の発見

私たちのプロジェクトは複数のアプリを含むViteベースのモノレポ構造です。コードレビュー中にTree Shakingでバンドルサイズを削減できる可能性を発見しました。これを確認するため、まずバンドル分析環境を構築しました。

バンドル分析ツールのインストール

バンドルの可視化のためにrollup-plugin-visualizerをdevDependencyとしてインストールしました。Viteは内部的にRollupを使用しているため、Rollupプラグインを活用してバンドルの可視化を簡単に追加できます。

yarn add -D rollup-plugin-visualizer

Vite設定にVisualizerプラグインを追加

各アプリのvite.config.tsに、ANALYZE環境変数が設定されている場合のみvisualizerプラグインが有効化されるよう、条件付きで追加しました。

import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    react(),
    ...(process.env.ANALYZE
      ? [
          visualizer({
            filename: 'dist/stats.html',
            open: true,
            gzipSize: true,
            brotliSize: true,
          }),
        ]
      : []),
  ],
});

各オプションの意味は以下の通りです:

オプション説明
filename分析結果をdist/stats.htmlとして出力
openビルド完了後にブラウザで自動的に開く
gzipSizegzip圧縮後のサイズを表示
brotliSizebrotli圧縮後のサイズを表示

分析用ビルドスクリプトの追加

各アプリのpackage.jsonに分析用ビルドコマンドを追加しました。

{
  "scripts": {
    "build:analyze": "ANALYZE=true yarn build"
  }
}

yarn build:analyzeを実行すると通常のプロダクションビルドが行われた後、dist/stats.htmlが生成され、ブラウザでtreemap形式のバンドル可視化結果が自動的に開きます。これにより、どのライブラリがバンドルでどれだけ占めているかが一目で分かります。

分析結果

分析の結果、lodashライブラリが各アプリのバンドルで大きな割合を占めていることが判明しました。

原因は単純でした。コードベース全体でlodashを以下のようにNamed Import方式で使用していたのです。

import { cloneDeep } from 'lodash';
import { isEqual } from 'lodash';

この方式では、実際に使用している関数がcloneDeep1つだけであっても、lodashライブラリ全体(約547KB)がバンドルに含まれてしまう問題を引き起こします。

Tree Shakingとは?

Tree Shakingは、バンドラー(Webpack、Vite/Rollupなど)がビルド時に使用されていないコードを除去する最適化手法です。木を揺すると枯れ葉が落ちるように、使われていないコードを「揺すって」落とすという概念です。

しかし、lodashのメインパッケージ(lodash)はCommonJSモジュール形式で書かれているため、ES Modulesベースの Tree Shakingが正しく動作しません。そのため、Named Importを使用してもバンドラーはライブラリ全体を含めるしかありません。

解決方法

1. Importパスの変更

解決方法は驚くほど単純です。lodashの個別関数モジュールから直接importするようパスを変更するだけです。

- import { cloneDeep } from 'lodash'
+ import cloneDeep from 'lodash/cloneDeep'

- import { isEqual } from 'lodash'
+ import isEqual from 'lodash/isEqual'

lodash/cloneDeepのように個別パスでimportすると、バンドラーはその関数のコードのみを含めます。ライブラリ全体をロードする必要がなくなるわけです。

2. 適用規模

この変更は決して1〜2ファイルの修正ではありませんでした。モノレポ全体にわたって合計1,012ファイルを修正する必要がありました。

アプリ/パッケージ修正ファイル数
アプリA394
アプリB300
アプリC182
アプリD114
アプリE9
コード生成ツール5
共有パッケージ8

3. ESLintルールによる再発防止

問題を修正することと同じくらい重要なのが、同じ問題が再発しないように防止することです。ESLintのno-restricted-importsルールを共有設定に追加し、lodashから直接importするコードが追加されるとエラーが発生するようにしました。

// ESLint共有設定
'no-restricted-imports': [
  'error',
  {
    paths: [
      {
        name: 'lodash',
        message:
          "Tree Shakingのため、import { fn } from 'lodash' ではなく import fn from 'lodash/fn' 形式を使用してください。",
      },
    ],
  },
],

このルールのおかげで、開発者が誤ってimport { cloneDeep } from 'lodash'形式のコードを書くと、リント段階で即座に警告を受けます。

ESLintのimport関連ルールの詳細については、eslint-plugin-importも併せてご参照ください。また、モノレポ環境でのESLint設定については、VSCodeでMonorepoのためのESLint設定で解説しています。

4. コード生成ツールの更新

プロジェクトで使用しているコード自動生成ツール(scaffolding)のテンプレートも合わせて修正しました。新しいページや機能を生成する際にも最初から正しいimport方式が適用されるようにし、開発ワークフロー全体で一貫性を確保しました。

結果

バンドルサイズの変化

lodashがバンドルで占めるサイズが劇的に減少しました。

547KB → 97KB(約82%削減)

モノレポ内の全アプリで同等レベルの改善が確認されました。

期待される効果

  • 初期ロード速度の向上:約450KBのJavaScript削減はパースおよび実行時間の短縮につながります。
  • ネットワークコストの削減:gzip圧縮後でも有意な転送量の削減が期待されます。
  • モバイルユーザー体験の改善:制限されたネットワーク環境で特に効果的です。

教訓と示唆

1. 小さな変更、大きな効果

importパス1行の変更でバンドルサイズを82%も削減できるという点は驚きです。フロントエンド最適化における「低く実った果実(low-hanging fruit)」の代表的な事例と言えます。

2. バンドル分析は必須

問題を認識するにはまず測定が必要です。rollup-plugin-visualizerのようなツールを活用した定期的なバンドル分析が最適化の出発点です。

3. ルールで強制する

コードレビューだけでは全てのケースを把握するのは困難です。ESLintルールによる自動検証を追加することで、チーム全体が特別な努力なく正しいパターンを維持できます。

4. コード生成ツールも合わせて更新する

プロジェクトでscaffoldingツールを使用している場合、最適化されたパターンがテンプレートにも反映されるべきです。そうしないと、新しいコードが生成されるたびに同じ問題が再発します。

5. 代替案も検討する

今回はimportパスの変更で解決しましたが、他のアプローチも存在します。

  • lodash-es:ES Modules形式のlodashで、Named Importを使用してもTree Shakingが可能です。
  • babel-plugin-lodash / eslint-plugin-lodash:ビルド時に自動的にimportを変換するプラグインです。
  • ネイティブ代替structuredClone()(cloneDeepの代替)、Object.is()(isEqualの部分的な代替)などブラウザ内蔵APIを活用する方法もあります。

まとめ

フロントエンドのパフォーマンス最適化は、大がかりなアーキテクチャの変更ではなく、今回のように細やかなコード管理から始まることが多いです。バンドル分析で問題を発見し、体系的に修正し、リントルールで再発を防止する。このサイクルが健全なフロントエンドコードベースを維持する鍵です。

もしあなたのプロジェクトでもlodashを使用しているなら、一度バンドル分析をしてみることをお勧めします。今すぐ数百KBを削減できるかもしれません。

この他にもフロントエンドのパフォーマンスを改善するさまざまな方法があります。画像フォーマットの最適化ウェブフォントのロード最適化Reactレンダリングパフォーマンス最適化なども併せてご覧ください。

またLighthouseを使えばパフォーマンスの測定ができます。詳細はLighthouseパフォーマンス最適化総合ガイドをご参照ください。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。



SHARE
Twitter Facebook RSS