Fix style injection (#2011)
This commit is contained in:
parent
4436592d22
commit
50f3b8d7ec
11 changed files with 101 additions and 10 deletions
5
.changeset/warm-students-melt.md
Normal file
5
.changeset/warm-students-melt.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Bugfix: improve style and script injection for partial pages
|
|
@ -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]);
|
const lastToFirst = Object.entries(pos).sort((a, b) => b[1] - a[1]);
|
||||||
lastToFirst.forEach(([name, i]) => {
|
lastToFirst.forEach(([name, i]) => {
|
||||||
if (i === -1) {
|
if (i === -1) {
|
||||||
// TODO: warn on missing tag? Is this an HTML partial?
|
// if page didn’t generate <head> or <body>, guess
|
||||||
return;
|
if (name === 'head-prepend' || name === 'head') i = 0;
|
||||||
|
if (name === 'body-prepend' || name === 'body') i = html.length;
|
||||||
}
|
}
|
||||||
let selected = tags.filter(({ injectTo }) => {
|
let selected = tags.filter(({ injectTo }) => {
|
||||||
if (name === 'head-prepend' && !injectTo) {
|
if (name === 'head-prepend' && !injectTo) {
|
||||||
|
|
|
@ -225,11 +225,6 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
|
||||||
|
|
||||||
let html = await renderPage(result, Component, pageProps, null);
|
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
|
// inject tags
|
||||||
const tags: vite.HtmlTagDescriptor[] = [];
|
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);
|
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;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ export async function renderComponent(result: SSRResult, displayName: string, Co
|
||||||
const probableRendererNames = guessRenderers(metadata.componentUrl);
|
const probableRendererNames = guessRenderers(metadata.componentUrl);
|
||||||
|
|
||||||
if (Array.isArray(renderers) && renderers.length === 0 && typeof Component !== 'string') {
|
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.
|
There are no \`renderers\` set in your \`astro.config.mjs\` file.
|
||||||
Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '`'))}?`;
|
Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '`'))}?`;
|
||||||
|
@ -384,7 +384,13 @@ export async function renderPage(result: SSRResult, Component: AstroComponentFac
|
||||||
if (needsHydrationStyles) {
|
if (needsHydrationStyles) {
|
||||||
styles.push(renderElement('style', { props: { 'astro-style': true }, children: 'astro-root, astro-fragment { display: contents; }' }));
|
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>) {
|
export async function renderAstroComponent(component: InstanceType<typeof AstroComponent>) {
|
||||||
|
|
41
packages/astro/test/astro-partial-html.test.js
Normal file
41
packages/astro/test/astro-partial-html.test.js
Normal file
|
@ -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;
|
||||||
|
});
|
||||||
|
});
|
3
packages/astro/test/fixtures/astro-partial-html/src/components/Component.css
vendored
Normal file
3
packages/astro/test/fixtures/astro-partial-html/src/components/Component.css
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
h1 {
|
||||||
|
color: red;
|
||||||
|
}
|
6
packages/astro/test/fixtures/astro-partial-html/src/components/Component.jsx
vendored
Normal file
6
packages/astro/test/fixtures/astro-partial-html/src/components/Component.jsx
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './Component.css';
|
||||||
|
|
||||||
|
export default function({ children }) {
|
||||||
|
return (<>{children}</>);
|
||||||
|
}
|
1
packages/astro/test/fixtures/astro-partial-html/src/components/Layout.astro
vendored
Normal file
1
packages/astro/test/fixtures/astro-partial-html/src/components/Layout.astro
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<slot />
|
15
packages/astro/test/fixtures/astro-partial-html/src/pages/astro.astro
vendored
Normal file
15
packages/astro/test/fixtures/astro-partial-html/src/pages/astro.astro
vendored
Normal file
|
@ -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>
|
12
packages/astro/test/fixtures/astro-partial-html/src/pages/jsx.astro
vendored
Normal file
12
packages/astro/test/fixtures/astro-partial-html/src/pages/jsx.astro
vendored
Normal file
|
@ -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>
|
||||||
|
|
|
@ -60,7 +60,8 @@ export async function loadFixture(inlineConfig) {
|
||||||
build: (opts = {}) => build(config, { mode: 'development', logging: 'error', ...opts }),
|
build: (opts = {}) => build(config, { mode: 'development', logging: 'error', ...opts }),
|
||||||
startDevServer: async (opts = {}) => {
|
startDevServer: async (opts = {}) => {
|
||||||
const devServer = await dev(config, { logging: 'error', ...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;
|
return devServer;
|
||||||
},
|
},
|
||||||
config,
|
config,
|
||||||
|
|
Loading…
Add table
Reference in a new issue