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();