Tree shaking: how to remove unused JavaScript safely
Tree shaking is a build-time technique that removes unused code from JavaScript bundles. It analyses the relationships between imported and exported modules, then keeps the functions, variables and dependencies that can reach the application’s entry points. The result is usually a smaller download, faster parsing and less work for the browser.
The phrase comes from shaking a tree so that dead branches fall away. In a modern front-end project, those branches may be unused utility functions, optional components or entire modules imported from a large library. The process is valuable for Australian websites serving mobile users in Sydney, Melbourne, Brisbane, Perth and regional areas, where efficient delivery can improve the experience on varied connections and devices.
How tree shaking works
Tree shaking relies mainly on static analysis. A bundler reads ES modules, follows statements such as import { formatDate } from './dates.js', and identifies which exports are actually used. An unused export can then be excluded from the production bundle, provided removing it will not change the application’s behaviour.
This is different from simply minifying JavaScript. Minification shortens names, removes whitespace and applies safe code transformations. Tree shaking removes unreachable module code. A production build may use both methods: the bundler eliminates unused branches first, then a minifier compresses what remains.
The technique works best with ES modules because their import and export structure is known before execution. CommonJS patterns such as require() can be harder to analyse, particularly when module names or property access are calculated dynamically. Some bundlers can still optimise CommonJS packages, but the result is less predictable.
Use module-friendly imports
The clearest starting point is to import only what the page needs. A selective import communicates intent to the bundler:
import { debounce } from 'lodash-es';
A broad import can make optimisation more difficult:
import _ from 'lodash';
The second form does not always force the entire package into the final file, since modern tools can inspect some libraries. Still, it introduces more opportunities for compatibility issues and retained code. Prefer packages that publish modern ES module builds, and check the package documentation before choosing an import style.
Your own code should follow the same principle. Keep related functionality in focused modules rather than placing every helper in one large file. Named exports make usage easier to inspect:
export const formatPrice = () => {};
export const formatWeight = () => {};
If a checkout page imports only formatPrice, a capable bundler has a better chance of excluding formatWeight. This modular structure also helps teams maintain projects as content, analytics and interactive features expand.
Configure the bundler correctly
Tools such as Rollup, webpack, esbuild and Vite can perform dead-code elimination during a production build. Development servers often preserve modules or include extra diagnostics for rapid feedback, so inspect a production output rather than judging tree shaking from a local development file.
For webpack, production mode enables important optimisations, including minimisation and removal of unused exports. A typical configuration may look like this:
export default {
mode: 'production',
optimization: {
usedExports: true,
sideEffects: true
}
};
Rollup and Vite are also strong choices for ES module projects. Their output can be split into chunks, allowing a visitor to download an account dashboard or image editor only when that feature is opened. For a content-focused platform, this can keep the initial page lean while preserving richer functionality behind a route or interaction. A visual examples page, for instance, can load gallery controls separately from the basic site shell.
Use source maps and bundle analysis during testing. Reports from tools such as webpack-bundle-analyzer, rollup-plugin-visualizer or a browser coverage panel can reveal which dependency is responsible for unexpected growth. A smaller source file is not automatically a smaller network transfer; inspect compressed JavaScript and the chunks actually requested by the browser.
Mark side effects accurately
Tree shaking becomes unsafe when a module performs work as soon as it is imported. Examples include registering a custom element, adding a CSS class to document.body, polyfilling a browser API or attaching a global event listener. These actions are side effects because they matter even when no exported function is called.
A package can describe this behaviour with the sideEffects field in package.json:
{
"sideEffects": [
"*.css",
"./src/register-components.js"
]
}
A value of false tells the bundler that importing files has no effects beyond their exports. That declaration can produce excellent savings, but it must be accurate. Marking an initialisation module as side-effect-free may cause it to disappear, leaving a component unregistered or a stylesheet unloaded.
Keep side-effectful startup code in clearly named files and import those files deliberately. Avoid hiding essential setup inside a general utility module. When changing the setting, run tests that cover page startup, navigation, forms, styling and browser integrations. The speed gain is not worthwhile if production behaviour silently changes.
Avoid patterns that limit elimination
Dynamic access can prevent a bundler from proving that code is unused. For example:
const tools = require('./tools');
tools[actionFromUrl]();
Because the property is selected at runtime, the bundler may retain every export in tools. Explicit imports or a controlled map usually give it better information:
import { save, share } from './tools';
const actions = { save, share };
actions[actionFromUrl]();
Barrel files deserve attention as well. An index.js that re-exports dozens of modules is convenient, but it can hide expensive dependencies behind a single entry point. Proper ES module settings generally allow unused re-exports to be removed, yet packages with side effects or CommonJS internals may reduce the benefit.
Large UI libraries, date packages and icon collections are common sources of waste. Import a single icon rather than an entire icon set, select a smaller locale configuration, and use route-level code splitting for features that are not needed immediately. If a dependency offers a browser build and a server build, ensure the application resolves the appropriate one.
Measure results in an Australian context
Measure before and after with the production build, then test on realistic devices and network conditions. A developer connection in Melbourne does not represent every visitor in Australia. Regional users may experience higher latency, while people commuting in Sydney or using mobile data in Brisbane may notice parsing and download costs more than a desktop user on a fast office connection.
Performance budgets can make the improvement repeatable. Track initial JavaScript transfer, total page weight, Largest Contentful Paint and interaction responsiveness in a continuous integration pipeline. Use Australian test locations where available, and compare results through a mobile connection as well as the NBN connection commonly used at home. A smaller bundle also reduces data use for visitors with limited mobile allowances.
Compliance and trust should remain part of the implementation. If analytics, advertising or personalisation scripts are removed or deferred, review the consent flow and the way data is handled under the Australian Privacy Act and applicable Australian Privacy Principles. Removing an unused library is straightforward; removing code that supports a privacy notice or consent preference requires a deliberate review.
Content teams may also need to preserve public material for records, reporting or media work. A practical archiving guide can help clarify what should be retained outside the live application. Keeping archival workflows separate from the customer-facing bundle prevents administrative tools and capture utilities from inflating the JavaScript delivered to ordinary visitors.
Finally, verify the application after optimisation. Check menus, forms, keyboard navigation, screen-reader labels, responsive layouts and older supported browsers. Review the generated bundle after each major dependency change, because a package update can alter its module format or side-effect metadata. Applied carefully, tree shaking becomes a routine part of maintaining a fast, accessible and efficient web experience rather than a one-off cleanup exercise.