JavaScript bundle-optimering — Code splitting, tree shaking og analyse
JavaScript er dyrt: download, parse, kompilering og eksekvering. Code splitting sender kun den nødvendige kode til brugeren. Tree shaking fjerner ubrugt kode. Bundle-analyse afslører hvad der fylder.
JavaScript er den dyreste ressource per byte på nettet: det skal downloades, dekomprimeres, parses, kompileres og eksekveres — alt på main thread. Et 500 KB JavaScript-bundle kan blokere interaktivitet i 2-5 sekunder på en middel Android-telefon. Bundle-optimering handler om at sende så lidt JavaScript som muligt, og kun det der er nødvendigt.
Tree shaking
Tree shaking er bundlerens evne til at fjerne ubrugt kode (dead code elimination). Det kræver ESM-imports (ikke CommonJS require):
// ✅ Tree shakeable — kun `formatDate` inkluderes i bundle
import { formatDate } from './utils';
// ❌ Ikke tree shakeable — hele utils-modulet inkluderes
const utils = require('./utils');
Mange npm-pakker eksponerer ikke tree-shakeable ESM. lodash er et klassisk eksempel — import _ from 'lodash' inkluderer hele biblioteket (70+ KB). Alternativet: import debounce from 'lodash/debounce' eller lodash-es.
Sidecar bundles: Vite og Rollup er fremragende til tree shaking af ESM. Webpack kræver mode: 'production' og sideEffects: false i package.json for optimal tree shaking.
Code splitting
Code splitting opdeler ét monolitisk bundle i chunks der loades on-demand:
Route-baseret splitting (React, Next.js):
// React.lazy — chunk loades kun når komponenten renderes
const CheckoutPage = React.lazy(() => import('./CheckoutPage'));
const BlogPost = React.lazy(() => import('./BlogPost'));
Dynamiske imports (alle bundlers):
// Kode loades kun ved klik
button.addEventListener('click', async () => {
const { initChart } = await import('./chart-library');
initChart(data);
});
Vendor splitting — adskil tredjeparts-biblioteker fra applikationskode. Biblioteker ændres sjældnere og caches længere:
// vite.config.js
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
charts: ['chart.js'],
}
}
}
}
Bundle-analyse
Webpack Bundle Analyzer visualiserer hvad der fylder:
npm install --save-dev webpack-bundle-analyzer
# Kør med npx webpack-bundle-analyzer stats.json
Vite bundle visualizer:
npx vite-bundle-visualizer
source-map-explorer analyserer en compiled bundle via source maps:
npx source-map-explorer dist/main.js
Kig efter: duplikeret kode, unødvendigt store biblioteker, og kode der burde lazy-loades.
Komprimering
Alle moderne servere og CDN’er understøtter komprimering. Gzip reducerer typisk JavaScript med 60-70%. Brotli reducerer 15-25% mere end Gzip for tekstfiler:
# Nginx — aktiver Brotli og Gzip
brotli on;
brotli_comp_level 6;
gzip on;
gzip_types text/javascript application/javascript;
Vite og Webpack genererer .gz og .br filer ved build — serveren sender den komprimerede version automatisk.
Moderne JavaScript med browsermål
Bundling til ES2015 (default for mange tools) inkluderer polyfills der er unødvendige for moderne browsere. Specificér et moderne browsermål for at eliminere unødvendige polyfills:
// vite.config.js
build: {
target: 'es2020'
}
// .browserslistrc — moderne browsere only
last 2 Chrome versions
last 2 Firefox versions
last 2 Safari versions
En moderne target reducerer typisk bundle-størrelse med 10-20% ved at eliminere transpilerings-overhead.
Import cost og editor-integration
VS Code extension Import Cost viser størrelsen af hvert import direkte i editoren — tidlig feedback inden bundling. Det er det nemmeste tool til at fange dyre biblioteker før de når produktion. → Denne artikel er en del af Web Performance — Core Web Vitals og teknisk hastighed.
Andre artikler i samme emne
- Animation performance — Frame rate og jank-fri bevægelse
- Billedoptimering — Formater, størrelser og SEO
- Browser rendering pipeline — Fra HTML til pixels
- CLS-problemer — Årsager til Cumulative Layout Shift og løsninger
- Core Web Vitals — LCP, INP og CLS forklaret
- CSS — Cascading Style Sheets og SEO
- CSS containment — Isolér rendering og accelerér layout
- Font-optimering — Webfonts, FOUT og SEO
- HTTP/2 og HTTP/3 — Protokoller der eliminerer latency-overhead
- INP-optimering — Interaction to Next Paint og Core Web Vitals
- Kritisk renderingsti — Hvad browseren gør før du ser noget
- Latency — Forsinkelse i netværket og hvad du kan gøre ved det
- Lazy loading — Udskyd indlæsning af billeder og ressourcer
- LCP-optimering — Sådan forbedrer du Largest Contentful Paint
- Long Tasks og LoAF — Hvad blokerer main thread
- Navigation og resource timings — Browserens performance-API
- PageSpeed og SEO — Hastighed som rankingfaktor
- Performance budgets — Grænser der forhindrer regressioner
- Performance timings — Hvornår er det for langsomt?
- Resource hints — Preload, prefetch og preconnect til SEO
- Responsive images — srcset, sizes og art direction
- Responsivt design — Mobile-first og SEO
- RUM vs. syntetisk monitoring — To syn på web performance
- Service Workers — Offline caching og PWA performance
- Speculative loading — Prefetch og prerender af næste side
- Startup performance — Hurtig app-start og time to interactive
- Tredjeparts-scripts og performance — Impact og strategier
- Tredjepartsscripts — Analytics, ads og performance-konsekvenser
- TTFB og hosting — Server response time og SEO
- Video performance — Lazy loading, formater og indlæsningsstrategi
- Viewport tag — Meta viewport og mobil-rendering
- Web Workers — Parallel JavaScript uden main thread-blokning
Ofte stillede spørgsmål
- Hvad er forskellen på code splitting og tree shaking?
- Code splitting opdeler bundlet i chunks der loades on-demand — brugere downloader kun kode til de sider de besøger. Tree shaking fjerner ubrugt kode fra bundlet ved build-time — hvis du importerer én funktion fra et bibliotek, inkluderes kun den funktion. De er komplementære: tree shaking reducerer chunk-størrelse, code splitting reducerer hvad der loades.
- Hvad er et godt JavaScript-budget for mobilbrugere?
- Google anbefaler under 150 KB komprimeret JavaScript for den kritiske eksekverings-sti (kode der kræves for at siden er interaktiv). Total JavaScript-budget varierer med kontekst, men 300-400 KB komprimeret er en praktisk grænse for de fleste sites. Husk at 300 KB komprimeret JavaScript kan være 1 MB+ ukomprimeret eksekvering.
- Hvordan analyserer jeg hvad der fylder i mit JavaScript-bundle?
- Bundle-analyse kræver et visualiseringsværktøj der viser bundle-indholdet som et interaktivt treemap. Til Vite bruges vite-bundle-visualizer eller rollup-plugin-visualizer. Til Webpack bruges webpack-bundle-analyzer. Source map explorer analyserer den producerede kode via source maps. Typiske synder: store tredjeparts-biblioteker (moment.js, lodash) der importeres i sin helhed, duplicate dependencies og vendor chunks der er for store til at cache effektivt.
- Hvad er dynamic import og hvornår bør det bruges?
- Dynamic import (import()) er JavaScript-syntaks der loader et modul asynkront og on-demand frem for ved startup. Det er mekanismen bag route-based code splitting i frameworks og lazy-loading af komponenter. Brug dynamic import til kode der kun behøves ved brugerinteraktion (fx en modal, en chart-komponent eller et rigt tekstfelt), kode der kun bruges på specifikke sider, og store biblioteker som kun en lille del af brugerne når. Det reducerer initial bundle-størrelse og forbedrer Time to Interactive.
- Hvad er preload og prefetch til JavaScript og hvornår bruges hvad?
- Preload (<link rel=preload as=script>) fortæller browseren at fetche en ressource med høj prioritet — bruges til scripts der er kritiske for sideindlæsning men opdages sent i HTML. Prefetch (<link rel=prefetch>) fetcher ressourcer med lav prioritet til fremtidig brug — bruges til scripts der sandsynligvis behøves på næste side i brugerens navigation. For JavaScript-chunks: brug preload til kritisk JavaScript der er nødvendigt for den aktuelle sides interaktivitet, og prefetch til route-chunks for sandsynlige næste sider.
Placering i ordbogen
- Animation performance — Frame rate og jank-fri bevægelse
- Billedoptimering — Formater, størrelser og SEO
- Browser rendering pipeline — Fra HTML til pixels
- CLS-problemer — Årsager til Cumulative Layout Shift og løsninger
- Core Web Vitals — LCP, INP og CLS forklaret
- CSS — Cascading Style Sheets og SEO
- CSS containment — Isolér rendering og accelerér layout
- Font-optimering — Webfonts, FOUT og SEO
- HTTP/2 og HTTP/3 — Protokoller der eliminerer latency-overhead
- INP-optimering — Interaction to Next Paint og Core Web Vitals
- Kritisk renderingsti — Hvad browseren gør før du ser noget
- Latency — Forsinkelse i netværket og hvad du kan gøre ved det
- Lazy loading — Udskyd indlæsning af billeder og ressourcer
- LCP-optimering — Sådan forbedrer du Largest Contentful Paint
- Long Tasks og LoAF — Hvad blokerer main thread
- Navigation og resource timings — Browserens performance-API
- PageSpeed og SEO — Hastighed som rankingfaktor
- Performance budgets — Grænser der forhindrer regressioner
- Performance timings — Hvornår er det for langsomt?
- Resource hints — Preload, prefetch og preconnect til SEO
- Responsive images — srcset, sizes og art direction
- Responsivt design — Mobile-first og SEO
- RUM vs. syntetisk monitoring — To syn på web performance
- Service Workers — Offline caching og PWA performance
- Speculative loading — Prefetch og prerender af næste side
- Startup performance — Hurtig app-start og time to interactive
- Tredjeparts-scripts og performance — Impact og strategier
- Tredjepartsscripts — Analytics, ads og performance-konsekvenser
- TTFB og hosting — Server response time og SEO
- Video performance — Lazy loading, formater og indlæsningsstrategi
- Viewport tag — Meta viewport og mobil-rendering
- Web Workers — Parallel JavaScript uden main thread-blokning