The problem isn't Vite; it's a package on your application that is likely causing the issue.
I was running a Remix application and found it strange that one of my JS files was 914KB in size!
I pinpointed my problem by running vite-bundle-visualizer. It showed me that highlight.js, a package I'm using to highlight code, was bundling itself, contributing to the gigantic file size.
The highlight is the highlight.js package contributing to the overall
bundle size
To do the same, navigate to your project's root directory (where the vite.config.ts file is):
Run:
npx vite-bundle-visualizer
And see which packages Vite includes.
In my case, a direct import of the core library of highlight.js fixed my issue:
import hljs from "highlight.js/lib/core";
Afterwards:
Note:
Not all packages you can import {}
via destruct, like date-fns
(V1 or V2), support tree shaking (You need to use the /ESM suffix for that). So check that you are correctly importing them. If not, try to create a combination of:
- Lazy loading
- Suspense
- Intersection Observer
- Dynamic Imports
- Move tricky packages to a single file and destruct its import.
I know this is a Vue.js answer, but I wanted to share my React code so anyone can adapt it:
Example: (Actual production code)
Generic component that pulls from:
- Local SVG files
- Local png files
- react-icons different libraries.
This is a generic component in which I centralize all of my icons in a single place and then call them via a map key.
Problem:
It imported all the destructed icons even though I only requested one.
Solution 1:
I wrapped everything with a Suspense and a lazy import.
This allowed each icon to be solicited only when needed.
Problem 2:
The problem is that dynamic imports won't destruct the code correctly.
Solution 2:
We create another file that exports the destructed icon and import the file.
I also sprinkle some IntersectionObserver even further to delay the import.
Bringing everything together (simplified):
const iconMap = {
copy: () =>
import("./icon-exports/AiOutlineCopy").then((module) => ({
default: module.AiOutlineCopy,
})),
// Other icons omitted
awsLambda: () =>
import("./icon-exports/AwsLambda").then((module) => ({
default: module.AwsLambda,
})),
};
export type ColorVariant = "white" | "dark" | "red";
export const colorMap: Record<ColorVariant, string> = {
white: "#fff",
dark: "#1e2329",
red: "#e33122",
};
export type IconType = keyof typeof iconMap;
export type IconProps = {
icon: IconType;
color?: ColorVariant;
stroke?: ColorVariant;
fill?: ColorVariant;
size?: number;
width?: string;
height?: string;
customColor?: string;
className?: string;
};
export const Icon: React.FC<IconProps> = (props) => {
const { icon, color, stroke, customColor, size = 24, ...otherProps } = props;
const [loaded, setLoaded] = useState(false);
const { isIntersecting, setRef } = useIntersectionObserver({
root: null,
rootMargin: "0px",
threshold: 0.1,
});
useEffect(() => {
if (isIntersecting) {
setLoaded(true);
}
}, [isIntersecting]);
const Element = loaded ? lazy(iconMap[icon]) : null;
const width = `${size}px`;
const height = `${size}px`;
const colors = {
stroke: stroke ? colorMap[stroke] : undefined,
color: customColor ? customColor : color ? colorMap[color] : undefined,
};
return (
<div ref={setRef} style={{ width, height }}>
{loaded && Element ? (
<Suspense fallback={<div style={{ width, height }} />}>
<Element size={size} {...otherProps} {...colors} />
</Suspense>
) : (
<div style={{ width, height }} />
)}
</div>
);
};
Intersection Observer Hook:
import { useEffect, useState } from "react";
export const useIntersectionObserver = (options: IntersectionObserverInit) => {
const [isIntersecting, setIsIntersecting] = useState(false);
const [ref, setRef] = useState<HTMLElement | null>(null);
useEffect(() => {
if (!ref) return;
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIsIntersecting(true);
observer.disconnect();
}
}, options);
observer.observe(ref);
return () => observer.disconnect();
}, [ref, options]);
return { isIntersecting, setRef };
};
Then, in ./icon-exports/AwsLambda.tsx
, you'd have:
export { AwsLambda } from "../../../../assets/generated/icons";
Creating this indirection will allow Vite to import only the AwsLambda into its own JS file and render it only when needed.