-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat: add server islands support for MDX
#12574
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
Changes from all commits
5c1def6
e63f5a2
ff01d35
14ba50f
18449c8
585dc3c
4ed2af3
228354d
0d8271a
502ad17
bc0b59c
84872bc
41e55b2
eec8d96
4bcf13d
d3383ec
1d6f204
ee584d1
dbe12a9
b072817
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'astro': patch | ||
| --- | ||
|
|
||
| Allows using server islands in mdx files |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| import Island from '../components/Island.astro'; | ||
|
|
||
| {/* empty div is needed, otherwise island script is injected in the head */} | ||
| <div></div> | ||
| <Island server:defer /> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,52 +31,50 @@ export function vitePluginServerIslands({ settings, logger }: AstroPluginOptions | |||||||||||||||||||
| } | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| transform(_code, id) { | ||||||||||||||||||||
| if (id.endsWith('.astro')) { | ||||||||||||||||||||
| const info = this.getModuleInfo(id); | ||||||||||||||||||||
| if (info?.meta) { | ||||||||||||||||||||
| const astro = info.meta.astro as AstroPluginMetadata['astro'] | undefined; | ||||||||||||||||||||
| if (astro?.serverComponents.length) { | ||||||||||||||||||||
| for (const comp of astro.serverComponents) { | ||||||||||||||||||||
| if (!settings.serverIslandNameMap.has(comp.resolvedPath)) { | ||||||||||||||||||||
| if (!settings.adapter) { | ||||||||||||||||||||
| logger.error( | ||||||||||||||||||||
| 'islands', | ||||||||||||||||||||
| 'You tried to render a server island without an adapter added to your project. An adapter is required to use the `server:defer` attribute on any component. Your project will fail to build unless you add an adapter or remove the attribute.', | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| // We run the transform for all file extensions to support transformed files, eg. mdx | ||||||||||||||||||||
| const info = this.getModuleInfo(id); | ||||||||||||||||||||
| if (!info?.meta?.astro) return; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let name = comp.localName; | ||||||||||||||||||||
| let idx = 1; | ||||||||||||||||||||
| const astro = info.meta.astro as AstroPluginMetadata['astro']; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| while (true) { | ||||||||||||||||||||
| // Name not taken, let's use it. | ||||||||||||||||||||
| if (!settings.serverIslandMap.has(name)) { | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| // Increment a number onto the name: Avatar -> Avatar1 | ||||||||||||||||||||
| name += idx++; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| for (const comp of astro.serverComponents) { | ||||||||||||||||||||
| if (!settings.serverIslandNameMap.has(comp.resolvedPath)) { | ||||||||||||||||||||
| if (!settings.adapter) { | ||||||||||||||||||||
| logger.error( | ||||||||||||||||||||
| 'islands', | ||||||||||||||||||||
| 'You tried to render a server island without an adapter added to your project. An adapter is required to use the `server:defer` attribute on any component. Your project will fail to build unless you add an adapter or remove the attribute.', | ||||||||||||||||||||
| ); | ||||||||||||||||||||
|
Comment on lines
+43
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We should not process the island, I think. Make sure to print the name of the component, so the users know which component island is failing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should stop the processing. We have this in a few places IIRC where we only warn if stuff is going to break if no adapter is added, and only break during build. I think that applies here too |
||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let name = comp.localName; | ||||||||||||||||||||
| let idx = 1; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| while (true) { | ||||||||||||||||||||
| // Name not taken, let's use it. | ||||||||||||||||||||
| if (!settings.serverIslandMap.has(name)) { | ||||||||||||||||||||
| break; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| // Increment a number onto the name: Avatar -> Avatar1 | ||||||||||||||||||||
| name += idx++; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Append the name map, for prod | ||||||||||||||||||||
| settings.serverIslandNameMap.set(comp.resolvedPath, name); | ||||||||||||||||||||
| // Append the name map, for prod | ||||||||||||||||||||
| settings.serverIslandNameMap.set(comp.resolvedPath, name); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| settings.serverIslandMap.set(name, () => { | ||||||||||||||||||||
| return viteServer?.ssrLoadModule(comp.resolvedPath) as any; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| settings.serverIslandMap.set(name, () => { | ||||||||||||||||||||
| return viteServer?.ssrLoadModule(comp.resolvedPath) as any; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Build mode | ||||||||||||||||||||
| if (command === 'build') { | ||||||||||||||||||||
| let referenceId = this.emitFile({ | ||||||||||||||||||||
| type: 'chunk', | ||||||||||||||||||||
| id: comp.specifier, | ||||||||||||||||||||
| importer: id, | ||||||||||||||||||||
| name: comp.localName, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| // Build mode | ||||||||||||||||||||
| if (command === 'build') { | ||||||||||||||||||||
| let referenceId = this.emitFile({ | ||||||||||||||||||||
| type: 'chunk', | ||||||||||||||||||||
| id: comp.specifier, | ||||||||||||||||||||
| importer: id, | ||||||||||||||||||||
| name: comp.localName, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| referenceIdMap.set(comp.resolvedPath, referenceId); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| referenceIdMap.set(comp.resolvedPath, referenceId); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,10 +4,10 @@ import { | |
| HTMLString, | ||
| escapeHTML, | ||
| markHTMLString, | ||
| renderToString, | ||
| spreadAttributes, | ||
| voidElementNames, | ||
| } from './index.js'; | ||
| import { isAstroComponentFactory } from './render/astro/factory.js'; | ||
| import { renderComponentToString } from './render/component.js'; | ||
|
|
||
| const ClientOnlyPlaceholder = 'astro-client-only'; | ||
|
|
@@ -54,7 +54,7 @@ Did you forget to import the component or is it possible there is a typo?`); | |
| } | ||
| case (vnode.type as any) === Symbol.for('astro:fragment'): | ||
| return renderJSX(result, vnode.props.children); | ||
| case (vnode.type as any).isAstroComponentFactory: { | ||
| case isAstroComponentFactory(vnode.type): { | ||
| let props: Record<string, any> = {}; | ||
| let slots: Record<string, any> = {}; | ||
| for (const [key, value] of Object.entries(vnode.props ?? {})) { | ||
|
|
@@ -64,10 +64,14 @@ Did you forget to import the component or is it possible there is a typo?`); | |
| props[key] = value; | ||
| } | ||
| } | ||
| const str = await renderToString(result, vnode.type as any, props, slots); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was calling the component factory as is. Instead, we want it to go through |
||
| if (str instanceof Response) { | ||
| throw str; | ||
| } | ||
| // We don't use renderToString because it doesn't contain the server island script handling | ||
| const str = await renderComponentToString( | ||
| result, | ||
| vnode.type.name, | ||
| vnode.type, | ||
| props, | ||
| slots, | ||
| ); | ||
| const html = markHTMLString(str); | ||
| return html; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,8 @@ import { getRoutePrerenderOption } from '../core/routing/manifest/prerender.js'; | |
| import { isEndpoint, isPage } from '../core/util.js'; | ||
| import { normalizePath, rootRelativePath } from '../core/viteUtils.js'; | ||
| import type { AstroSettings, RoutesList } from '../types/astro.js'; | ||
| import type { PluginMetadata } from '../vite-plugin-astro/types.js'; | ||
| import { createDefaultAstroMetadata } from '../vite-plugin-astro/metadata.js'; | ||
|
|
||
| interface AstroPluginScannerOptions { | ||
| settings: AstroSettings; | ||
|
|
@@ -73,11 +75,11 @@ export default function astroScannerPlugin({ | |
| meta: { | ||
| ...meta, | ||
| astro: { | ||
| ...(meta.astro ?? { hydratedComponents: [], clientOnlyComponents: [], scripts: [] }), | ||
| ...(meta.astro ?? createDefaultAstroMetadata()), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that the server island plugin is running for all files, this was failing because the metadata was not initialized with |
||
| pageOptions: { | ||
| prerender: route.prerender, | ||
| }, | ||
| }, | ||
| } satisfies PluginMetadata['astro'], | ||
| }, | ||
| }; | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,14 @@ | ||
| import svelte from '@astrojs/svelte'; | ||
| import { defineConfig } from 'astro/config'; | ||
| import testAdapter from '../../../test-adapter.js'; | ||
| import mdx from '@astrojs/mdx'; | ||
|
|
||
| export default defineConfig({ | ||
| adapter: testAdapter(), | ||
| output: 'server', | ||
| integrations: [ | ||
| svelte() | ||
| svelte(), | ||
| mdx() | ||
| ], | ||
| }); | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| --- | ||
|
|
||
| import Island from '../components/Island.astro'; | ||
|
|
||
| <Island server:defer /> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was preventing retrieving server islands data from mdx files metadata