-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Treeshake exported client components that are not imported (#6527)
* Treeshake exported client components that are not imported * Fix plugin name * Fix mdx test
- Loading branch information
Showing
13 changed files
with
191 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'astro': patch | ||
--- | ||
|
||
Treeshake exported client components that are not imported |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
packages/astro/src/core/build/plugins/plugin-component-entry.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import type { Plugin as VitePlugin } from 'vite'; | ||
import type { BuildInternals } from '../internal.js'; | ||
import type { AstroBuildPlugin } from '../plugin.js'; | ||
|
||
const astroEntryPrefix = '\0astro-entry:'; | ||
|
||
/** | ||
* When adding hydrated or client:only components as Rollup inputs, sometimes we're not using all | ||
* of the export names, e.g. `import { Counter } from './ManyComponents.jsx'`. This plugin proxies | ||
* entries to re-export only the names the user is using. | ||
*/ | ||
export function vitePluginComponentEntry(internals: BuildInternals): VitePlugin { | ||
const componentToExportNames: Map<string, string[]> = new Map(); | ||
|
||
mergeComponentExportNames(internals.discoveredHydratedComponents); | ||
mergeComponentExportNames(internals.discoveredClientOnlyComponents); | ||
|
||
for (const [componentId, exportNames] of componentToExportNames) { | ||
// If one of the imports has a dot, it's a namespaced import, e.g. `import * as foo from 'foo'` | ||
// and `<foo.Counter />`, in which case we re-export `foo` entirely and we don't need to handle | ||
// it in this plugin as it's default behaviour from Rollup. | ||
if (exportNames.some((name) => name.includes('.') || name === '*')) { | ||
componentToExportNames.delete(componentId); | ||
} else { | ||
componentToExportNames.set(componentId, Array.from(new Set(exportNames))); | ||
} | ||
} | ||
|
||
function mergeComponentExportNames(components: Map<string, string[]>) { | ||
for (const [componentId, exportNames] of components) { | ||
if (componentToExportNames.has(componentId)) { | ||
componentToExportNames.get(componentId)?.push(...exportNames); | ||
} else { | ||
componentToExportNames.set(componentId, exportNames); | ||
} | ||
} | ||
} | ||
|
||
return { | ||
name: '@astro/plugin-component-entry', | ||
enforce: 'pre', | ||
config(config) { | ||
const rollupInput = config.build?.rollupOptions?.input; | ||
// Astro passes an array of inputs by default. Even though other Vite plugins could | ||
// change this to an object, it shouldn't happen in practice as our plugin runs first. | ||
if (Array.isArray(rollupInput)) { | ||
// @ts-expect-error input is definitely defined here, but typescript thinks it doesn't | ||
config.build.rollupOptions.input = rollupInput.map((id) => { | ||
if (componentToExportNames.has(id)) { | ||
return astroEntryPrefix + id; | ||
} else { | ||
return id; | ||
} | ||
}); | ||
} | ||
}, | ||
async resolveId(id) { | ||
if (id.startsWith(astroEntryPrefix)) { | ||
return id; | ||
} | ||
}, | ||
async load(id) { | ||
if (id.startsWith(astroEntryPrefix)) { | ||
const componentId = id.slice(astroEntryPrefix.length); | ||
const exportNames = componentToExportNames.get(componentId); | ||
if (exportNames) { | ||
return `export { ${exportNames.join(', ')} } from ${JSON.stringify(componentId)}`; | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
|
||
export function normalizeEntryId(id: string): string { | ||
return id.startsWith(astroEntryPrefix) ? id.slice(astroEntryPrefix.length) : id; | ||
} | ||
|
||
export function pluginComponentEntry(internals: BuildInternals): AstroBuildPlugin { | ||
return { | ||
build: 'client', | ||
hooks: { | ||
'build:before': () => { | ||
return { | ||
vitePlugin: vitePluginComponentEntry(internals), | ||
}; | ||
}, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { expect } from 'chai'; | ||
import { loadFixture } from './test-utils.js'; | ||
|
||
describe('Component bundling', () => { | ||
let fixture; | ||
|
||
before(async () => { | ||
fixture = await loadFixture({ root: './fixtures/astro-component-bundling/' }); | ||
await fixture.build(); | ||
}); | ||
|
||
it('should treeshake FooComponent', async () => { | ||
const astroChunkDir = await fixture.readdir('/_astro'); | ||
const manyComponentsChunkName = astroChunkDir.find((chunk) => | ||
chunk.startsWith('ManyComponents') | ||
); | ||
const manyComponentsChunkContent = await fixture.readFile(`/_astro/${manyComponentsChunkName}`); | ||
expect(manyComponentsChunkContent).to.not.include('FooComponent'); | ||
}); | ||
}); |
7 changes: 7 additions & 0 deletions
7
packages/astro/test/fixtures/astro-component-bundling/astro.config.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { defineConfig } from 'astro/config'; | ||
import react from '@astrojs/react'; | ||
|
||
// https://astro.build/config | ||
export default defineConfig({ | ||
integrations: [react()], | ||
}); |
11 changes: 11 additions & 0 deletions
11
packages/astro/test/fixtures/astro-component-bundling/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "@test/astro-component-bundling", | ||
"version": "0.0.0", | ||
"private": true, | ||
"dependencies": { | ||
"@astrojs/react": "workspace:*", | ||
"astro": "workspace:*", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0" | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
packages/astro/test/fixtures/astro-component-bundling/src/components/ManyComponents.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const FooComponent = () => <div>Foo</div>; | ||
export const BarComponent = () => <div>Bar</div>; | ||
export const BazComponent = () => <div>Baz</div>; |
10 changes: 10 additions & 0 deletions
10
packages/astro/test/fixtures/astro-component-bundling/src/pages/index.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
import { BarComponent, BazComponent } from '../components/ManyComponents.jsx' | ||
--- | ||
<html> | ||
<head><title>Component bundling</title></head> | ||
<body> | ||
<BarComponent client:idle /> | ||
<BazComponent client:only="react" /> | ||
</body> | ||
</html> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.