* Revert "Fixes duplicate head content (#995)"
This reverts commit 268a36f399
.
* Changeset
This commit is contained in:
parent
57b736211a
commit
b1959f0fed
16 changed files with 78 additions and 121 deletions
5
.changeset/eighty-walls-count.md
Normal file
5
.changeset/eighty-walls-count.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Reverts a change to head content that was breaking docs site
|
|
@ -662,7 +662,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
buffers[curr] += `h(__astro_slot_content, { name: ${attributes.slot} },`;
|
buffers[curr] += `h(__astro_slot_content, { name: ${attributes.slot} },`;
|
||||||
paren++;
|
paren++;
|
||||||
}
|
}
|
||||||
buffers[curr] += `h("${name}", ${generateAttributes(attributes)},`;
|
buffers[curr] += `h("${name}", ${generateAttributes(attributes)}`;
|
||||||
paren++;
|
paren++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -692,7 +692,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
buffers[curr] += `h(__astro_slot_content, { name: ${attributes.slot} },`;
|
buffers[curr] += `h(__astro_slot_content, { name: ${attributes.slot} },`;
|
||||||
paren++;
|
paren++;
|
||||||
}
|
}
|
||||||
buffers[curr] += `h(${componentName}, ${generateAttributes(attributes)},`;
|
buffers[curr] += `h(${componentName}, ${generateAttributes(attributes)}`;
|
||||||
paren++;
|
paren++;
|
||||||
return;
|
return;
|
||||||
} else if (!componentInfo && !isCustomElementTag(componentName)) {
|
} else if (!componentInfo && !isCustomElementTag(componentName)) {
|
||||||
|
|
|
@ -150,8 +150,8 @@ async function __render(props, ...children) {
|
||||||
value: props,
|
value: props,
|
||||||
enumerable: true
|
enumerable: true
|
||||||
},
|
},
|
||||||
pageCSS: {
|
css: {
|
||||||
value: (props[__astroContext] && props[__astroContext].pageCSS) || [],
|
value: (props[__astroInternal] && props[__astroInternal].css) || [],
|
||||||
enumerable: true
|
enumerable: true
|
||||||
},
|
},
|
||||||
isPage: {
|
isPage: {
|
||||||
|
@ -181,7 +181,6 @@ export async function __renderPage({request, children, props, css}) {
|
||||||
|
|
||||||
Object.defineProperty(props, __astroContext, {
|
Object.defineProperty(props, __astroContext, {
|
||||||
value: {
|
value: {
|
||||||
pageCSS: css,
|
|
||||||
request
|
request
|
||||||
},
|
},
|
||||||
writable: false,
|
writable: false,
|
||||||
|
@ -190,6 +189,7 @@ export async function __renderPage({request, children, props, css}) {
|
||||||
|
|
||||||
Object.defineProperty(props, __astroInternal, {
|
Object.defineProperty(props, __astroInternal, {
|
||||||
value: {
|
value: {
|
||||||
|
css,
|
||||||
isPage: true
|
isPage: true
|
||||||
},
|
},
|
||||||
writable: false,
|
writable: false,
|
||||||
|
|
|
@ -73,7 +73,7 @@ export default function (opts: TransformOptions): Transformer {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
type: 'Expression',
|
type: 'Expression',
|
||||||
codeChunks: ['Astro.pageCSS.map(css => (', '))'],
|
codeChunks: ['Astro.css.map(css => (', '))'],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: 'Element',
|
type: 'Element',
|
||||||
|
@ -162,15 +162,22 @@ export default function (opts: TransformOptions): Transformer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eoh.foundHeadOrHtmlElement || eoh.foundHeadAndBodyContent) {
|
const conditionalNode = {
|
||||||
const topLevelFragment = {
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
type: 'Expression',
|
||||||
|
codeChunks: ['Astro.isPage ? (', ') : null'],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
type: 'Fragment',
|
type: 'Fragment',
|
||||||
children,
|
children,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
eoh.append(topLevelFragment);
|
|
||||||
}
|
eoh.append(conditionalNode);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,21 @@
|
||||||
import type { TemplateNode } from '@astrojs/parser';
|
import type { TemplateNode } from '@astrojs/parser';
|
||||||
|
|
||||||
const beforeHeadElements = new Set(['!doctype', 'html']);
|
const validHeadElements = new Set(['!doctype', 'title', 'meta', 'link', 'style', 'script', 'noscript', 'base']);
|
||||||
const validHeadElements = new Set(['title', 'meta', 'link', 'style', 'script', 'noscript', 'base']);
|
|
||||||
|
|
||||||
export class EndOfHead {
|
export class EndOfHead {
|
||||||
private html: TemplateNode | null = null;
|
|
||||||
private head: TemplateNode | null = null;
|
private head: TemplateNode | null = null;
|
||||||
private firstNonHead: TemplateNode | null = null;
|
private firstNonHead: TemplateNode | null = null;
|
||||||
private parent: TemplateNode | null = null;
|
private parent: TemplateNode | null = null;
|
||||||
private stack: TemplateNode[] = [];
|
private stack: TemplateNode[] = [];
|
||||||
|
|
||||||
public foundHeadElements = false;
|
|
||||||
public foundBodyElements = false;
|
|
||||||
public append: (...node: TemplateNode[]) => void = () => void 0;
|
public append: (...node: TemplateNode[]) => void = () => void 0;
|
||||||
|
|
||||||
get found(): boolean {
|
get found(): boolean {
|
||||||
return !!(this.head || this.firstNonHead);
|
return !!(this.head || this.firstNonHead);
|
||||||
}
|
}
|
||||||
|
|
||||||
get foundHeadContent(): boolean {
|
|
||||||
return !!this.head || this.foundHeadElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
get foundHeadAndBodyContent(): boolean {
|
|
||||||
return this.foundHeadContent && this.foundBodyElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
get foundHeadOrHtmlElement(): boolean {
|
|
||||||
return !!(this.html || this.head);
|
|
||||||
}
|
|
||||||
|
|
||||||
enter(node: TemplateNode) {
|
enter(node: TemplateNode) {
|
||||||
const name = node.name ? node.name.toLowerCase() : null;
|
|
||||||
|
|
||||||
if (this.found) {
|
if (this.found) {
|
||||||
if (!validHeadElements.has(name)) {
|
|
||||||
if (node.type === 'Element') {
|
|
||||||
this.foundBodyElements = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +26,8 @@ export class EndOfHead {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const name = node.name.toLowerCase();
|
||||||
|
|
||||||
if (name === 'head') {
|
if (name === 'head') {
|
||||||
this.head = node;
|
this.head = node;
|
||||||
this.parent = this.stack[this.stack.length - 2];
|
this.parent = this.stack[this.stack.length - 2];
|
||||||
|
@ -56,24 +35,11 @@ export class EndOfHead {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip !doctype and html elements
|
|
||||||
if (beforeHeadElements.has(name)) {
|
|
||||||
if (name === 'html') {
|
|
||||||
this.html = node;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validHeadElements.has(name)) {
|
if (!validHeadElements.has(name)) {
|
||||||
if (node.type === 'Element') {
|
|
||||||
this.foundBodyElements = true;
|
|
||||||
}
|
|
||||||
this.firstNonHead = node;
|
this.firstNonHead = node;
|
||||||
this.parent = this.stack[this.stack.length - 2];
|
this.parent = this.stack[this.stack.length - 2];
|
||||||
this.append = this.prependToFirstNonHead;
|
this.append = this.prependToFirstNonHead;
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
this.foundHeadElements = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,38 @@
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { suite } from 'uvu';
|
import { suite } from 'uvu';
|
||||||
import * as assert from 'uvu/assert';
|
import * as assert from 'uvu/assert';
|
||||||
import { doc } from './test-utils.js';
|
import { loadConfig } from '#astro/config';
|
||||||
import { setup } from './helpers.js';
|
import { createRuntime } from '#astro/runtime';
|
||||||
|
|
||||||
const DType = suite('doctype');
|
const DType = suite('doctype');
|
||||||
|
|
||||||
setup(DType, './fixtures/astro-doctype');
|
let runtime, setupError;
|
||||||
|
|
||||||
DType('Automatically prepends the standards mode doctype', async ({ runtime }) => {
|
DType.before(async () => {
|
||||||
|
try {
|
||||||
|
const astroConfig = await loadConfig(fileURLToPath(new URL('./fixtures/astro-doctype', import.meta.url)));
|
||||||
|
|
||||||
|
const logging = {
|
||||||
|
level: 'error',
|
||||||
|
dest: process.stderr,
|
||||||
|
};
|
||||||
|
|
||||||
|
runtime = await createRuntime(astroConfig, { logging });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
setupError = err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DType.after(async () => {
|
||||||
|
(await runtime) && runtime.shutdown();
|
||||||
|
});
|
||||||
|
|
||||||
|
DType('No errors creating a runtime', () => {
|
||||||
|
assert.equal(setupError, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
DType('Automatically prepends the standards mode doctype', async () => {
|
||||||
const result = await runtime.load('/prepend');
|
const result = await runtime.load('/prepend');
|
||||||
assert.ok(!result.error, `build error: ${result.error}`);
|
assert.ok(!result.error, `build error: ${result.error}`);
|
||||||
|
|
||||||
|
@ -15,7 +40,7 @@ DType('Automatically prepends the standards mode doctype', async ({ runtime }) =
|
||||||
assert.ok(html.startsWith('<!doctype html>'), 'Doctype always included');
|
assert.ok(html.startsWith('<!doctype html>'), 'Doctype always included');
|
||||||
});
|
});
|
||||||
|
|
||||||
DType('No attributes added when doctype is provided by user', async ({ runtime }) => {
|
DType('No attributes added when doctype is provided by user', async () => {
|
||||||
const result = await runtime.load('/provided');
|
const result = await runtime.load('/provided');
|
||||||
assert.ok(!result.error, `build error: ${result.error}`);
|
assert.ok(!result.error, `build error: ${result.error}`);
|
||||||
|
|
||||||
|
@ -23,7 +48,7 @@ DType('No attributes added when doctype is provided by user', async ({ runtime }
|
||||||
assert.ok(html.startsWith('<!doctype html>'), 'Doctype always included');
|
assert.ok(html.startsWith('<!doctype html>'), 'Doctype always included');
|
||||||
});
|
});
|
||||||
|
|
||||||
DType.skip('Preserves user provided doctype', async ({ runtime }) => {
|
DType.skip('Preserves user provided doctype', async () => {
|
||||||
const result = await runtime.load('/preserve');
|
const result = await runtime.load('/preserve');
|
||||||
assert.ok(!result.error, `build error: ${result.error}`);
|
assert.ok(!result.error, `build error: ${result.error}`);
|
||||||
|
|
||||||
|
@ -31,7 +56,7 @@ DType.skip('Preserves user provided doctype', async ({ runtime }) => {
|
||||||
assert.ok(html.startsWith('<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'), 'Doctype included was preserved');
|
assert.ok(html.startsWith('<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'), 'Doctype included was preserved');
|
||||||
});
|
});
|
||||||
|
|
||||||
DType('User provided doctype is case insensitive', async ({ runtime }) => {
|
DType('User provided doctype is case insensitive', async () => {
|
||||||
const result = await runtime.load('/capital');
|
const result = await runtime.load('/capital');
|
||||||
assert.ok(!result.error, `build error: ${result.error}`);
|
assert.ok(!result.error, `build error: ${result.error}`);
|
||||||
|
|
||||||
|
@ -40,23 +65,4 @@ DType('User provided doctype is case insensitive', async ({ runtime }) => {
|
||||||
assert.not.ok(html.includes('</!DOCTYPE>'), 'There should not be a closing tag');
|
assert.not.ok(html.includes('</!DOCTYPE>'), 'There should not be a closing tag');
|
||||||
});
|
});
|
||||||
|
|
||||||
DType('Doctype can be provided in a layout', async ({ runtime }) => {
|
|
||||||
const result = await runtime.load('/in-layout');
|
|
||||||
assert.ok(!result.error, `build error: ${result.error}`);
|
|
||||||
|
|
||||||
const html = result.contents.toString('utf-8');
|
|
||||||
assert.ok(html.startsWith('<!doctype html>'), 'doctype is at the front');
|
|
||||||
|
|
||||||
const $ = doc(html);
|
|
||||||
assert.equal($('head link').length, 1, 'A link inside of the head');
|
|
||||||
});
|
|
||||||
|
|
||||||
DType('Doctype is added in a layout without one', async ({ runtime }) => {
|
|
||||||
const result = await runtime.load('/in-layout-no-doctype');
|
|
||||||
assert.ok(!result.error, `build error: ${result.error}`);
|
|
||||||
|
|
||||||
const html = result.contents.toString('utf-8');
|
|
||||||
assert.ok(html.startsWith('<!doctype html>'), 'doctype is at the front');
|
|
||||||
});
|
|
||||||
|
|
||||||
DType.run();
|
DType.run();
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
import SubMeta from './SubMeta.astro';
|
|
||||||
---
|
|
||||||
<meta name="author" content="Astro Fan">
|
|
||||||
<SubMeta />
|
|
|
@ -1 +0,0 @@
|
||||||
<meta name="keywords" content="JavaScript,Astro">
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
import '../styles/global.css';
|
|
||||||
import Meta from '../components/Meta.astro';
|
|
||||||
---
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>My App</title>
|
|
||||||
<Meta />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
import '../styles/global.css'
|
|
||||||
---
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>My App</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
import WithoutDoctype from '../layouts/WithoutDoctype.astro';
|
|
||||||
---
|
|
||||||
<WithoutDoctype />
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
import WithDoctype from '../layouts/WithDoctype.astro';
|
|
||||||
---
|
|
||||||
<WithDoctype />
|
|
|
@ -1,3 +0,0 @@
|
||||||
body {
|
|
||||||
background: green;
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@
|
||||||
import Something from '../components/Something.jsx';
|
import Something from '../components/Something.jsx';
|
||||||
import Child from '../components/Child.astro';
|
import Child from '../components/Child.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
<title>My page</title>
|
<title>My page</title>
|
||||||
<style>
|
<style>
|
||||||
.h1 {
|
.h1 {
|
||||||
|
|
6
packages/astro/test/fixtures/no-head-el/src/pages/no-elements.astro
vendored
Normal file
6
packages/astro/test/fixtures/no-head-el/src/pages/no-elements.astro
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
import Something from '../components/Something.jsx';
|
||||||
|
import Child from '../components/Child.astro';
|
||||||
|
---
|
||||||
|
<Something client:load />
|
||||||
|
<Child />
|
|
@ -25,4 +25,14 @@ NoHeadEl('Places style and scripts before the first non-head element', async ({
|
||||||
assert.equal($('script[src="/_snowpack/hmr-client.js"]').length, 1, 'Only the hmr client for the page');
|
assert.equal($('script[src="/_snowpack/hmr-client.js"]').length, 1, 'Only the hmr client for the page');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
NoHeadEl('Injects HMR script even when there are no elements on the page', async ({ runtime }) => {
|
||||||
|
const result = await runtime.load('/no-elements');
|
||||||
|
assert.ok(!result.error, `build error: ${result.error}`);
|
||||||
|
|
||||||
|
const html = result.contents;
|
||||||
|
const $ = doc(html);
|
||||||
|
|
||||||
|
assert.equal($('script[src="/_snowpack/hmr-client.js"]').length, 1, 'Only the hmr client for the page');
|
||||||
|
});
|
||||||
|
|
||||||
NoHeadEl.run();
|
NoHeadEl.run();
|
||||||
|
|
Loading…
Reference in a new issue