Contents
1. ビルド段階でできるサイズ削減
1‑1. コード分割と動的 import()
- ポイント
- 初回ロードに必要なコードだけをバンドルし、残りはユーザー操作時に取得する。
- Vite・Webpack が自動でチャンク化し、
[hash].jsとしてキャッシュできる。
|
1 2 3 4 5 6 7 8 9 |
// src/main.ts import { initHome } from './pages/home'; // ダッシュボードは必要になったときだけ読み込む document.getElementById('dashboardBtn')?.addEventListener('click', async () => { const { initDashboard } = await import('./pages/dashboard'); initDashboard(); }); |
1‑2. ツリーシェイキング
package.jsonの"sideEffects": falseと Webpack/Rollup のusedExports:trueを組み合わせると、未使用の ES モジュールが自動除去されます。- 注意点:副作用のあるモジュール(例: グローバル CSS)は
sideEffects: ["*.css"]のように明示的に残す必要があります。
|
1 2 3 4 5 6 7 8 |
// webpack.config.js module.exports = { optimization: { usedExports: true, sideEffects: false, }, }; |
1‑3. JavaScript 圧縮:esbuild vs terser
| 項目 | esbuild (v0.19) | terser (5.x) |
|---|---|---|
| ビルド速度 | 10 ~ 30 倍速(Go 実装) | 1 ~ 2 倍速(JS 実装) |
| 圧縮率* | 平均 5‑10 % の削減 | 平均 10‑15 % の削減 |
| ESNext 対応 | ES2024 までフルサポート | ECMAScript 2021 がベース、プラグインで拡張 |
| 設定の簡易性 | minify: "esbuild" だけで完了 |
terserOptions の詳細設定が必要 |
*※ベンチマークは同一コードベース(React + TypeScript)を Vite でビルドした結果。出典は Vite公式ベンチマーク 。
選択指針
- 開発サイクルが短いプロジェクト → esbuild(高速フィードバック)
- 本番ビルドで最大サイズ削減を狙う場合 → terser を併用(
minify: "esbuild"での事前圧縮後に terser で最終調整)
|
1 2 3 4 5 6 7 8 9 10 |
// vite.config.ts import { defineConfig } from 'vite'; export default defineConfig({ build: { minify: 'esbuild', // 開発時は esbuild target: 'es2024', }, }); |
1‑4. CSS 圧縮とベンダープレフィックス除去
|
1 2 3 4 5 6 7 |
// package.json scripts { "scripts": { "build:css": "postcss src/styles.css -o dist/styles.min.css --env production" } } |
postcss.config.js
|
1 2 3 4 5 6 7 |
module.exports = { plugins: [ require('autoprefixer'), // ベンダープレフィックス自動付与 require('cssnano')({ preset: 'default' }) // 圧縮 ], }; |
参考: CSSnano – GitHub
2. ランタイムでの高速化テクニック
2‑1. IntersectionObserver による遅延ロード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// lazyLoad.js export function observeLazy(selector, loadCallback) { const targets = document.querySelectorAll(selector); const observer = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (entry.isIntersecting) { loadCallback(entry.target); obs.unobserve(entry.target); } }); }); targets.forEach(el => observer.observe(el)); } |
使用例(画像):
|
1 2 3 4 5 6 |
import { observeLazy } from './lazyLoad'; observeLazy('img[data-src]', img => { img.src = img.dataset.src; }); |
フォールバック: loading="lazy" が未対応ブラウザ向けに src を直接設定するシンプルな代替手段を用意しておくと安全です。
2‑2. V8 のインラインキャッシュ(IC)活用
- 同一形状のオブジェクトをまとめて生成すると、V8 はプロパティアクセスをキャッシュしループ処理が 3 ~ 5 倍 高速になる。
- 逆に実行中にプロパティを追加・削除するとデオプティマイズが走り、速度が低下する。
|
1 2 3 4 5 6 7 8 9 10 |
// 推奨:形状を揃えてからループ const rows = [ { id: 1, name: 'A' }, { id: 2, name: 'B' } ]; for (let i = 0; i < rows.length; ++i) { console.log(rows[i].id); // IC が有効 } |
2‑3. デオプティマイズ回避の実装指針
| 項目 | 回避策 |
|---|---|
| 型変換の頻発 | 入力データは Number() / String() で統一し、関数内部で typeof をチェックしない |
| 大量例外処理 | エラーハンドリングは別ユーティリティに切り出し、本体ロジックから除外 |
| 動的プロパティ追加 | 必要なキーはオブジェクト生成時にすべて列挙する(Object.assign({}, base, extra) で一括) |
2‑4. V8 プロファイラの基本フロー
- Chrome DevTools → Performance タブで「Record」開始
- 対象操作を実行し、記録停止
- 「Bottom‑Up」ビューで
functionName (ms)が長い項目をクリック → 「Show JavaScript profile」でコールスタックを見る
Tips: 50 ms を超えるタスクは「Long Task」として Chrome のコンソールに警告が出るので、即座に対象関数の最適化候補になる。
3. ブラウザ API とオンデマンド描画
3‑1. Performance API & Web Vitals 計測例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// webVitals.js (Google の web-vitals ライブラリ使用) import { getCLS, getFID, getLCP } from 'web-vitals'; function sendToAnalytics(metric) { fetch('/analytics', { method: 'POST', body: JSON.stringify(metric), keepalive: true, }); } getCLS(sendToAnalytics); getFID(sendToAnalytics); getLCP(sendToAnalytics); |
| 指標 | 推奨上限 (Google) |
|---|---|
| LCP | < 2.5 s |
| CLS | < 0.1 |
| FID | < 100 ms |
3‑2. ESM の動的インポートと modulepreload
|
1 2 3 |
<!-- 重要度が低いモジュールは事前取得 --> <link rel="modulepreload" href="/src/heavy-module.js"> |
- 静的 import → 初回ロード必須、ツリーシェイキング対象
- 動的 import() + preload → ユーザー操作直前にキャッシュが確保でき、体感遅延を最小化
4. フレームワーク別ビルド設定例
| フレームワーク | ビルドツール | 主な最適化 |
|---|---|---|
| React | Vite (esbuild) | optimizeDeps.exclude で不要依存除外、manualChunks に UI ライブラリ分割設定 |
| Vue 3 | Vite + @vitejs/plugin-vue | <script setup> の自動コード分割、defineConfig({ build: { chunkSizeWarningLimit: 500 } }) |
| Svelte | Rollup | output.manualChunks で大規模コンポーネント切り出し、svelte-preprocess に esbuild ミニファイ統合 |
React(Vite)設定例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], build: { rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { return id.split('node_modules/')[1].split('/')[0]; } }, }, }, }, }); |
Vue 3(Vite)設定例
|
1 2 3 4 5 6 7 8 9 10 11 |
// vite.config.ts import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], build: { chunkSizeWarningLimit: 500, // 大きなチャンクを警告で検知 }, }); |
5. パフォーマンス測定ツール活用フロー
5‑1. Lighthouse(Chrome DevTools 内蔵)
- Audits → 「Performance」だけ実行
- スコアが 90 未満 の項目は Opportunities に注目し、
Reduce unused JavaScriptやServe static assets with an efficient cache policyを順次適用
5‑2. Chrome DevTools – Performance タブ
- 記録開始 → ページ遷移・操作 → 停止
- Main(JS 実行)と Rendering の時間比率を確認し、50 ms 超のタスクは「Long Task」レポートで特定
5‑3. WebPageTest
- https://webpagetest.org/ に URL を入力
- 「Chrome (Emulated Mobile)」でテスト実行
- Waterfall と Filmstrip で遅延ロードが期待通りに機能しているかを視覚的にチェック
5‑4. Perfetto(Android デバイス向け)
- Chrome の「chrome://tracing」からトレース取得 →
perfettoにエクスポート - CPU スケジューラや GC 発生頻度を詳細分析し、モバイル端末特有のボトルネックを把握
6. 実装チェックリスト(まとめ)
| カテゴリ | チェック項目 |
|---|---|
| ビルド | - コード分割 & 動的 import が適切か - sideEffects:false とツリーシェイキングが有効か - 圧縮は esbuild → terser の二段階で行う |
| CSS | - PostCSS に CSSnano + autoprefixer を組み込んだか |
| ランタイム | - IntersectionObserver で画像・コンポーネントを遅延ロード - オブジェクト形状は統一し IC が効くように設計 - デオプティマイズ要因(型変換・例外)を排除 |
| V8 プロファイル | - Performance タブで長タスクを特定し、該当関数を最適化 |
| 測定 | - Lighthouse でスコア ≥ 90 を目指す - WebPageTest の Waterfall で遅延ロードが期待通りか確認 - 必要なら Perfetto でモバイル解析 |
次のステップ
- CI に Lint + Lighthouse(
npm run lighthouse-ci)を組み込み、プルリクエストごとにスコアを自動取得。 - 本番環境で Web Vitals を計測し、目標値未達の場合は上記チェックリストの該当項目を再検証。
参考リンク一覧
- MDN – Dynamic Import: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import
- Webpack – Tree Shaking: https://webpack.js.org/guides/tree-shaking/
- Vite ベンチマーク: https://vitejs.dev/guide/build.html#benchmark
- CSSnano GitHub: https://github.com/cssnano/cssnano
- V8 Blog – Inline Caches: https://v8.dev/blog/inline-caches
- Web Vitals Official Docs: https://web.dev/vitals/
- IntersectionObserver MDN: https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API
- WebPageTest Documentation: https://docs.webpagetest.org/