diff --git a/.changeset/shiny-plums-smile.md b/.changeset/shiny-plums-smile.md
new file mode 100644
index 000000000..c928011c1
--- /dev/null
+++ b/.changeset/shiny-plums-smile.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/compiler': minor
+---
+
+Fixes an edge case that caused `html` and `body` tags with attributes to be ignored when they were wrapped in a component.
diff --git a/cmd/astro-wasm/astro-wasm.go b/cmd/astro-wasm/astro-wasm.go
index 2930d0ad7..41ed3be0d 100644
--- a/cmd/astro-wasm/astro-wasm.go
+++ b/cmd/astro-wasm/astro-wasm.go
@@ -232,7 +232,7 @@ func Parse() any {
h := handler.NewHandler(source, parseOptions.Filename)
var doc *astro.Node
- doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h), astro.ParseOptionEnableLiteral(true))
+ doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionEnableLiteral(true), astro.ParseOptionWithHandler(h))
if err != nil {
h.AppendError(err)
}
@@ -256,7 +256,7 @@ func ConvertToTSX() any {
h := handler.NewHandler(source, transformOptions.Filename)
var doc *astro.Node
- doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h), astro.ParseOptionEnableLiteral(true))
+ doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionEnableLiteral(true), astro.ParseOptionWithHandler(h))
if err != nil {
h.AppendError(err)
}
@@ -307,7 +307,7 @@ func Transform() any {
}
}()
- doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionWithHandler(h))
+ doc, err := astro.ParseWithOptions(strings.NewReader(source), astro.ParseOptionEnableLiteral(true), astro.ParseOptionWithHandler(h))
if err != nil {
reject.Invoke(wasm_utils.ErrorToJSError(h, err))
return
diff --git a/internal/parser.go b/internal/parser.go
index 18275683a..a413e502c 100644
--- a/internal/parser.go
+++ b/internal/parser.go
@@ -354,7 +354,7 @@ func (p *parser) addText(text string) {
})
}
-func (p *parser) addFrontmatter(empty bool) {
+func (p *parser) addFrontmatter() {
if p.frontmatterState == FrontmatterInitial {
if p.doc.FirstChild != nil {
p.fm = &Node{
@@ -369,13 +369,8 @@ func (p *parser) addFrontmatter(empty bool) {
}
p.doc.AppendChild(p.fm)
}
- if empty {
- p.frontmatterState = FrontmatterClosed
- p.fm.Attr = append(p.fm.Attr, Attribute{Key: ImplicitNodeMarker, Type: EmptyAttribute})
- } else {
- p.frontmatterState = FrontmatterOpen
- p.oe = append(p.oe, p.fm)
- }
+ p.frontmatterState = FrontmatterOpen
+ p.oe = append(p.oe, p.fm)
}
}
@@ -646,9 +641,6 @@ func initialIM(p *parser) bool {
p.im = beforeHTMLIM
return true
}
- if p.frontmatterState == FrontmatterInitial {
- p.addFrontmatter(true)
- }
p.quirks = true
p.im = beforeHTMLIM
return false
@@ -1534,9 +1526,6 @@ func inBodyIM(p *parser) bool {
}
}
}
- if p.frontmatterState == FrontmatterInitial {
- p.addFrontmatter(true)
- }
return true
}
@@ -2642,7 +2631,7 @@ func frontmatterIM(p *parser) bool {
switch p.tok.Type {
case FrontmatterFenceToken:
if p.frontmatterState == FrontmatterInitial {
- p.addFrontmatter(false)
+ p.addFrontmatter()
return true
} else {
p.frontmatterState = FrontmatterClosed
diff --git a/internal/printer/printer_test.go b/internal/printer/printer_test.go
index 938e3c4e8..fc34006c0 100644
--- a/internal/printer/printer_test.go
+++ b/internal/printer/printer_test.go
@@ -259,7 +259,7 @@ func TestPrinter(t *testing.T) {
},
{
name: "slot with fallback",
- source: `
Hello world!
`,
+ source: `Hello world!
`,
want: want{
code: `${$$maybeRenderHead($$result)}${$$renderSlot($$result,$$slots["default"],$$render` + BACKTICK + `Hello world!
` + BACKTICK + `)}`,
},
@@ -568,9 +568,9 @@ import type data from "test"
div+h2 ${dummyKey}
-
p+h2 ${dummyKey}
- ` + BACKTICK + `
- );
+ p+h2 ${dummyKey}
+ ` + BACKTICK + `
+ );
})
}
@@ -588,8 +588,8 @@ import type data from "test"
want: want{
code: `
${$$maybeRenderHead($$result)}
-${Object.keys(importedAuthors).map(author => $$render` + BACKTICK + `hello
` + BACKTICK + `)}
-${Object.keys(importedAuthors).map(author => $$render` + BACKTICK + `${author}
` + BACKTICK + `)}
+${Object.keys(importedAuthors).map(author => $$render` + BACKTICK + `hello
` + BACKTICK + `)}
+${Object.keys(importedAuthors).map(author => $$render` + BACKTICK + `${author}
` + BACKTICK + `)}
`,
},
@@ -1602,6 +1602,13 @@ const name = 'named';
code: "${cond && $$render``}${cond && $$render`My title`}",
},
},
+ {
+ name: "top-level component does not drop body attributes",
+ source: ``,
+ want: want{
+ code: "${$$renderComponent($$result,'Base',Base,{},{\"default\": () => $$render`${$$maybeRenderHead($$result)}${$$renderSlot($$result,$$slots[\"default\"])}`,})}",
+ },
+ },
{
name: "custom elements",
source: `---
@@ -1661,7 +1668,7 @@ ${$$renderComponent($$result,'my-element','my-element',{"client:load":true,"clie
},
{
name: "Self-closing script in head works",
- source: ``,
+ source: ``,
want: want{
code: `` + RENDER_HEAD_RESULT + ``,
},
@@ -1682,7 +1689,7 @@ ${$$renderComponent($$result,'my-element','my-element',{"client:load":true,"clie
},
{
name: "Self-closing components in head can have siblings",
- source: ``,
+ source: ``,
want: want{
code: `${$$renderComponent($$result,'BaseHead',BaseHead,{})}` + RENDER_HEAD_RESULT + ``,
},
@@ -2082,7 +2089,7 @@ import { Container, Col, Row } from 'react-bootstrap';
name: "Preserve namespaces in expressions",
source: ``,
want: want{
- code: `${$$maybeRenderHead($$result)}`,
+ code: `${$$maybeRenderHead($$result)}`,
},
},
{
@@ -2297,6 +2304,42 @@ const content = "lol";
`,
},
},
+ {
+ // ensurethere are no duplicate elements matching the ones in the link below (`` in this test)
+ // https://github.com/withastro/compiler/blob/a90d99ee8cc3ad92d1b39d73df1f7301011ee970/internal/parser.go#L1490
+ name: " tag with expression in table ",
+ source: `
+
+`,
+ want: want{
+ code: `${$$maybeRenderHead($$result)}
+
+`,
+ },
+ },
+ {
+ // makes sure that there are no duplicate elements matching the ones in the link below (`` in this test)
+ // https://github.com/withastro/compiler/blob/a90d99ee8cc3ad92d1b39d73df1f7301011ee970/internal/parser.go#L1490
+ name: " tag with expression in template",
+ source: `
+ {text}.
+
+This should not be a link
`,
+ want: want{
+ code: `
+ ${$$maybeRenderHead($$result)}${text}.
+
+This should not be a link
`,
+ },
+ },
{
name: "complex table",
source: `
@@ -3444,7 +3487,7 @@ const items = ["Dog", "Cat", "Platipus"];
filename: "/projects/app/src/pages/page.astro",
transitions: true,
want: want{
- code: `${$$maybeRenderHead($$result)}`,
+ code: `${$$maybeRenderHead($$result)}`,
},
},
{
@@ -3453,7 +3496,7 @@ const items = ["Dog", "Cat", "Platipus"];
filename: "/projects/app/src/pages/page.astro",
transitions: true,
want: want{
- code: `${$$maybeRenderHead($$result)}`,
+ code: `${$$maybeRenderHead($$result)}`,
},
},
{
@@ -3462,7 +3505,7 @@ const items = ["Dog", "Cat", "Platipus"];
filename: "/projects/app/src/pages/page.astro",
transitions: true,
want: want{
- code: `${$$maybeRenderHead($$result)}`,
+ code: `${$$maybeRenderHead($$result)}`,
},
},
{
@@ -3471,7 +3514,7 @@ const items = ["Dog", "Cat", "Platipus"];
filename: "/projects/app/src/pages/page.astro",
transitions: true,
want: want{
- code: `${$$renderComponent($$result,'Component',Component,{"class":"bar","data-astro-transition-scope":($$renderTransition($$result, "wkm5vset", "morph", ""))})}`,
+ code: `${$$renderComponent($$result,'Component',Component,{"class":"bar","data-astro-transition-scope":($$renderTransition($$result, "byigm4lx", "morph", ""))})}`,
},
},
{
@@ -3479,7 +3522,7 @@ const items = ["Dog", "Cat", "Platipus"];
source: ``,
transitions: true,
want: want{
- code: `${$$maybeRenderHead($$result)}`,
+ code: `${$$maybeRenderHead($$result)}`,
},
},
{
@@ -3487,7 +3530,7 @@ const items = ["Dog", "Cat", "Platipus"];
source: ``,
transitions: true,
want: want{
- code: `${$$maybeRenderHead($$result)}`,
+ code: `${$$maybeRenderHead($$result)}`,
},
},
{
@@ -3495,7 +3538,7 @@ const items = ["Dog", "Cat", "Platipus"];
source: ``,
transitions: true,
want: want{
- code: `${$$renderComponent($$result,'my-island','my-island',{"data-astro-transition-persist-props":"false","data-astro-transition-persist":($$createTransitionScope($$result, "otghnj5u"))})}`,
+ code: `${$$renderComponent($$result,'my-island','my-island',{"data-astro-transition-persist-props":"false","data-astro-transition-persist":($$createTransitionScope($$result, "rho3aldc"))})}`,
},
},
{
@@ -3505,6 +3548,31 @@ const items = ["Dog", "Cat", "Platipus"];
code: `${$$renderComponent($$result,'Component',Component,{})}${(void 0)}`,
},
},
+ {
+ name: "nested head content stays in the head",
+ source: `---
+const meta = { title: 'My App' };
+---
+
+
+
+
+
+ {
+ meta && {meta.title}
+ }
+
+
+
+
+ My App
+
+`,
+ want: want{
+ frontmatter: []string{"", `const meta = { title: 'My App' };`},
+ code: ` ${ meta && $$render` + BACKTICK + `${meta.title}` + BACKTICK + ` } ${$$renderHead($$result)} My App
`,
+ },
+ },
}
for _, tt := range tests {
@@ -3520,8 +3588,8 @@ const items = ["Dog", "Cat", "Platipus"];
// transform output from source
code := test_utils.Dedent(tt.source)
- doc, err := astro.Parse(strings.NewReader(code))
h := handler.NewHandler(code, "")
+ doc, err := astro.ParseWithOptions(strings.NewReader(code), astro.ParseOptionEnableLiteral(true), astro.ParseOptionWithHandler(h))
if err != nil {
t.Error(err)
@@ -3535,6 +3603,7 @@ const items = ["Dog", "Cat", "Platipus"];
RenderScript: tt.transformOptions.RenderScript,
}
transform.Transform(doc, transformOptions, h) // note: we want to test Transform in context here, but more advanced cases could be tested separately
+
result := PrintToJS(code, doc, 0, transform.TransformOptions{
Scope: "XXXX",
InternalURL: "http://localhost:3000/",
@@ -3746,6 +3815,11 @@ const c = '\''
source: `Hello world!
`,
want: []ASTNode{{Type: "element", Name: "style"}, {Type: "element", Name: "html", Children: []ASTNode{{Type: "element", Name: "body", Children: []ASTNode{{Type: "element", Name: "h1", Children: []ASTNode{{Type: "text", Value: "Hello world!"}}}}}}}},
},
+ {
+ name: "empty style",
+ source: ``,
+ want: []ASTNode{{Type: "element", Name: "style", Attributes: []ASTNode{{Type: "attribute", Kind: "expression", Name: "define:vars", Value: "{ color: \"Gainsboro\" }"}}}},
+ },
{
name: "style after html",
source: `Hello world!
`,
@@ -3776,6 +3850,11 @@ const c = '\''
source: ``,
want: []ASTNode{{Type: "element", Name: "main", Attributes: []ASTNode{{Type: "attribute", Kind: "template-literal", Name: "id", Value: "gotcha", Raw: "`gotcha"}}}},
},
+ {
+ name: "top-level component does not drop body attributes",
+ source: ``,
+ want: []ASTNode{{Type: "component", Name: "Base", Attributes: []ASTNode{}, Children: []ASTNode{{Type: "element", Name: "body", Attributes: []ASTNode{{Type: "attribute", Kind: "quoted", Name: "class", Value: "foobar", Raw: "\"foobar\""}}, Children: []ASTNode{{Type: "element", Name: "slot"}}}}}},
+ },
}
for _, tt := range tests {
diff --git a/internal/token_test.go b/internal/token_test.go
index 39992f6b9..8ab374fff 100644
--- a/internal/token_test.go
+++ b/internal/token_test.go
@@ -500,6 +500,11 @@ func TestBasic(t *testing.T) {
`Hello world!
`,
+ []TokenType{StartTagToken, StartTagToken, StartTagToken, TextToken, EndTagToken, EndTagToken, EndTagToken},
+ },
}
runTokenTypeTest(t, Basic)
diff --git a/internal/transform/transform.go b/internal/transform/transform.go
index f83f554df..c0030d1f5 100644
--- a/internal/transform/transform.go
+++ b/internal/transform/transform.go
@@ -242,7 +242,7 @@ func TrimTrailingSpace(doc *astro.Node) {
return
}
- if doc.LastChild.Type == astro.TextNode {
+ if doc.LastChild.Type == astro.TextNode && len(doc.LastChild.Data) < len(strings.TrimRightFunc(doc.LastChild.Data, unicode.IsSpace)) {
doc.LastChild.Data = strings.TrimRightFunc(doc.LastChild.Data, unicode.IsSpace)
return
}
@@ -254,7 +254,6 @@ func TrimTrailingSpace(doc *astro.Node) {
n = n.LastChild
continue
} else {
- n = nil
break
}
}
@@ -262,7 +261,11 @@ func TrimTrailingSpace(doc *astro.Node) {
// Collapse all trailing text nodes
for n != nil && n.Type == astro.TextNode {
n.Data = strings.TrimRightFunc(n.Data, unicode.IsSpace)
- n = n.PrevSibling
+ if len(n.Data) > 0 {
+ break
+ } else {
+ n = n.PrevSibling
+ }
}
}
diff --git a/internal/transform/transform_test.go b/internal/transform/transform_test.go
index a52dc11b6..2eaf49e41 100644
--- a/internal/transform/transform_test.go
+++ b/internal/transform/transform_test.go
@@ -253,13 +253,13 @@ func TestFullTransform(t *testing.T) {
},
{
name: "Component before html I",
- source: `Astro
`,
- want: `Astro
`,
+ source: `Astro
`,
+ want: `Astro
`,
},
{
name: "Component before html II",
source: ``,
- want: ``,
+ want: ``,
},
{
name: "respects explicitly authored elements",
@@ -281,6 +281,11 @@ func TestFullTransform(t *testing.T) {
source: ``,
want: ``,
},
+ {
+ name: "top-level component does not drop body attributes",
+ source: ``,
+ want: ``,
+ },
{
name: "works with nested components",
source: `
`,
@@ -302,7 +307,7 @@ func TestFullTransform(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b.Reset()
- doc, err := astro.Parse(strings.NewReader(tt.source))
+ doc, err := astro.ParseWithOptions(strings.NewReader(tt.source), astro.ParseOptionEnableLiteral(true))
if err != nil {
t.Error(err)
}
@@ -513,11 +518,12 @@ func TestAnnotation(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b.Reset()
- doc, err := astro.Parse(strings.NewReader(tt.source))
+
+ h := handler.NewHandler(tt.source, "/src/pages/index.astro")
+ doc, err := astro.ParseWithOptions(strings.NewReader(tt.source), astro.ParseOptionEnableLiteral(true), astro.ParseOptionWithHandler(h))
if err != nil {
t.Error(err)
}
- h := handler.NewHandler(tt.source, "/src/pages/index.astro")
Transform(doc, TransformOptions{
AnnotateSourceFile: true,
Filename: "/src/pages/index.astro",
diff --git a/packages/compiler/test/basic/body-in-component.ts b/packages/compiler/test/basic/body-in-component.ts
new file mode 100644
index 000000000..5776a1c8e
--- /dev/null
+++ b/packages/compiler/test/basic/body-in-component.ts
@@ -0,0 +1,24 @@
+import { test } from 'uvu';
+import * as assert from 'uvu/assert';
+import { transform } from '@astrojs/compiler';
+
+const FIXTURE = `
+---
+let value = 'world';
+---
+
+
+`;
+
+let result;
+test.before(async () => {
+ result = await transform(FIXTURE);
+});
+
+test('top-level component does not drop body attributes', () => {
+ console.log(result.code);
+ assert.match(result.code, "${$$renderComponent($$result,'Base',Base,{},{\"default\": () => $$render`${$$maybeRenderHead($$result)}${$$renderSlot($$result,$$slots[\"default\"])}`,})}", `Expected body to be included!`);
+});
+
+
+test.run();
diff --git a/packages/compiler/test/basic/trailing-spaces-ii.ts b/packages/compiler/test/basic/trailing-spaces-ii.ts
index 198626215..44277dcb2 100644
--- a/packages/compiler/test/basic/trailing-spaces-ii.ts
+++ b/packages/compiler/test/basic/trailing-spaces-ii.ts
@@ -23,7 +23,7 @@ test.before(async () => {
});
test('trailing space', () => {
- assert.ok(result.code, 'Expected to compiler');
+ assert.ok(result.code, 'Expected to compile');
assert.match(
result.code,
`
diff --git a/packages/compiler/test/tsx/basic.ts b/packages/compiler/test/tsx/basic.ts
index e5f705f6d..d2e7d80b2 100644
--- a/packages/compiler/test/tsx/basic.ts
+++ b/packages/compiler/test/tsx/basic.ts
@@ -267,8 +267,8 @@ test('return ranges - no frontmatter', async () => {
assert.equal(metaRanges, {
frontmatter: {
- start: 30,
- end: 30,
+ start: 0,
+ end: 0,
},
body: {
start: 41,