Skip to content

Commit db7404a

Browse files
committed
added a more detailed example for tree-shaking clarification
1 parent c1ac637 commit db7404a

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed

Diff for: src/content/guides/tree-shaking.mdx

+218
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ contributors:
1818
- torifat
1919
- rahul3v
2020
- snitin315
21+
- vansh5632
2122
related:
2223
- title: Debugging Optimization Bailouts
2324
url: https://webpack.js.org/plugins/module-concatenation-plugin/#debugging-optimization-bailouts
@@ -312,6 +313,187 @@ After this optimization, other optimizations can still apply. For example: `butt
312313

313314
Module Concatenation also applies. So that these 4 modules plus the entry module (and probably more dependencies) can be concatenated. **`index.js` has no code generated in the end**.
314315

316+
## Full Example: Understanding Side Effects with CSS Files
317+
318+
To better understand the impact of the `sideEffects` flag, let's look at a complete example of an npm package with CSS assets and how they might be affected during tree shaking. We'll create a fictional UI component library called "awesome-ui".
319+
320+
### Package Structure
321+
322+
Our example package looks like this:
323+
324+
```bash
325+
awesome-ui/
326+
├── package.json
327+
├── dist/
328+
│ ├── index.js
329+
│ ├── components/
330+
│ │ ├── index.js
331+
│ │ ├── Button/
332+
│ │ │ ├── index.js
333+
│ │ │ └── Button.css
334+
│ │ ├── Card/
335+
│ │ │ ├── index.js
336+
│ │ │ └── Card.css
337+
│ │ └── Modal/
338+
│ │ ├── index.js
339+
│ │ └── Modal.css
340+
│ └── theme/
341+
│ ├── index.js
342+
│ └── defaultTheme.css
343+
```
344+
345+
### Package Files Content
346+
347+
**package.json**
348+
349+
```json
350+
{
351+
"name": "awesome-ui",
352+
"version": "1.0.0",
353+
"main": "dist/index.js",
354+
"sideEffects": false
355+
}
356+
```
357+
358+
**dist/index.js**
359+
360+
```javascript
361+
export * from './components';
362+
export * from './theme';
363+
```
364+
365+
**dist/components/index.js**
366+
367+
```javascript
368+
export { default as Button } from './Button';
369+
export { default as Card } from './Card';
370+
export { default as Modal } from './Modal';
371+
```
372+
373+
**dist/components/Button/index.js**
374+
375+
```javascript
376+
import './Button.css'; // This has a side effect - it applies styles when imported!
377+
378+
export default function Button(props) {
379+
// Button component implementation
380+
return {
381+
type: 'button',
382+
...props,
383+
};
384+
}
385+
```
386+
387+
**dist/components/Button/Button.css**
388+
389+
```css
390+
.awesome-ui-button {
391+
background-color: #0078d7;
392+
color: white;
393+
padding: 8px 16px;
394+
border-radius: 4px;
395+
border: none;
396+
cursor: pointer;
397+
}
398+
```
399+
400+
**dist/components/Card/index.js** and **dist/components/Modal/index.js** would have similar structure.
401+
402+
**dist/theme/index.js**
403+
404+
```javascript
405+
import './defaultTheme.css'; // This has a side effect!
406+
407+
export const themeColors = {
408+
primary: '#0078d7',
409+
secondary: '#f3f2f1',
410+
danger: '#d13438',
411+
};
412+
```
413+
414+
### What Happens When Consuming This Package?
415+
416+
Now, imagine a consumer application that only wants to use the Button component:
417+
418+
```javascript
419+
import { Button } from 'awesome-ui';
420+
421+
// Use the Button component
422+
```
423+
424+
#### With `sideEffects: false` in package.json
425+
426+
When webpack processes this import with tree shaking enabled:
427+
428+
1. It sees the import for only Button
429+
2. It looks at the package.json and sees `sideEffects: false`
430+
3. It determines it only needs to include the Button component code
431+
4. Since all files are marked as having no side effects, it will include **only** the JavaScript code for the Button
432+
5. **The CSS file import gets dropped!** Even though Button.css is imported in Button/index.js, webpack assumes this import has no side effects.
433+
434+
The result: The Button component will render, but without any styling since Button.css was eliminated during tree shaking.
435+
436+
#### The Correct Configuration for This Package
437+
438+
To fix this, we need to update package.json to properly mark CSS files as having side effects:
439+
440+
```json
441+
{
442+
"name": "awesome-ui",
443+
"version": "1.0.0",
444+
"main": "dist/index.js",
445+
"sideEffects": ["**/*.css"]
446+
}
447+
```
448+
449+
With this configuration:
450+
451+
1. Webpack still identifies that only the Button component is needed
452+
2. But now it recognizes that CSS files have side effects
453+
3. So, it includes Button.css when processing Button/index.js
454+
455+
### The Decision Tree for Side Effects
456+
457+
Here's how webpack evaluates modules during tree shaking:
458+
459+
1. Is the export from this module used directly or indirectly?
460+
461+
- If yes: Include the module
462+
- If no: Continue to step 2
463+
464+
2. Is the module marked with side effects?
465+
- If yes (`sideEffects` includes this file or is `true`): Include the module
466+
- If no (`sideEffects` is `false` or doesn't include this file): Exclude the module and its dependencies
467+
468+
For our library's files with the proper sideEffects configuration:
469+
470+
- `dist/index.js`: No direct export used, no side effects -> Skip over
471+
- `dist/components/index.js`: No direct export used, no side effects -> Skip over
472+
- `dist/components/Button/index.js`: Direct export used -> Include
473+
- `dist/components/Button/Button.css`: No exports, has side effects -> Include
474+
- `dist/components/Card/*`: No exports used, no side effects -> Exclude
475+
- `dist/components/Modal/*`: No exports used, no side effects -> Exclude
476+
- `dist/theme/*`: No exports used, no side effects -> Exclude
477+
478+
### Real-World Impact
479+
480+
The impact of incorrect side effects configuration can be significant:
481+
482+
1. **CSS not being included**: Components render without styles
483+
2. **Global JavaScript not running**: Polyfills or global configurations don't execute
484+
3. **Initialization code skipped**: Functions that register components or set up event listeners never run
485+
486+
These issues can be particularly hard to debug because they often only appear in production builds when tree shaking is enabled.
487+
488+
### Testing Side Effects Configuration
489+
490+
A good way to test if your side effects configuration is correct:
491+
492+
1. Create a minimal application that imports just one component
493+
2. Build it with production settings (with tree shaking enabled)
494+
3. Check if all necessary styles and behaviors work correctly
495+
4. Look at the generated bundle to confirm the right files are included
496+
315497
## Mark a function call as side-effect-free
316498

317499
It is possible to tell webpack that a function call is side-effect-free (pure) by using the `/*#__PURE__*/` annotation. It can be put in front of function calls to mark them as side-effect-free. Arguments passed to the function are not being marked by the annotation and may need to be marked individually. When the initial value in a variable declaration of an unused variable is considered as side-effect-free (pure), it is getting marked as dead code, not executed and dropped by the minimizer.
@@ -354,13 +536,49 @@ Notice anything different about `dist/bundle.js`? The whole bundle is now minifi
354536

355537
T> [`ModuleConcatenationPlugin`](/plugins/module-concatenation-plugin/) is needed for the tree shaking to work. It is added by `mode: 'production'`. If you are not using it, remember to add the [`ModuleConcatenationPlugin`](/plugins/module-concatenation-plugin/) manually.
356538

539+
## Common Pitfalls with Side Effects
540+
541+
When working with tree shaking and the `sideEffects` flag, there are several common pitfalls to avoid:
542+
543+
### 1. Over-optimistic `sideEffects: false`
544+
545+
Setting `sideEffects: false` in your package.json is tempting for optimal tree shaking, but this can cause problems if your code actually does have side effects. Examples of hidden side effects:
546+
547+
- CSS imports (as demonstrated above)
548+
- Polyfills that modify global objects
549+
- Libraries that register global event listeners
550+
- Code that modifies prototype chains
551+
552+
### 2. Re-exports with Side Effects
553+
554+
Consider this pattern:
555+
556+
```javascript
557+
// This file has side effects that might be skipped
558+
import './polyfill';
559+
560+
// Re-export components
561+
export * from './components';
562+
```
563+
564+
If a consumer only imports specific components, the polyfill import might be skipped entirely if not properly marked with side effects.
565+
566+
### 3. Forgetting about Nested Dependencies
567+
568+
Your package might correctly mark side effects, but if it depends on third-party packages that incorrectly mark their side effects, you might still encounter issues.
569+
570+
### 4. Testing Only in Development Mode
571+
572+
Tree shaking typically only fully activates in production mode. Testing only in development can hide tree shaking issues until deployment.
573+
357574
## Conclusion
358575

359576
What we've learned is that in order to take advantage of _tree shaking_, you must...
360577

361578
- Use ES2015 module syntax (i.e. `import` and `export`).
362579
- Ensure no compilers transform your ES2015 module syntax into CommonJS modules (this is the default behavior of the popular Babel preset @babel/preset-env - see the [documentation](https://babeljs.io/docs/en/babel-preset-env#modules) for more details).
363580
- Add a `"sideEffects"` property to your project's `package.json` file.
581+
- Be careful about correctly marking files with side effects, especially CSS imports.
364582
- Use the [`production`](/configuration/mode/#mode-production) `mode` configuration option to enable [various optimizations](/configuration/mode/#usage) including minification and tree shaking (side effects optimization is enabled in development mode using the flag value).
365583
- Make sure you set a correct value for [`devtool`](/configuration/devtool/#devtool) as some of them can't be used in `production` mode.
366584

0 commit comments

Comments
 (0)