diff --git a/.changeset/kind-crabs-marry.md b/.changeset/kind-crabs-marry.md
new file mode 100644
index 000000000..77be85624
--- /dev/null
+++ b/.changeset/kind-crabs-marry.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes a previous revert, makes sure head content is injected into the right place
diff --git a/packages/astro/src/compiler/codegen/index.ts b/packages/astro/src/compiler/codegen/index.ts
index e53845720..9934959f6 100644
--- a/packages/astro/src/compiler/codegen/index.ts
+++ b/packages/astro/src/compiler/codegen/index.ts
@@ -662,7 +662,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
buffers[curr] += `h(__astro_slot_content, { name: ${attributes.slot} },`;
paren++;
}
- buffers[curr] += `h("${name}", ${generateAttributes(attributes)}`;
+ buffers[curr] += `h("${name}", ${generateAttributes(attributes)},`;
paren++;
return;
}
@@ -692,7 +692,7 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
buffers[curr] += `h(__astro_slot_content, { name: ${attributes.slot} },`;
paren++;
}
- buffers[curr] += `h(${componentName}, ${generateAttributes(attributes)}`;
+ buffers[curr] += `h(${componentName}, ${generateAttributes(attributes)},`;
paren++;
return;
} else if (!componentInfo && !isCustomElementTag(componentName)) {
diff --git a/packages/astro/src/compiler/index.ts b/packages/astro/src/compiler/index.ts
index c9cce245f..763e2aef6 100644
--- a/packages/astro/src/compiler/index.ts
+++ b/packages/astro/src/compiler/index.ts
@@ -150,8 +150,8 @@ async function __render(props, ...children) {
value: props,
enumerable: true
},
- css: {
- value: (props[__astroInternal] && props[__astroInternal].css) || [],
+ pageCSS: {
+ value: (props[__astroContext] && props[__astroContext].pageCSS) || [],
enumerable: true
},
isPage: {
@@ -179,18 +179,21 @@ export async function __renderPage({request, children, props, css}) {
__render,
};
- Object.defineProperty(props, __astroContext, {
- value: {
- request
- },
- writable: false,
- enumerable: false
- });
+ const isLayout = (__astroContext in props);
+ if(!isLayout) {
+ Object.defineProperty(props, __astroContext, {
+ value: {
+ pageCSS: css,
+ request
+ },
+ writable: false,
+ enumerable: false
+ });
+ }
Object.defineProperty(props, __astroInternal, {
value: {
- css,
- isPage: true
+ isPage: !isLayout
},
writable: false,
enumerable: false
@@ -202,7 +205,7 @@ export async function __renderPage({request, children, props, css}) {
if (currentChild.layout) {
return currentChild.layout({
request,
- props: {content: currentChild.content},
+ props: {content: currentChild.content, [__astroContext]: props[__astroContext]},
children: [childBodyResult],
});
}
diff --git a/packages/astro/src/compiler/transform/head.ts b/packages/astro/src/compiler/transform/head.ts
index e608f8135..f277b56f1 100644
--- a/packages/astro/src/compiler/transform/head.ts
+++ b/packages/astro/src/compiler/transform/head.ts
@@ -73,7 +73,7 @@ export default function (opts: TransformOptions): Transformer {
start: 0,
end: 0,
type: 'Expression',
- codeChunks: ['Astro.css.map(css => (', '))'],
+ codeChunks: ['Astro.pageCSS.map(css => (', '))'],
children: [
{
type: 'Element',
@@ -162,22 +162,15 @@ export default function (opts: TransformOptions): Transformer {
);
}
- const conditionalNode = {
- start: 0,
- end: 0,
- type: 'Expression',
- codeChunks: ['Astro.isPage ? (', ') : null'],
- children: [
- {
- start: 0,
- end: 0,
- type: 'Fragment',
- children,
- },
- ],
- };
-
- eoh.append(conditionalNode);
+ if (eoh.foundHeadOrHtmlElement || eoh.foundHeadAndBodyContent) {
+ const topLevelFragment = {
+ start: 0,
+ end: 0,
+ type: 'Fragment',
+ children,
+ };
+ eoh.append(topLevelFragment);
+ }
},
};
}
diff --git a/packages/astro/src/compiler/transform/util/end-of-head.ts b/packages/astro/src/compiler/transform/util/end-of-head.ts
index cdf4d3423..ddc4b5136 100644
--- a/packages/astro/src/compiler/transform/util/end-of-head.ts
+++ b/packages/astro/src/compiler/transform/util/end-of-head.ts
@@ -1,21 +1,44 @@
import type { TemplateNode } from '@astrojs/parser';
-const validHeadElements = new Set(['!doctype', 'title', 'meta', 'link', 'style', 'script', 'noscript', 'base']);
+const beforeHeadElements = new Set(['!doctype', 'html']);
+const validHeadElements = new Set(['title', 'meta', 'link', 'style', 'script', 'noscript', 'base']);
export class EndOfHead {
+ private html: TemplateNode | null = null;
private head: TemplateNode | null = null;
private firstNonHead: TemplateNode | null = null;
private parent: TemplateNode | null = null;
private stack: TemplateNode[] = [];
+ public foundHeadElements = false;
+ public foundBodyElements = false;
public append: (...node: TemplateNode[]) => void = () => void 0;
get found(): boolean {
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) {
+ const name = node.name ? node.name.toLowerCase() : null;
+
if (this.found) {
+ if (!validHeadElements.has(name)) {
+ if (node.type === 'Element') {
+ this.foundBodyElements = true;
+ }
+ }
return;
}
@@ -26,8 +49,6 @@ export class EndOfHead {
return;
}
- const name = node.name.toLowerCase();
-
if (name === 'head') {
this.head = node;
this.parent = this.stack[this.stack.length - 2];
@@ -35,11 +56,24 @@ export class EndOfHead {
return;
}
+ // Skip !doctype and html elements
+ if (beforeHeadElements.has(name)) {
+ if (name === 'html') {
+ this.html = node;
+ }
+ return;
+ }
+
if (!validHeadElements.has(name)) {
+ if (node.type === 'Element') {
+ this.foundBodyElements = true;
+ }
this.firstNonHead = node;
this.parent = this.stack[this.stack.length - 2];
this.append = this.prependToFirstNonHead;
return;
+ } else {
+ this.foundHeadElements = true;
}
}
diff --git a/packages/astro/test/astro-doctype.test.js b/packages/astro/test/astro-doctype.test.js
index 86a7c57a6..0740d5af6 100644
--- a/packages/astro/test/astro-doctype.test.js
+++ b/packages/astro/test/astro-doctype.test.js
@@ -1,38 +1,13 @@
-import { fileURLToPath } from 'url';
import { suite } from 'uvu';
import * as assert from 'uvu/assert';
-import { loadConfig } from '#astro/config';
-import { createRuntime } from '#astro/runtime';
+import { doc } from './test-utils.js';
+import { setup } from './helpers.js';
const DType = suite('doctype');
-let runtime, setupError;
+setup(DType, './fixtures/astro-doctype');
-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 () => {
+DType('Automatically prepends the standards mode doctype', async ({ runtime }) => {
const result = await runtime.load('/prepend');
assert.ok(!result.error, `build error: ${result.error}`);
@@ -40,7 +15,7 @@ DType('Automatically prepends the standards mode doctype', async () => {
assert.ok(html.startsWith(''), 'Doctype always included');
});
-DType('No attributes added when doctype is provided by user', async () => {
+DType('No attributes added when doctype is provided by user', async ({ runtime }) => {
const result = await runtime.load('/provided');
assert.ok(!result.error, `build error: ${result.error}`);
@@ -48,7 +23,7 @@ DType('No attributes added when doctype is provided by user', async () => {
assert.ok(html.startsWith(''), 'Doctype always included');
});
-DType.skip('Preserves user provided doctype', async () => {
+DType.skip('Preserves user provided doctype', async ({ runtime }) => {
const result = await runtime.load('/preserve');
assert.ok(!result.error, `build error: ${result.error}`);
@@ -56,7 +31,7 @@ DType.skip('Preserves user provided doctype', async () => {
assert.ok(html.startsWith(''), 'Doctype included was preserved');
});
-DType('User provided doctype is case insensitive', async () => {
+DType('User provided doctype is case insensitive', async ({ runtime }) => {
const result = await runtime.load('/capital');
assert.ok(!result.error, `build error: ${result.error}`);
@@ -65,4 +40,34 @@ DType('User provided doctype is case insensitive', async () => {
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 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 is at the front');
+});
+
+DType('Doctype is added in a layout used with markdown pages', async ({ runtime }) => {
+ const result = await runtime.load('/in-layout-article');
+ assert.ok(!result.error, `build error: ${result.error}`);
+
+ const html = result.contents.toString('utf-8');
+ assert.ok(html.startsWith(''), 'doctype is at the front');
+
+ const $ = doc(html);
+ assert.equal($('head link').length, 1, 'A link inside of the head');
+});
+
DType.run();
diff --git a/packages/astro/test/fixtures/astro-doctype/src/components/Meta.astro b/packages/astro/test/fixtures/astro-doctype/src/components/Meta.astro
new file mode 100644
index 000000000..903b1bd79
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-doctype/src/components/Meta.astro
@@ -0,0 +1,5 @@
+---
+import SubMeta from './SubMeta.astro';
+---
+
+