From 50f3b8d7ec6205340e4205db1227c248ac9884c1 Mon Sep 17 00:00:00 2001 From: Drew Powers <1369770+drwpow@users.noreply.github.com> Date: Mon, 29 Nov 2021 12:33:40 -0700 Subject: [PATCH] Fix style injection (#2011) --- .changeset/warm-students-melt.md | 5 +++ packages/astro/src/core/ssr/html.ts | 5 ++- packages/astro/src/core/ssr/index.ts | 10 ++--- packages/astro/src/runtime/server/index.ts | 10 ++++- .../astro/test/astro-partial-html.test.js | 41 +++++++++++++++++++ .../src/components/Component.css | 3 ++ .../src/components/Component.jsx | 6 +++ .../src/components/Layout.astro | 1 + .../astro-partial-html/src/pages/astro.astro | 15 +++++++ .../astro-partial-html/src/pages/jsx.astro | 12 ++++++ packages/astro/test/test-utils.js | 3 +- 11 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 .changeset/warm-students-melt.md create mode 100644 packages/astro/test/astro-partial-html.test.js create mode 100644 packages/astro/test/fixtures/astro-partial-html/src/components/Component.css create mode 100644 packages/astro/test/fixtures/astro-partial-html/src/components/Component.jsx create mode 100644 packages/astro/test/fixtures/astro-partial-html/src/components/Layout.astro create mode 100644 packages/astro/test/fixtures/astro-partial-html/src/pages/astro.astro create mode 100644 packages/astro/test/fixtures/astro-partial-html/src/pages/jsx.astro diff --git a/.changeset/warm-students-melt.md b/.changeset/warm-students-melt.md new file mode 100644 index 0000000000..89580014b7 --- /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 faa6f055b4..aa5a3d8478 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 <head> or <body>, 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 0812c5ed36..5aad47b4bb 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 <!doctype html> if missing (TODO: is a more robust check needed for comments, etc.?) - if (!/<!doctype html/i.test(html)) { - html = '<!DOCTYPE html>\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 <!doctype html> if missing (TODO: is a more robust check needed for comments, etc.?) + if (!/<!doctype html/i.test(html)) { + html = '<!DOCTYPE html>\n' + html; + } + return html; } diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 640c6aa6c8..37a7dc2e22 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('</head>', styles.join('\n') + scripts.join('\n') + '</head>'); + + // inject styles & scripts at end of <head> + let headPos = template.indexOf('</head>'); + if (headPos === -1) { + return styles.join('\n') + scripts.join('\n') + template; // if no </head>, prepend styles & scripts + } + return template.substring(0, headPos) + styles.join('\n') + scripts.join('\n') + template.substring(headPos); } export async function renderAstroComponent(component: InstanceType<typeof AstroComponent>) { 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 0000000000..45ad620389 --- /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(/^<!DOCTYPE html/); + + // test 2: correct CSS present + const css = $('style[astro-style]').html(); + expect(css).to.match(/\.astro-[^{]+{color:red;}/); + }); + + it('injects framework styles', async () => { + const html = await fixture.fetch('/jsx').then((res) => res.text()); + const $ = cheerio.load(html); + + // test 1: Doctype first + expect(html).to.match(/^<!DOCTYPE html/); + + // test 2: link tag present + const href = $('link[rel=stylesheet][data-astro-injected]').attr('href'); + expect(href).to.be.ok; + }); +}); diff --git a/packages/astro/test/fixtures/astro-partial-html/src/components/Component.css b/packages/astro/test/fixtures/astro-partial-html/src/components/Component.css new file mode 100644 index 0000000000..adc68fa6a4 --- /dev/null +++ b/packages/astro/test/fixtures/astro-partial-html/src/components/Component.css @@ -0,0 +1,3 @@ +h1 { + color: red; +} diff --git a/packages/astro/test/fixtures/astro-partial-html/src/components/Component.jsx b/packages/astro/test/fixtures/astro-partial-html/src/components/Component.jsx new file mode 100644 index 0000000000..63a06c0188 --- /dev/null +++ b/packages/astro/test/fixtures/astro-partial-html/src/components/Component.jsx @@ -0,0 +1,6 @@ +import React from 'react'; +import './Component.css'; + +export default function({ children }) { + return (<>{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 0000000000..4fa864ce7a --- /dev/null +++ b/packages/astro/test/fixtures/astro-partial-html/src/components/Layout.astro @@ -0,0 +1 @@ +<slot /> 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 0000000000..2384cefec1 --- /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 <Layout> to be the very first element +--- + +<Layout> + <h1>Astro Partial HTML</h1> +</Layout> + +<style> + h1 { + color: red; + } +</style> 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 0000000000..b5a34f4ced --- /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 <Layout> to be the very first element +--- +<Layout> + <Component> + <h1>JSX Partial HTML</h1> + </Component> +</Layout> + diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index cf3f947b8e..bc57ee3a50 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,