ビルド最適化
SvelteKitのビルドプロセスを最適化することで、アプリケーションのパフォーマンスを大幅に改善できます。このページでは、バンドルサイズの削減、ビルド時間の短縮、デプロイの効率化について詳しく解説します。
ビルドプロセスの全体像
SvelteKitのビルドプロセスを理解することで、最適化のポイントが明確になります。
ダイアグラムを読み込み中...
Vite設定による最適化
基本的な最適化設定
Viteの設定を調整することで、ビルドの効率を大幅に改善できます。
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
build: {
// ブラウザ互換性の設定
target: 'es2020',
// チャンクサイズの警告閾値
chunkSizeWarningLimit: 1000,
// ソースマップの生成(本番環境では無効化を推奨)
sourcemap: false,
// ロールアップオプション
rollupOptions: {
output: {
// マニュアルチャンク分割
manualChunks: {
'vendor': ['svelte', '@sveltejs/kit'],
'utils': ['date-fns', 'lodash-es']
},
// アセットファイル名の設定
assetFileNames: 'assets/[name]-[hash][extname]',
// チャンクファイル名の設定
chunkFileNames: 'chunks/[name]-[hash].js',
// エントリーファイル名の設定
entryFileNames: 'entries/[name]-[hash].js'
}
},
// Terserによる圧縮設定
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info'],
passes: 2
},
mangle: {
safari10: true
},
format: {
comments: false
}
}
},
// 依存関係の最適化
optimizeDeps: {
include: ['svelte', '@sveltejs/kit'],
exclude: ['@sveltejs/kit/node']
}
});
typescript
環境別の設定
開発環境と本番環境で異なる最適化戦略を適用します。
// vite.config.ts - 環境別設定
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig(({ mode }) => {
const isDev = mode === 'development';
const isProd = mode === 'production';
return {
plugins: [sveltekit()],
build: {
// 開発環境では高速ビルド、本番環境では最適化重視
minify: isProd ? 'terser' : false,
sourcemap: isDev ? 'inline' : false,
rollupOptions: {
output: {
// 本番環境のみハッシュを付与
entryFileNames: isProd
? 'entries/[name]-[hash].js'
: 'entries/[name].js',
manualChunks: isProd ? (id) => {
// node_modulesのパッケージをvendorチャンクに
if (id.includes('node_modules')) {
// 大きなライブラリは個別チャンクに
if (id.includes('lodash')) return 'lodash';
if (id.includes('chart.js')) return 'charts';
if (id.includes('@sveltejs/kit')) return 'sveltekit';
return 'vendor';
}
} : undefined
}
}
},
// 開発サーバーの設定
server: {
hmr: {
overlay: isDev
}
}
};
});
typescript
コード分割戦略
効果的なコード分割により、初期ロード時間を大幅に短縮できます。
ダイアグラムを読み込み中...
動的インポートによる遅延ロード
必要な時にのみコードをロードすることで、初期バンドルサイズを削減します。
// +page.svelte - 動的インポートの実装
<script lang="ts">
import { onMount } from 'svelte';
let ChartComponent: any;
let isLoading = true;
onMount(async () => {
// 重いライブラリは必要時にのみロード
const module = await import('$lib/components/HeavyChart.svelte');
ChartComponent = module.default;
isLoading = false;
});
// 条件付き動的インポート
async function loadAdminPanel() {
const { AdminPanel } = await import('$lib/components/AdminPanel.svelte');
return AdminPanel;
}
</script>
{#if isLoading}
<div class="skeleton">チャートを読み込み中...</div>
{:else if ChartComponent}
<svelte:component this={ChartComponent} />
{/if}
typescript
ルートベースのコード分割
SvelteKitは自動的にルートごとにコードを分割しますが、さらに最適化できます。
// +layout.ts - ルートグループごとの設定
export const prerender = true;
export const ssr = true;
// 管理画面は別バンドルに
export async function load({ route }) {
if (route.id?.startsWith('/(admin)')) {
// 管理画面用の重いライブラリ
const { setupAdmin } = await import('$lib/admin/setup');
await setupAdmin();
}
return {};
}
typescript
Tree Shakingの最適化
使用されていないコードを除去し、バンドルサイズを削減します。
副作用フリーなコードの記述
Tree Shakingが効果的に動作するよう、副作用のないコードを書きます。
// lib/utils/index.ts - Tree Shaking対応
// ❌ 悪い例:副作用のあるコード
export const utils = {
formatDate: () => { /* ... */ },
parseJSON: () => { /* ... */ },
calculateSum: () => { /* ... */ }
};
// グローバルな副作用
window.myUtils = utils;
// ✅ 良い例:個別エクスポート
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('ja-JP').format(date);
}
export function parseJSON<T>(json: string): T | null {
try {
return JSON.parse(json);
} catch {
return null;
}
}
export function calculateSum(numbers: number[]): number {
return numbers.reduce((sum, n) => sum + n, 0);
}
typescript
package.jsonの最適化
パッケージが正しくTree Shakingされるよう設定します。
// package.json
{
"name": "my-sveltekit-app",
"type": "module",
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js",
"default": "./dist/index.js"
},
"./utils": {
"types": "./dist/utils/index.d.ts",
"import": "./dist/utils/index.js"
}
}
}
javascript
画像とアセットの最適化
画像の自動最適化
画像を自動的に最適化し、適切なフォーマットで配信します。
// vite.config.ts - 画像最適化プラグイン
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import imagemin from 'vite-plugin-imagemin';
export default defineConfig({
plugins: [
sveltekit(),
imagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 80
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
})
]
});
typescript
レスポンシブ画像の実装
適切なサイズの画像を配信することで、パフォーマンスを向上させます。
<!-- lib/components/OptimizedImage.svelte -->
<script lang="ts">
export let src: string;
export let alt: string;
export let sizes = '100vw';
// 画像URLから各サイズを生成
function generateSrcSet(src: string): string {
const widths = [320, 640, 768, 1024, 1280, 1920];
return widths
.map(w => `${src}?w=${w} ${w}w`)
.join(', ');
}
// WebP対応の判定
let supportsWebP = false;
if (typeof window !== 'undefined') {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
supportsWebP = canvas.toDataURL?.('image/webp').indexOf('image/webp') === 5;
}
</script>
<picture>
{#if supportsWebP}
<source
type="image/webp"
srcset={generateSrcSet(src.replace(/\.(jpg|png)$/, '.webp'))}
{sizes}
/>
{/if}
<source
type="image/jpeg"
srcset={generateSrcSet(src)}
{sizes}
/>
<img
{src}
{alt}
loading="lazy"
decoding="async"
/>
</picture>
svelte
バンドル分析
Bundle Analyzerの設定
バンドルの内容を可視化し、最適化の機会を特定します。
// vite.config.ts - Bundle Analyzer設定
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
sveltekit(),
visualizer({
filename: './stats.html',
open: true,
gzipSize: true,
brotliSize: true,
template: 'treemap' // or 'sunburst', 'network'
})
]
});
typescript
分析結果の活用
// バンドルサイズ監視スクリプト
// scripts/analyze-bundle.js
import { readFileSync } from 'fs';
import { join } from 'path';
const BUDGET = {
main: 50 * 1024, // 50KB
vendor: 200 * 1024, // 200KB
total: 500 * 1024 // 500KB
};
function analyzeBundleSize() {
const buildDir = '.svelte-kit/output/client';
const manifest = JSON.parse(
readFileSync(join(buildDir, '.vite/manifest.json'), 'utf-8')
);
let totalSize = 0;
const bundles = {};
for (const [key, value] of Object.entries(manifest)) {
const size = value.file ?
readFileSync(join(buildDir, value.file)).length : 0;
bundles[key] = size;
totalSize += size;
// 予算超過の警告
if (key.includes('main') && size > BUDGET.main) {
console.warn(`⚠️ Main bundle exceeds budget: ${size / 1024}KB`);
}
}
console.log('Bundle Analysis:');
console.log('================');
Object.entries(bundles)
.sort((a, b) => b[1] - a[1])
.forEach(([name, size]) => {
console.log(`${name}: ${(size / 1024).toFixed(2)}KB`);
});
console.log(`\nTotal: ${(totalSize / 1024).toFixed(2)}KB`);
if (totalSize > BUDGET.total) {
console.error(`❌ Total size exceeds budget!`);
process.exit(1);
}
}
analyzeBundleSize();
typescript
圧縮戦略
Gzip/Brotli圧縮
配信時のファイルサイズを削減します。
// vite.config.ts - 圧縮設定
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import viteCompression from 'vite-plugin-compression';
export default defineConfig({
plugins: [
sveltekit(),
// Gzip圧縮
viteCompression({
algorithm: 'gzip',
ext: '.gz'
}),
// Brotli圧縮
viteCompression({
algorithm: 'brotliCompress',
ext: '.br',
threshold: 1024,
deleteOriginFile: false
})
]
});
typescript
最適化の効果測定
最適化の効果を定量的に測定し、継続的に改善します。
ダイアグラムを読み込み中...
パフォーマンス測定スクリプト
// scripts/measure-performance.ts
import { chromium } from 'playwright';
async function measurePerformance(url: string) {
const browser = await chromium.launch();
const page = await browser.newPage();
// パフォーマンス測定を有効化
await page.coverage.startJSCoverage();
await page.coverage.startCSSCoverage();
const metrics = await page.evaluate(() => {
return JSON.stringify(performance.getEntriesByType('navigation')[0]);
});
// Core Web Vitals取得
const vitals = await page.evaluate(() => {
return new Promise((resolve) => {
new PerformanceObserver((list) => {
const entries = list.getEntries();
resolve({
LCP: entries.find(e => e.name === 'largest-contentful-paint')?.startTime,
FID: entries.find(e => e.name === 'first-input')?.processingStart,
CLS: entries.reduce((acc, e) => {
if (e.name === 'layout-shift' && !e.hadRecentInput) {
return acc + e.value;
}
return acc;
}, 0)
});
}).observe({ entryTypes: ['paint', 'layout-shift', 'first-input'] });
});
});
// カバレッジレポート
const jsCoverage = await page.coverage.stopJSCoverage();
const cssCoverage = await page.coverage.stopCSSCoverage();
const totalBytes = [...jsCoverage, ...cssCoverage].reduce(
(acc, entry) => acc + entry.text.length, 0
);
const usedBytes = [...jsCoverage, ...cssCoverage].reduce(
(acc, entry) => acc + entry.ranges.reduce(
(acc2, range) => acc2 + range.end - range.start, 0
), 0
);
console.log('Performance Metrics:');
console.log('===================');
console.log(`Navigation: ${JSON.parse(metrics)}`);
console.log(`Core Web Vitals: ${JSON.stringify(vitals)}`);
console.log(`Code Coverage: ${(usedBytes / totalBytes * 100).toFixed(2)}%`);
console.log(`Unused Code: ${((totalBytes - usedBytes) / 1024).toFixed(2)}KB`);
await browser.close();
}
measurePerformance('http://localhost:5173');
typescript
ベストプラクティス
チェックリスト
ビルド最適化を実施する際の確認事項です。
コード分割
- ルートベースの自動分割を活用
- 重いコンポーネントは動的インポート
- 管理画面など特定機能は別バンドル
Tree Shaking
- 個別エクスポートを使用
- 副作用のないコードを記述
- package.jsonでsideEffects: false
画像最適化
- 適切なフォーマット(WebP/AVIF)
- レスポンシブ画像の実装
- Lazy loadingの活用
圧縮
- Brotli圧縮を優先
- 1KB以上のファイルを圧縮
- CDNレベルでも圧縮を有効化
測定と監視
- Bundle Analyzerでサイズ確認
- Core Web Vitals測定
- 継続的なパフォーマンス監視
まとめ
ビルド最適化は、アプリケーションのパフォーマンスを左右する重要な要素です。Viteの設定、コード分割、Tree Shaking、圧縮戦略を適切に組み合わせることで、高速で効率的なアプリケーションを実現できます。
重要なポイント
- 測定なくして最適化なし - 常にパフォーマンスを測定し、効果を検証
- 段階的な最適化 - 一度にすべてを最適化せず、効果の高いものから実施
- ユーザー体験を優先 - 数値だけでなく、実際の使用感を重視
次のステップ
- パフォーマンス最適化 - ランタイムパフォーマンスの改善
- キャッシュ戦略 - 効果的なキャッシュの実装
- 実行環境とランタイム - デプロイ環境の最適化