惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
O
OpenAI News
D
Docker
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 司徒正美
C
CXSECURITY Database RSS Feed - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
K
Kaspersky official blog
Security Latest
Security Latest
T
Tailwind CSS Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Vulnerabilities – Threatpost
W
WeLiveSecurity
N
News and Events Feed by Topic
aimingoo的专栏
aimingoo的专栏
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cyber Attacks, Cyber Crime and Cyber Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
爱范儿
爱范儿
宝玉的分享
宝玉的分享
腾讯CDC
H
Heimdal Security Blog
Webroot Blog
Webroot Blog
AI
AI
WordPress大学
WordPress大学
Recorded Future
Recorded Future
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
C
Check Point Blog
TaoSecurity Blog
TaoSecurity Blog
Cisco Talos Blog
Cisco Talos Blog
The Cloudflare Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - Franky
云风的 BLOG
云风的 BLOG

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Tree Shaking and Code Splitting in JavaScript
Ashish Kumar · 2026-05-01 · via DEV Community

A 2MB gzipped JavaScript bundle is ~7MB for V8 to parse and compile. On a mid-range Android at 3G, that is 12 seconds before a single interaction is possible. Bundle size is not an abstract metric — it is directly proportional to Time to Interactive on real hardware.

Why bundle bloat happens silently: Every convenient import adds to the module graph. moment.js locale files, full lodash imports, icon libraries with 1000 icons — none of these produce visible errors. They just make the app slower on devices you don't test on.

What this covers: How bundlers construct the module graph, where tree shaking fails silently, how sideEffects in package.json controls elimination, and how dynamic import() splits the bundle into chunks that load on demand.

Diagram of a bundler pipeline: entry point → module graph → tree shaking → code splitting → output chunks.


What a bundle actually is

When you run npm run build, your bundler (Rollup inside Vite, Webpack, esbuild) does roughly this:

  1. Starting from your entry point (main.tsx), it follows every import statement
  2. It builds a module graph a directed graph of every file that's reachable from the entry
  3. It merges all those files into one or more output files, resolving module boundaries
  4. It applies tree shaking to remove code that's imported but never called
  5. It minifies (removes whitespace and shortens names) and optionally compresses

The default result is one file containing your entire application: every component, every utility, every vendor library. That single file must be downloaded, parsed, and compiled before anything runs.

The reason everything ends up in one file by default is performance optimization for the common case: one large file is often faster than dozens of small ones, because each file requires a separate HTTP request (with its own connection overhead), and the browser's HTTP cache is most effective when file names are stable. One bundle = one cache entry = minimal request overhead.

But "one big file" becomes a liability when:

  • The bundle contains code for routes the user will never visit
  • It contains multiple large vendor libraries that could be split into separate caches
  • Any change to the app (even one line) invalidates the entire bundle cache

This is why code splitting matters.


Bundle analysis: seeing what's actually in there

Before optimizing, you need to understand the bundle's contents. Three tools I actually use:

rollup-plugin-visualizer (for Vite/Rollup projects) generates an interactive treemap:

// vite.config.ts

  plugins: [
    visualizer({
      filename: 'bundle-stats.html',
      gzipSize: true,
      brotliSize: true,
    })
  ]
};

Enter fullscreen mode Exit fullscreen mode

After npm run build, open bundle-stats.html. You see a rectangle-packed treemap where each rectangle's area is proportional to the module's size in the bundle. Scan for large vendor rectangles that surprise you. A common discovery is that moment.js is taking 300KB because someone imported it for date formatting once.

webpack-bundle-analyzer does the same for Webpack:

npm install --save-dev webpack-bundle-analyzer
npx webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json

Enter fullscreen mode Exit fullscreen mode

source-map-explorer is useful when you have source maps but not a plugin-compatible build setup:

npx source-map-explorer dist/assets/index-abc123.js

Enter fullscreen mode Exit fullscreen mode

What to look for when reading a bundle visualization:

  1. Unexpectedly large single modules often a library you didn't know was that big
  2. Duplicate packages the same library appearing twice at different versions
  3. Code you thought was excluded development utilities or storybook code in production builds
  4. Polyfills for modern browsers if your target browsers support native features, polyfills are dead weight

Tree shaking: how it works and why it fails silently

Tree shaking is the process of eliminating code from the bundle that is imported but never actually called. The term comes from "shaking a tree and letting the dead leaves fall."

Tree shaking relies on ES module static analysis. The key insight: import and export statements are static: the module graph is fully knowable at build time without executing any code.

// ES Module: statically analyzable

// Bundler knows: only formatDate and parseDate are used.
// Any other exports from dates.js can be eliminated.

Enter fullscreen mode Exit fullscreen mode

Compare to CommonJS:

// CommonJS: NOT statically analyzable
const dates = require('./dates.js');
// Bundler cannot know which properties of 'dates' are used at build time.
// require() could be called conditionally, so the entire module must be included.

Enter fullscreen mode Exit fullscreen mode

This is why mixing CommonJS and ES modules causes tree shaking to silently fail. The bundler falls back to including the entire module.


The sideEffects field

Even with ES modules, tree shaking requires one more thing: the sideEffects field in package.json.

A side effect is code that runs when a module is imported, regardless of whether you use its exports. CSS imports, polyfills, global registrations: these are side effects. If the bundler assumes a module has side effects, it can't eliminate it even if none of its exports are used.

// package.json
{
  "name": "my-library",
  "sideEffects": false
}

Enter fullscreen mode Exit fullscreen mode

"sideEffects": false tells bundlers: "every file in this package is pure. If nothing imports from it, it can be eliminated." Without this, even unused modules from a dependency are included in the bundle.

For your own application code, you can be more precise:

{
  "sideEffects": ["*.css", "src/polyfills.js"]
}

Enter fullscreen mode Exit fullscreen mode

This says: CSS files and the polyfills file have side effects (they must be included), but all other JavaScript files are pure. The bundler can tree-shake the JavaScript while preserving the CSS imports.


Common tree shaking failures

Barrel files

A barrel file is an index.js that re-exports everything from a directory:

// components/index.js (barrel file)

// ... 50 more exports

Enter fullscreen mode Exit fullscreen mode

// Using it

Enter fullscreen mode Exit fullscreen mode

The problem: some bundlers (particularly older Webpack configurations and certain Rollup setups) cannot tree-shake barrel files reliably. When you import Button from the barrel, the bundler may include the entire barrel all 50+ components because it can't prove the barrel itself doesn't have side effects from the re-export pattern.

The fix is to import directly from the source file:


Enter fullscreen mode Exit fullscreen mode

Or configure your bundler explicitly to handle barrels. Vite handles this well in modern versions, but it's worth verifying with the bundle analyzer that barrel imports aren't inflating your output.

lodash

Importing lodash like this includes the entire 70KB library:


Enter fullscreen mode Exit fullscreen mode

The fix is either lodash-es (which uses ES modules and tree-shakes correctly) or direct cherry-picking:


// or

Enter fullscreen mode Exit fullscreen mode

moment.js locale problem

moment.js has a notorious bundling issue: it includes all locale files by default. If you're using moment at all, you're pulling in ~300KB of locale data for languages you'll never use.

// This pulls in ALL locales: ~300KB gzipped

Enter fullscreen mode Exit fullscreen mode

The options, ranked by recommendation:

  1. Replace with date-fns tree-shakeable, only includes what you import
  2. Replace with dayjs 2KB gzipped, locale files are separate optional imports
  3. Use Webpack IgnorePlugin to exclude locale files from moment (if migration is not feasible)

Code splitting

Code splitting is the practice of splitting your bundle into multiple files that load on demand. Instead of one 2MB bundle, you might have:

  • A 200KB core bundle that loads immediately
  • A 400KB dashboard chunk that loads when the user navigates to /dashboard
  • A 100KB admin chunk that loads only if the user is an admin

The result: users only download code for the features they actually use.

React.lazy() and Suspense

The canonical React code splitting pattern:


// Instead of: import Dashboard from './Dashboard';
const Dashboard = lazy(() => import('./Dashboard'));
const AdminPanel = lazy(() => import('./AdminPanel'));

function App() {
  return (
    }>

    </Suspense>
  );
}

Enter fullscreen mode Exit fullscreen mode

React.lazy() takes a function that returns a dynamic import(). The bundler sees the dynamic import and automatically creates a separate chunk for Dashboard.tsx and all its unique dependencies. That chunk is not downloaded until the user navigates to /dashboard.

The Suspense boundary shows `` while the chunk is downloading. For route-level splits, this is typically the loading spinner or skeleton you'd show during data fetching anyway.

Dynamic import() and chunk naming

Under the hood, lazy() uses dynamic import(). You can use it directly for non-component code:

`js
// Loads only when called, not at app startup
async function processSpreadsheet(file) {
const { read, utils } = await import('xlsx'); // 400KB library, only loaded when needed
const workbook = read(await file.arrayBuffer());
return utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
}
`

Magic comments let you control chunk names:

`js
const Dashboard = lazy(() => import(
/* webpackChunkName: "dashboard" */
/* vite: { chunkName: "dashboard" } */
'./Dashboard'
));
`

Without magic comments, bundlers generate hash-based names like chunk-abc123.js. Named chunks make your build output readable and help with debugging production issues.


Vendor splitting for cache utilization

Libraries like React, React DOM, and React Router change infrequently, maybe once every few months when you upgrade. Your application code changes with every deployment.

If they're all in one bundle, every deployment invalidates the browser's cache for React even though React itself hasn't changed.

Vendor splitting keeps stable libraries in separate chunks with content-hash filenames:

`js
// vite.config.ts

build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
'vendor-query': ['@tanstack/react-query'],
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
}
}
}
}
};
`

Now vendor-react-[hash].js stays cached across deployments. Users who visited yesterday already have React cached. They only download your application code when you deploy.

Bundle type Changes on every deploy? Cache lifetime
App code Yes Short (invalidated on every deploy)
Vendor (React etc.) No (until you upgrade) Long (months between upgrades)
Feature chunks Only when that feature changes Medium

The entry point waterfall

One anti-pattern that's easy to overlook: eagerly importing all routes in the entry point.

`js
// main.tsx (WRONG): eager imports defeat code splitting

`

Even if you use React Router to only render one route at a time, Webpack/Vite will see these static imports and include all pages in the initial bundle. The dynamic import pattern only works when import() is actually dynamic:

`js
// main.tsx (RIGHT): all routes are lazily loaded
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Admin = lazy(() => import('./pages/Admin'));
const Reports = lazy(() => import('./pages/Reports'));
`

Check your bundle visualizer. If all your route components appear in the main chunk, this is why.


Measuring bundle impact in CI

Ad-hoc bundle analysis after the fact is better than nothing, but the real win is preventing bundle bloat in CI before it reaches production.

size-limit is a package that fails your CI build if the bundle exceeds defined limits:

`json
// package.json
{
"size-limit": [
{
"path": "dist/assets/index-*.js",
"limit": "300 KB",
"gzip": true
},
{
"path": "dist/assets/vendor-react-*.js",
"limit": "150 KB",
"gzip": true
}
]
}
`

`yaml

.github/workflows/build.yml

  • name: Check bundle size run: npx size-limit `

With this in place, a PR that inadvertently adds a 500KB dependency (say, someone imports moment instead of date-fns) will fail CI with a clear error message:

`
dist/assets/index-abc123.js
Size: 487 KB with all dependencies, minified and gzipped

Package size limit has exceeded the limit.
Size limit: 300 KB
Size: 487 KB
Try to reduce size or increase the limit.
`

Performance budgets go beyond just JS size. Lighthouse CI lets you set budgets on LCP, TTI, and total transfer size:

`json
// lighthouserc.js
module.exports = {
assert: {
assertions: {
'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
'interactive': ['error', { maxNumericValue: 5000 }],
'total-byte-weight': ['error', { maxNumericValue: 1000000 }],
}
}
};
`


The bundle optimization checklist

Step Tool Expected win
Analyze current bundle rollup-plugin-visualizer Identify large dependencies
Find duplicate packages npm dedupe, bundlesize Eliminate redundant copies
Fix lodash imports lodash-es or cherry-pick Save 50–70KB
Add sideEffects: false package.json Enable tree shaking for your code
Route-level code splitting React.lazy() Defer non-critical routes
Vendor splitting Rollup manualChunks Improve cache utilization
Dynamic import for heavy libs import() Only load when needed
Set size limits in CI size-limit Prevent future regressions

Bundles don't bloat all at once. They bloat incrementally — one convenient import at a time. The defense is measurement, budgets, and a clear understanding of what each dependency actually costs to download, parse, and compile on real hardware.


Read the original article on Renderlog.in:
https://renderlog.in/blog/build-bundles-treeshaking-code-splitting/

If you found this helpful, I've also built some free tools for developers and everyday users. Feel free to try them once:

JSON Tools: https://json.renderlog.in
Text Tools: https://text.renderlog.in
QR Tools: https://qr.renderlog.in