sideEffects and Tree Shaking Loss Issues
Background
After introducing lodash into the project, analysis of the bundle revealed that lodash was too large in size.
To optimize the bundle size, we decided to enable tree shaking by configuring the sideEffects field in package.json.
However, after enabling tree shaking, two issues appeared:
- CSS styles were missing
- Some JS modules were not executed
Solution
Solution for missing CSS styles
Mark CSS files as side effects in package.json:
"sideEffects": [
"**/*.css"
]
Solution for unexecuted JS modules
For modules that do not export anything (i.e., no export) but need to be executed, add them to the sideEffects configuration:
"sideEffects": [
"./src/utils/resetFontSize.ts"
]
Principles of Tree Shaking
The sideEffects field is used to mark which files have side effects. When performing tree shaking, the bundler generally follows these steps:
- Perform static analysis based on
ESModuleto identify unreferenced modules. - Determine whether the module has side effects; if not, it can be safely removed.
- How to determine if a module has side effects?
On one hand,
terser(whichwebpackdepends on) detects side effects in statements (but since JS is a dynamically typed language, in many cases the result cannot be analyzed without executing the code, so statement-level side effect analysis is limited. It can be assisted by magic comments/*#__PURE__*/to indicate that the declaration has no side effects).On the other hand, we can tell the bundler which files have side effects via the
sideEffectsconfiguration option, thus allowing the bundler to skip analysis of the entire module and its subtree. In many cases, static analysis cannot accurately determine side effects, so magic comments/*#__PURE__*/can be used to mark code without side effects.
Practical Example
Here is a typical problem example:
// resetFontSize.ts
import { detectDeviceType } from './deviceUtils';
(function flexible(window: Window, document: Document) {
function resetFontSize() {
const clientWidth = parseInt(
document.documentElement.clientWidth.toString(),
10
);
let size = 0;
// Use the detectDeviceType function to determine the device type
if (detectDeviceType() === 'desktop') {
size = (document.documentElement.clientWidth / 1920) * 16;
document.documentElement.style.fontSize = (size <= 14 ? 13 : size) + 'px';
} else {
size = clientWidth;
const fontSize = (size / 750) * 16;
document.documentElement.style.fontSize = fontSize + 'px';
}
}
resetFontSize();
window.addEventListener('pageshow', resetFontSize);
window.addEventListener('resize', resetFontSize);
})(window, document);
// index.ts
import '@utils/resetFontSize';
Although index.ts imports resetFontSize, because the module has no export, tree shaking will remove it. The solution is to declare it in sideEffects:
// package.json
"sideEffects": [
"./src/utils/resetFontSize.ts"
]
Special handling for Vue projects
For Vue projects, if a component contains styles, special treatment is required:
// package.json
"sideEffects": [
"**/*.css",
// Mark Vue components that contain styles
"./src/ComponentWithStyle.vue"
]
Alternatively, you can choose to completely disable tree shaking.
Best Practices
To better leverage tree shaking:
- Prefer using package versions that support ES modules, e.g., use
lodash-esinstead oflodash. - For necessary side-effect code, explicitly declare it in
sideEffects. - Use
/*#__PURE__*/comments appropriately to mark code without side effects.