-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vite not tree-shaking dynamic imports #14145
Comments
Ah, I guess this is happening because Vite replaces const { export3 } = await import('./not-tree-shaken.js');
// into
const { export3 } = await __vitePreload(() => import('./not-tree-shaken.js'), []); Then, rollup fails to analyze this. |
This comment was marked as duplicate.
This comment was marked as duplicate.
@sapphi-red , I see you added the "has workaround" label. Are you aware of a workaround? |
This workaround (#11080 (comment)) should still work. |
Thanks for the link. Unfortunately that workaround is too unwieldy for my use case. I'm using a |
You don't need to use |
Certainly true, though in our monorepo we enforce importing from the canonical main files for structure/API hygiene. |
Hi, I'm coming from this issue rollup/rollup#4951 and I cannot import dynamic component from a react library:
This will break tree-shaking and imports whole library.. I have to import and re-export my component to use it without importing whole library.
Then I can use lazy / await import
Any idea if it's possible since this PR was merged ? I don't know if there is any point in re-exporting to use Suspense/Lazy instead of a static import. |
I faced a similar issue where Vite wasn't tree-shaking dynamically imported modules, particularly when using a design system with a barrel file that imported all components. Even if only one component was used, all components were included in the bundle. I solved this by creating a custom Vite plugin to handle the imports more efficiently. Here’s how I resolved the issue: ProblemUsing a barrel file for components resulted in all components being included in the final bundle, even if only one component was used. For example, with the following barrel file: // packages/design-system/src/components/index.ts
export * from './Badge';
export * from './Button';
export * from './Checkbox';
export * from './Chip';
export * from './Divider';
// ...other components Even if only one component was imported and used, all components were bundled. // only one component is used
import { Button } from 'design-system'
// vite bundle was loaded all components exported like below
export * from "/@fs/path/design-system/src/components/Badge/index.ts";
export * from "/@fs/path/design-system/src/components/Button/index.ts";
export * from "/@fs/path/design-system/src/components/Checkbox/index.ts";
export * from "/@fs/path/design-system/src/components/Chip/index.ts";
export * from "/@fs/path/design-system/src/components/Divider/index.ts";
// ...other components SolutionTo resolve this, I created a custom Vite plugin in the vite.config.ts file. This plugin dynamically imports only the necessary components. Here’s the full configuration: // vite.config.ts
import legacy from '@vitejs/plugin-legacy';
import react from '@vitejs/plugin-react-swc';
import fs from 'node:fs';
import path from 'node:path';
import { defineConfig, type Plugin } from 'vite';
import svgr from 'vite-plugin-svgr';
// Read all component directories
const componentsDir = path.resolve(__dirname, '../../packages/design-system/src/components');
const componentNames = fs
.readdirSync(componentsDir)
.filter((name) => fs.lstatSync(path.join(componentsDir, name)).isDirectory());
const componentAlias = componentNames.reduce((acc, name) => {
acc[`design-system/${name}`] = path.join(componentsDir, name, `${name}.tsx`);
return acc;
}, {} as Record<string, string>);
function designSystemPlugin(): Plugin {
return {
name: 'design-system-plugin',
enforce: 'pre',
resolveId(source: string) {
if (source === 'design-system') {
return source;
}
return null;
},
// I used 'lazy' import for React, but you can use 'defineAsyncComponent' if you are using Vue.js
load(id: string) {
if (id === 'design-system') {
const imports = componentNames.map(
(name) => `
export const ${name} = lazy(() => import('${componentsDir}/${name}/${name}').then((module) => ({ default: module.${name} })));\n`,
);
return `import { lazy } from 'react';\n${imports.join('\n')}`;
}
return null;
},
};
}
export default defineConfig({
plugins: [
react(),
designSystemPlugin(),
],
resolve: {
alias: {
...componentAlias,
},
},
build: {
commonjsOptions: {
include: [/design-system/, /node_modules/],
},
target: 'esnext',
rollupOptions: {
plugins: [],
output: {
manualChunks(id) {
if (id.includes('design-system')) {
for (const component of componentNames) {
if (id.includes(component)) {
return `design-system/components/${component}/${component}.tsx`;
}
}
}
},
},
},
},
}); Explanation
This solution ensures that only the necessary components are included in the final bundle, achieving proper tree-shaking for dynamically imported modules in Vite. Final Bundled ResultThe final bundled result in Vite was as follows: // final bundled result in vite was like below
import __vite__cjsImport0_react from "/node_modules/.vite/deps/react.js?v=fc7ff56c";
const lazy = __vite__cjsImport0_react["lazy"];
export const Badge = lazy(()=>import("/@fs/path/design-system/src/components/Badge/Badge.tsx").then((module)=>({
default: module.Badge
})));
export const Button = lazy(()=>import("/@fs/path/design-system/src/components/Button/Button.tsx").then((module)=>({
default: module.Button
})));
export const Checkbox = lazy(()=>import("/@fs/path/design-system/src/components/Checkbox/Checkbox.tsx").then((module)=>({
default: module.Checkbox
})));
export const Chip = lazy(()=>import("/@fs/path/design-system/src/components/Chip/Chip.tsx").then((module)=>({
default: module.Chip
})));
export const Divider = lazy(()=>import("/@fs/path/design-system/src/components/Divider/Divider.tsx").then((module)=>({
default: module.Divider
})));
// ...other components |
Describe the bug
Vite 4.4.9 is not tree-shaking the contents of dynamically-imported modules:
In the above example, even though only export1 is imported, both export1 and export2 will be bundled by Vite.
If you use a static import, export2 is correctly tree-shaken from the final bundle:
Rollup has supported dynamic import tree-shaking since 3.21.0 (description of feature, PR), and Vite 4.4.9 is using Rollup 3.28.0, so Vite should also support it.
Vite #11080 was closed saying "rollup now supports this," but the repro link was not a Vite repro, but a vanilla Rollup repro. I attached a vanilla Vite repro, and here is a Vite Vue TS repro.
Finally, here is a repro of Rollup correctly tree-shaking the dynamic import.
Reproduction
https://stackblitz.com/edit/vitejs-vite-z6x1lz?file=main.js
Steps to reproduce
npm run dev
System Info
Used Package Manager
npm
Logs
No response
Validations
The text was updated successfully, but these errors were encountered: