Introduction
While working on a real-life project, I encountered an inefficient React.js import strategy. In this blog, I'll walk you through the problem I encountered. Read how I improved the design by making a more dynamic approach using the Wrapper Pattern.
Table of Contents
- The problem
- The issue with this approach
- Solution
- Visual representation
- Another example: Lodash
- How to choose the right library?
- Conslusion
The problem
In one project I saw importing Framer Motion (or any other lib), like this:
import { motion } from 'framer-motion' -> imported 110,63KB
The Issue with This Approach
At first glance, 110.63KB might not seem like much. However, if this import is used across multiple component files, the bundle size can quickly grow up to 40% or even more! Also, if in some future changes you wanted to change/remove imports for this you will need to modify all of these files.
Why Does This Happen?
Tree Shaking Limitations – Framer Motion exports a large set of utilities, and importing motion directly can pull in unnecessary code.
Redundant Imports in Multiple Files – Although the library is included in the final bundle only once, improper imports can lead to unnecessary bloat.
Solution
Create MotionWrapper.tsx
file:
// smaller bundle size
import { m } from 'framer-motion'
const MotionWrapper = m
export default MotionWrapper
Framer-motion exports also {m} which contains only neccessary things. Include it in the file above.
Now, wherever you had this for example:
// App.tsx
import { motion } from 'framer-motion'
const App = () => {
return(
<motion.div {smt} />
)
}
Refactor it to this:
// App.tsx
import { MotionWrapper } from '@/components/MotionWrapper'
const App = () => {
return(
<MotionWrapper.div {smt} />
)
}
Visual representation
Now our build looks like this:
Generated using vite-bundle-visualizer.
Another example: Lodash
Another example of an inefficient import strategy is using Lodash like this:
import _ from 'lodash' // full library, increases bundle size
A better way to handle this is by creating a LodashWrapper:
// LodashWrapper.tsx
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
export const LodashWrapper = { debounce, throttle };
Now, instead of importing the whole library, use only what you need:
import { LodashWrapper } from '@/utils/LodashWrapper';
const handleInput = LodashWrapper.debounce((value) => {
console.log('Debounced Input:', value);
}, 300);
This approach ensures only the necessary utilities are included in the bundle, significantly reducing unnecessary imports.
Conclusion
Now the size is around 50KB
which is totally fine.
This is around 3%
of our total bundle size.
How to choose the right library?
When selecting a library, it's essential to consider how it handles imports. For example, if you're deciding between Recharts
and Chart.js
, one major factor is how they manage imports.
Recharts does not yet support direct imports for individual components, meaning you must import the entire library, which increases the bundle size.
Chart.js, on the other hand, allows tree-shakable imports, meaning you can import only the specific chart components you need, reducing the final bundle size.
Example of how Recharts forces large imports:
import { LineChart } from 'recharts'; // Includes unnecessary code
With Chart.js, you can optimize your imports:
import { Chart, LineElement, CategoryScale } from 'chart.js';
Chart.register(LineElement, CategoryScale);
This flexibility makes Chart.js a better choice for performance-conscious applications.
Note:
- This section was not intended to criticize Recharts but was used for a practical demonstration.
-
Recharts will address this issue in the upcoming
v3
. You can check my opened issue in their repository.
Wrapper Pattern in real life project
You can visit the repository here.
Hope you found this solution helpful! Thanks for reading. 😊
Top comments (0)