diff --git a/.changeset/olive-beds-happen.md b/.changeset/olive-beds-happen.md
new file mode 100644
index 000000000..f45802e9c
--- /dev/null
+++ b/.changeset/olive-beds-happen.md
@@ -0,0 +1,5 @@
+---
+"@astrojs/compiler": minor
+---
+
+Add an experimental flag `experimentalScriptOrder` that corrects the order styles & scripts are rendered within a component. When enabled, the order styles & scripts are rendered will be consistent with the order they are defined.
diff --git a/cmd/astro-wasm/astro-wasm.go b/cmd/astro-wasm/astro-wasm.go
index 9f3ae1324..3dfdba839 100644
--- a/cmd/astro-wasm/astro-wasm.go
+++ b/cmd/astro-wasm/astro-wasm.go
@@ -138,6 +138,11 @@ func makeTransformOptions(options js.Value) transform.TransformOptions {
renderScript = true
}
+ experimentalScriptOrder := false
+ if jsBool(options.Get("experimentalScriptOrder")) {
+ experimentalScriptOrder = true
+ }
+
return transform.TransformOptions{
Filename: filename,
NormalizedFilename: normalizedFilename,
@@ -152,6 +157,7 @@ func makeTransformOptions(options js.Value) transform.TransformOptions {
TransitionsAnimationURL: transitionsAnimationURL,
AnnotateSourceFile: annotateSourceFile,
RenderScript: renderScript,
+ ExperimentalScriptOrder: experimentalScriptOrder,
}
}
@@ -336,7 +342,7 @@ func Transform() any {
}
// Hoist styles and scripts to the top-level
- transform.ExtractStyles(doc)
+ transform.ExtractStyles(doc, &transformOptions)
// Pre-process styles
// Important! These goroutines need to be spawned from this file or they don't work
diff --git a/internal/printer/__printer_js__/multiple_define_vars_on_style.snap b/internal/printer/__printer_js__/multiple_define_vars_on_style.snap
index 0c082c436..fdfc39070 100755
--- a/internal/printer/__printer_js__/multiple_define_vars_on_style.snap
+++ b/internal/printer/__printer_js__/multiple_define_vars_on_style.snap
@@ -34,7 +34,7 @@ export const $$metadata = $$createMetadata(import.meta.url, { modules: [], hydra
const $$Component = $$createComponent(($$result, $$props, $$slots) => {
-const $$definedVars = $$defineStyleVars([{color:'red'},{color:'green'}]);
+const $$definedVars = $$defineStyleVars([{color:'green'},{color:'red'}]);
return $$render`${$$maybeRenderHead($$result)}
foo
bar
`;
}, undefined, undefined);
export default $$Component;
diff --git a/internal/printer/__printer_js__/script_multiple__renderScript__true_.snap b/internal/printer/__printer_js__/script_multiple__renderScript__true_.snap
index 12b257be5..8526ea485 100755
--- a/internal/printer/__printer_js__/script_multiple__renderScript__true_.snap
+++ b/internal/printer/__printer_js__/script_multiple__renderScript__true_.snap
@@ -30,7 +30,7 @@ import {
createMetadata as $$createMetadata
} from "http://localhost:3000/";
-export const $$metadata = $$createMetadata("/src/pages/index.astro", { modules: [], hydratedComponents: [], clientOnlyComponents: [], hydrationDirectives: new Set([]), hoisted: [{ type: 'inline', value: `console.log("World");` }, { type: 'inline', value: `console.log("Hello");` }] });
+export const $$metadata = $$createMetadata("/src/pages/index.astro", { modules: [], hydratedComponents: [], clientOnlyComponents: [], hydrationDirectives: new Set([]), hoisted: [{ type: 'inline', value: `console.log("Hello");` }, { type: 'inline', value: `console.log("World");` }] });
const $$Index = $$createComponent(($$result, $$props, $$slots) => {
diff --git a/internal/printer/printer_css_test.go b/internal/printer/printer_css_test.go
index 25d8ff12d..92373ba8c 100644
--- a/internal/printer/printer_css_test.go
+++ b/internal/printer/printer_css_test.go
@@ -87,8 +87,9 @@ func TestPrinterCSS(t *testing.T) {
}
hash := astro.HashString(code)
- transform.ExtractStyles(doc)
- transform.Transform(doc, transform.TransformOptions{Scope: hash, ScopedStyleStrategy: scopedStyleStrategy}, handler.NewHandler(code, "/test.astro")) // note: we want to test Transform in context here, but more advanced cases could be tested separately
+ opts := transform.TransformOptions{Scope: hash, ScopedStyleStrategy: scopedStyleStrategy, ExperimentalScriptOrder: true}
+ transform.ExtractStyles(doc, &opts)
+ transform.Transform(doc, opts, handler.NewHandler(code, "/test.astro")) // note: we want to test Transform in context here, but more advanced cases could be tested separately
result := PrintCSS(code, doc, transform.TransformOptions{
Scope: "astro-XXXX",
InternalURL: "http://localhost:3000/",
diff --git a/internal/printer/printer_test.go b/internal/printer/printer_test.go
index 0089ea34f..3042c0ee3 100644
--- a/internal/printer/printer_test.go
+++ b/internal/printer/printer_test.go
@@ -2066,12 +2066,13 @@ const meta = { title: 'My App' };
}
hash := astro.HashString(code)
- transform.ExtractStyles(doc)
// combine from tt.transformOptions
transformOptions := transform.TransformOptions{
- Scope: hash,
- RenderScript: tt.transformOptions.RenderScript,
+ Scope: hash,
+ RenderScript: tt.transformOptions.RenderScript,
+ ExperimentalScriptOrder: true,
}
+ transform.ExtractStyles(doc, &transformOptions)
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{
diff --git a/internal/transform/transform.go b/internal/transform/transform.go
index c411361b9..3e8d281d0 100644
--- a/internal/transform/transform.go
+++ b/internal/transform/transform.go
@@ -34,6 +34,7 @@ type TransformOptions struct {
PreprocessStyle interface{}
AnnotateSourceFile bool
RenderScript bool
+ ExperimentalScriptOrder bool
}
func Transform(doc *astro.Node, opts TransformOptions, h *handler.Handler) *astro.Node {
@@ -115,7 +116,7 @@ func Transform(doc *astro.Node, opts TransformOptions, h *handler.Handler) *astr
return doc
}
-func ExtractStyles(doc *astro.Node) {
+func ExtractStyles(doc *astro.Node, opts *TransformOptions) {
walk(doc, func(n *astro.Node) {
if n.Type == astro.ElementNode && n.DataAtom == a.Style {
if HasSetDirective(n) || HasInlineDirective(n) {
@@ -125,8 +126,12 @@ func ExtractStyles(doc *astro.Node) {
if !IsHoistable(n) {
return
}
- // prepend node to maintain authored order
- doc.Styles = append([]*astro.Node{n}, doc.Styles...)
+ // append node to maintain authored order
+ if opts.ExperimentalScriptOrder {
+ doc.Styles = append(doc.Styles, n)
+ } else {
+ doc.Styles = append([]*astro.Node{n}, doc.Styles...)
+ }
}
})
// Important! Remove styles from original location *after* walking the doc
@@ -434,9 +439,13 @@ func ExtractScript(doc *astro.Node, n *astro.Node, opts *TransformOptions, h *ha
}
}
- // prepend node to maintain authored order
+ // append node to maintain authored order
if shouldAdd {
- doc.Scripts = append([]*astro.Node{n}, doc.Scripts...)
+ if opts.ExperimentalScriptOrder {
+ doc.Scripts = append(doc.Scripts, n)
+ } else {
+ doc.Scripts = append([]*astro.Node{n}, doc.Scripts...)
+ }
n.HandledScript = true
}
} else {
diff --git a/internal/transform/transform_test.go b/internal/transform/transform_test.go
index bc80b77d1..1da0cbe2b 100644
--- a/internal/transform/transform_test.go
+++ b/internal/transform/transform_test.go
@@ -182,7 +182,6 @@ func TestTransformScoping(t *testing.T) {
if err != nil {
t.Error(err)
}
- ExtractStyles(doc)
var scopeStyle string
if tt.scopeStyle == "attribute" {
scopeStyle = "attribute"
@@ -191,7 +190,9 @@ func TestTransformScoping(t *testing.T) {
} else {
scopeStyle = "where"
}
- Transform(doc, TransformOptions{Scope: "xxxxxx", ScopedStyleStrategy: scopeStyle}, handler.NewHandler(tt.source, "/test.astro"))
+ transformOptions := TransformOptions{Scope: "xxxxxx", ScopedStyleStrategy: scopeStyle}
+ ExtractStyles(doc, &transformOptions)
+ Transform(doc, transformOptions, handler.NewHandler(tt.source, "/test.astro"))
astro.PrintToSource(&b, doc.LastChild.FirstChild.NextSibling.FirstChild)
got := b.String()
if tt.want != got {
@@ -211,8 +212,9 @@ func FuzzTransformScoping(f *testing.F) {
if err != nil {
t.Skip("Invalid parse, skipping rest of fuzz test")
}
- ExtractStyles(doc)
- Transform(doc, TransformOptions{Scope: "xxxxxx"}, handler.NewHandler(source, "/test.astro"))
+ transformOptions := TransformOptions{Scope: "xxxxxx"}
+ ExtractStyles(doc, &transformOptions)
+ Transform(doc, transformOptions, handler.NewHandler(source, "/test.astro"))
var b strings.Builder
astro.PrintToSource(&b, doc.LastChild.FirstChild.NextSibling.FirstChild)
got := b.String()
@@ -297,10 +299,11 @@ func TestFullTransform(t *testing.T) {
if err != nil {
t.Error(err)
}
- ExtractStyles(doc)
+ transformOptions := TransformOptions{}
+ ExtractStyles(doc, &transformOptions)
// Clear doc.Styles to avoid scoping behavior, we're not testing that here
doc.Styles = make([]*astro.Node, 0)
- Transform(doc, TransformOptions{}, handler.NewHandler(tt.source, "/test.astro"))
+ Transform(doc, transformOptions, handler.NewHandler(tt.source, "/test.astro"))
astro.PrintToSource(&b, doc)
got := strings.TrimSpace(b.String())
if tt.want != got {
@@ -345,10 +348,11 @@ func TestTransformTrailingSpace(t *testing.T) {
if err != nil {
t.Error(err)
}
- ExtractStyles(doc)
+ transformOptions := TransformOptions{}
+ ExtractStyles(doc, &transformOptions)
// Clear doc.Styles to avoid scoping behavior, we're not testing that here
doc.Styles = make([]*astro.Node, 0)
- Transform(doc, TransformOptions{}, handler.NewHandler(tt.source, "/test.astro"))
+ Transform(doc, transformOptions, handler.NewHandler(tt.source, "/test.astro"))
astro.PrintToSource(&b, doc)
got := b.String()
if tt.want != got {
@@ -463,12 +467,13 @@ func TestCompactTransform(t *testing.T) {
if err != nil {
t.Error(err)
}
- ExtractStyles(doc)
+ transformOptions := TransformOptions{
+ Compact: true,
+ }
+ ExtractStyles(doc, &transformOptions)
// Clear doc.Styles to avoid scoping behavior, we're not testing that here
doc.Styles = make([]*astro.Node, 0)
- Transform(doc, TransformOptions{
- Compact: true,
- }, &handler.Handler{})
+ Transform(doc, transformOptions, &handler.Handler{})
astro.PrintToSource(&b, doc)
got := strings.TrimSpace(b.String())
if tt.want != got {
diff --git a/packages/compiler/src/shared/types.ts b/packages/compiler/src/shared/types.ts
index 8ac858027..aca3027e7 100644
--- a/packages/compiler/src/shared/types.ts
+++ b/packages/compiler/src/shared/types.ts
@@ -65,6 +65,7 @@ export interface TransformOptions {
* @experimental
*/
renderScript?: boolean;
+ experimentalScriptOrder?: boolean;
}
export type ConvertToTSXOptions = Pick<
diff --git a/packages/compiler/test/scripts/order.ts b/packages/compiler/test/scripts/order.ts
new file mode 100644
index 000000000..762617a32
--- /dev/null
+++ b/packages/compiler/test/scripts/order.ts
@@ -0,0 +1,25 @@
+import { transform } from '@astrojs/compiler';
+import { test } from 'uvu';
+import * as assert from 'uvu/assert';
+
+test('outputs scripts in expected order', async () => {
+ const result = await transform(
+ `
+
+ `,
+ {
+ experimentalScriptOrder: true,
+ }
+ );
+
+ const scripts = result.scripts;
+
+ // for typescript
+ if (scripts[0].type === 'external') throw new Error('Script is external');
+ if (scripts[1].type === 'external') throw new Error('Script is external');
+
+ assert.match(scripts[0].code, 'console.log(1)');
+ assert.match(scripts[1].code, 'console.log(2)');
+});
+
+test.run();
diff --git a/packages/compiler/test/styles/sass.ts b/packages/compiler/test/styles/sass.ts
index 3c0f65263..f11385672 100644
--- a/packages/compiler/test/styles/sass.ts
+++ b/packages/compiler/test/styles/sass.ts
@@ -33,19 +33,20 @@ test.before(async () => {
result = await transform(FIXTURE, {
sourcemap: true,
preprocessStyle,
+ experimentalScriptOrder: true,
});
});
test('transforms scss one', () => {
- assert.match(
- result.css[result.css.length - 1],
- 'color:red',
- 'Expected "color:red" to be present.'
- );
+ assert.match(result.css[0], 'color:red', 'Expected "color:red" to be present.');
});
test('transforms scss two', () => {
- assert.match(result.css[0], 'color:green', 'Expected "color:green" to be present.');
+ assert.match(
+ result.css[result.css.length - 1],
+ 'color:green',
+ 'Expected "color:green" to be present.'
+ );
});
test.run();