diff --git a/.changeset/calm-hotels-kiss.md b/.changeset/calm-hotels-kiss.md new file mode 100644 index 0000000000..4b565fd9e3 --- /dev/null +++ b/.changeset/calm-hotels-kiss.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +improves `client:only` error message diff --git a/packages/astro/src/runtime/server/render/component.ts b/packages/astro/src/runtime/server/render/component.ts index b74fac9a64..70224ce9d4 100644 --- a/packages/astro/src/runtime/server/render/component.ts +++ b/packages/astro/src/runtime/server/render/component.ts @@ -32,6 +32,7 @@ import { formatList, internalSpreadAttributes, renderElement, voidElementNames } const needsHeadRenderingSymbol = Symbol.for('astro.needsHeadRendering'); const rendererAliases = new Map([['solid', 'solid-js']]); +const clientOnlyValues = new Set(['solid-js', 'react', 'preact', 'vue', 'svelte', 'lit']); function guessRenderers(componentUrl?: string): string[] { const extname = componentUrl?.split('.').pop(); @@ -80,7 +81,7 @@ async function renderFrameworkComponent( _props: Record, slots: any = {} ): Promise { - if (!Component && !_props['client:only']) { + if (!Component && "client:only" in _props === false) { throw new Error( `Unable to render ${displayName} because it is ${Component}!\nDid you forget to import the component or is it possible there is a typo?` ); @@ -164,13 +165,14 @@ async function renderFrameworkComponent( } else { // Attempt: use explicitly passed renderer name if (metadata.hydrateArgs) { - const passedName = metadata.hydrateArgs; - const rendererName = rendererAliases.has(passedName) - ? rendererAliases.get(passedName) - : passedName; - renderer = renderers.find( - ({ name }) => name === `@astrojs/${rendererName}` || name === rendererName - ); + const rendererName = rendererAliases.has(metadata.hydrateArgs) + ? rendererAliases.get(metadata.hydrateArgs) + : metadata.hydrateArgs; + if (clientOnlyValues.has(rendererName)) { + renderer = renderers.find( + ({ name }) => name === `@astrojs/${rendererName}` || name === rendererName + ); + } } // Attempt: user only has a single renderer, default to that if (!renderer && validRenderers.length === 1) { @@ -189,13 +191,34 @@ async function renderFrameworkComponent( // If no one claimed the renderer if (!renderer) { if (metadata.hydrate === 'only') { - throw new AstroError({ - ...AstroErrorData.NoClientOnlyHint, - message: AstroErrorData.NoClientOnlyHint.message(metadata.displayName), - hint: AstroErrorData.NoClientOnlyHint.hint( - probableRendererNames.map((r) => r.replace('@astrojs/', '')).join('|') - ), - }); + const rendererName = rendererAliases.has(metadata.hydrateArgs) + ? rendererAliases.get(metadata.hydrateArgs) + : metadata.hydrateArgs; + if (clientOnlyValues.has(rendererName)) { + // throw an error if provide correct client:only directive but not find the renderer + const plural = validRenderers.length > 1; + throw new AstroError({ + ...AstroErrorData.NoMatchingRenderer, + message: AstroErrorData.NoMatchingRenderer.message( + metadata.displayName, + metadata?.componentUrl?.split('.').pop(), + plural, + validRenderers.length + ), + hint: AstroErrorData.NoMatchingRenderer.hint( + formatList(probableRendererNames.map((r) => '`' + r + '`')) + ), + }); + } else { + // throw an error if an invalid hydration directive was provided + throw new AstroError({ + ...AstroErrorData.NoClientOnlyHint, + message: AstroErrorData.NoClientOnlyHint.message(metadata.displayName), + hint: AstroErrorData.NoClientOnlyHint.hint( + probableRendererNames.map((r) => r.replace('@astrojs/', '')).join('|') + ), + }); + } } else if (typeof Component !== 'string') { const matchingRenderers = validRenderers.filter((r) => probableRendererNames.includes(r.name) @@ -241,6 +264,15 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr } } else { if (metadata.hydrate === 'only') { + const rendererName = rendererAliases.has(metadata.hydrateArgs) + ? rendererAliases.get(metadata.hydrateArgs) + : metadata.hydrateArgs; + if (!clientOnlyValues.has(rendererName)) { + // warning if provide incorrect client:only directive but find the renderer by guess + console.warn( + `The client:only directive for ${metadata.displayName} is not recognized. The renderer ${renderer.name} will be used. If you intended to use a different renderer, please provide a valid client:only directive.` + ); + } html = await renderSlotToString(result, slots?.fallback); } else { const componentRenderStartTime = performance.now();