diff --git a/.changeset/warm-students-melt.md b/.changeset/warm-students-melt.md new file mode 100644 index 000000000..89580014b --- /dev/null +++ b/.changeset/warm-students-melt.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Bugfix: improve style and script injection for partial pages diff --git a/packages/astro/src/core/ssr/html.ts b/packages/astro/src/core/ssr/html.ts index faa6f055b..aa5a3d847 100644 --- a/packages/astro/src/core/ssr/html.ts +++ b/packages/astro/src/core/ssr/html.ts @@ -27,8 +27,9 @@ export function injectTags(html: string, tags: vite.HtmlTagDescriptor[]): string const lastToFirst = Object.entries(pos).sort((a, b) => b[1] - a[1]); lastToFirst.forEach(([name, i]) => { if (i === -1) { - // TODO: warn on missing tag? Is this an HTML partial? - return; + // if page didn’t generate or , guess + if (name === 'head-prepend' || name === 'head') i = 0; + if (name === 'body-prepend' || name === 'body') i = html.length; } let selected = tags.filter(({ injectTo }) => { if (name === 'head-prepend' && !injectTo) { diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 0812c5ed3..5aad47b4b 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -225,11 +225,6 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO let html = await renderPage(result, Component, pageProps, null); - // inject if missing (TODO: is a more robust check needed for comments, etc.?) - if (!/\n' + html; - } - // inject tags const tags: vite.HtmlTagDescriptor[] = []; @@ -274,6 +269,11 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO html = await viteServer.transformIndexHtml(viteifyURL(filePath), html, pathname); } + // inject if missing (TODO: is a more robust check needed for comments, etc.?) + if (!/\n' + html; + } + return html; } diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 640c6aa6c..37a7dc2e2 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -146,7 +146,7 @@ export async function renderComponent(result: SSRResult, displayName: string, Co const probableRendererNames = guessRenderers(metadata.componentUrl); if (Array.isArray(renderers) && renderers.length === 0 && typeof Component !== 'string') { - const message = `Unable to render ${metadata.displayName}! + const message = `Unable to render ${metadata.displayName}! There are no \`renderers\` set in your \`astro.config.mjs\` file. Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '`'))}?`; @@ -384,7 +384,13 @@ export async function renderPage(result: SSRResult, Component: AstroComponentFac if (needsHydrationStyles) { styles.push(renderElement('style', { props: { 'astro-style': true }, children: 'astro-root, astro-fragment { display: contents; }' })); } - return template.replace('', styles.join('\n') + scripts.join('\n') + ''); + + // inject styles & scripts at end of + let headPos = template.indexOf(''); + if (headPos === -1) { + return styles.join('\n') + scripts.join('\n') + template; // if no , prepend styles & scripts + } + return template.substring(0, headPos) + styles.join('\n') + scripts.join('\n') + template.substring(headPos); } export async function renderAstroComponent(component: InstanceType) { diff --git a/packages/astro/test/astro-partial-html.test.js b/packages/astro/test/astro-partial-html.test.js new file mode 100644 index 000000000..45ad62038 --- /dev/null +++ b/packages/astro/test/astro-partial-html.test.js @@ -0,0 +1,41 @@ +import { expect } from 'chai'; +import cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Partial HTML ', async () => { + let fixture; + let devServer; + + before(async () => { + fixture = await loadFixture({ projectRoot: './fixtures/astro-partial-html/' }); + devServer = await fixture.startDevServer(); + }); + + after(async () => { + devServer && devServer.stop(); + }); + + it('injects Astro styles and scripts', async () => { + const html = await fixture.fetch('/astro').then((res) => res.text()); + const $ = cheerio.load(html); + + // test 1: Doctype first + expect(html).to.match(/^ { + const html = await fixture.fetch('/jsx').then((res) => res.text()); + const $ = cheerio.load(html); + + // test 1: Doctype first + expect(html).to.match(/^{children}); +} diff --git a/packages/astro/test/fixtures/astro-partial-html/src/components/Layout.astro b/packages/astro/test/fixtures/astro-partial-html/src/components/Layout.astro new file mode 100644 index 000000000..4fa864ce7 --- /dev/null +++ b/packages/astro/test/fixtures/astro-partial-html/src/components/Layout.astro @@ -0,0 +1 @@ + diff --git a/packages/astro/test/fixtures/astro-partial-html/src/pages/astro.astro b/packages/astro/test/fixtures/astro-partial-html/src/pages/astro.astro new file mode 100644 index 000000000..2384cefec --- /dev/null +++ b/packages/astro/test/fixtures/astro-partial-html/src/pages/astro.astro @@ -0,0 +1,15 @@ +--- +import Layout from '../components/Layout.astro'; + +// note: this test requires to be the very first element +--- + + +

Astro Partial HTML

+
+ + diff --git a/packages/astro/test/fixtures/astro-partial-html/src/pages/jsx.astro b/packages/astro/test/fixtures/astro-partial-html/src/pages/jsx.astro new file mode 100644 index 000000000..b5a34f4ce --- /dev/null +++ b/packages/astro/test/fixtures/astro-partial-html/src/pages/jsx.astro @@ -0,0 +1,12 @@ +--- +import Layout from '../components/Layout.astro'; +import Component from '../components/Component.jsx'; + +// note: this test requires to be the very first element +--- + + +

JSX Partial HTML

+
+
+ diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index cf3f947b8..bc57ee3a5 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -60,7 +60,8 @@ export async function loadFixture(inlineConfig) { build: (opts = {}) => build(config, { mode: 'development', logging: 'error', ...opts }), startDevServer: async (opts = {}) => { const devServer = await dev(config, { logging: 'error', ...opts }); - inlineConfig.devOptions.port = devServer.port; // update port + config.devOptions.port = devServer.port; // update port + inlineConfig.devOptions.port = devServer.port; return devServer; }, config,