Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reverse order of styles & scripts #1055

Merged
merged 12 commits into from
Feb 21, 2025
5 changes: 5 additions & 0 deletions .changeset/olive-beds-happen.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 7 additions & 1 deletion cmd/astro-wasm/astro-wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"fmt"
"strings"
"sync"
"syscall/js"

Check failure on line 9 in cmd/astro-wasm/astro-wasm.go

View workflow job for this annotation

GitHub Actions / lint

could not import syscall/js (-: build constraints exclude all Go files in /opt/hostedtoolcache/go/1.23.6/x64/src/syscall/js) (typecheck)
"unicode"

"github.com/norunners/vert"
Expand Down Expand Up @@ -138,6 +138,11 @@
renderScript = true
}

experimentalScriptOrder := false
if jsBool(options.Get("experimentalScriptOrder")) {
experimentalScriptOrder = true
}

return transform.TransformOptions{
Filename: filename,
NormalizedFilename: normalizedFilename,
Expand All @@ -152,6 +157,7 @@
TransitionsAnimationURL: transitionsAnimationURL,
AnnotateSourceFile: annotateSourceFile,
RenderScript: renderScript,
ExperimentalScriptOrder: experimentalScriptOrder,
}
}

Expand Down Expand Up @@ -260,7 +266,7 @@
// AFTER printing, exec transformations to pickup any errors/warnings
transform.Transform(doc, transformOptions, h)

return vert.ValueOf(ParseResult{

Check failure on line 269 in cmd/astro-wasm/astro-wasm.go

View workflow job for this annotation

GitHub Actions / lint

undefined: vert.ValueOf (typecheck)
AST: string(result.Output),
Diagnostics: h.Diagnostics(),
}).Value
Expand Down Expand Up @@ -294,7 +300,7 @@
code += "\n" + inlineSourcemap
}

return vert.ValueOf(TSXResult{

Check failure on line 303 in cmd/astro-wasm/astro-wasm.go

View workflow job for this annotation

GitHub Actions / lint

undefined: vert.ValueOf (typecheck)
Code: code,
Map: sourcemapString,
Diagnostics: h.Diagnostics(),
Expand Down Expand Up @@ -336,7 +342,7 @@
}

// 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
Expand Down Expand Up @@ -506,13 +512,13 @@
}`, sourcemap.Sources[0], sourcemap.SourcesContent[0], sourcemap.Mappings)
}

func createExternalSourceMap(source string, transformResult *TransformResult, result printer.PrintResult, transformOptions transform.TransformOptions) vert.Value {

Check failure on line 515 in cmd/astro-wasm/astro-wasm.go

View workflow job for this annotation

GitHub Actions / lint

undefined: vert.Value (typecheck)
transformResult.Code = string(result.Output)
transformResult.Map = createSourceMapString(source, result, transformOptions)
return vert.ValueOf(transformResult)
}

func createInlineSourceMap(source string, transformResult *TransformResult, result printer.PrintResult, transformOptions transform.TransformOptions) vert.Value {

Check failure on line 521 in cmd/astro-wasm/astro-wasm.go

View workflow job for this annotation

GitHub Actions / lint

undefined: vert.Value (typecheck)
sourcemapString := createSourceMapString(source, result, transformOptions)
inlineSourcemap := `//# sourceMappingURL=data:application/json;charset=utf-8;base64,` + base64.StdEncoding.EncodeToString([]byte(sourcemapString))
transformResult.Code = string(result.Output) + "\n" + inlineSourcemap
Expand All @@ -520,7 +526,7 @@
return vert.ValueOf(transformResult)
}

func createBothSourceMap(source string, transformResult *TransformResult, result printer.PrintResult, transformOptions transform.TransformOptions) vert.Value {

Check failure on line 529 in cmd/astro-wasm/astro-wasm.go

View workflow job for this annotation

GitHub Actions / lint

undefined: vert.Value (typecheck)
sourcemapString := createSourceMapString(source, result, transformOptions)
inlineSourcemap := `//# sourceMappingURL=data:application/json;charset=utf-8;base64,` + base64.StdEncoding.EncodeToString([]byte(sourcemapString))
transformResult.Code = string(result.Output) + "\n" + inlineSourcemap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)}<h1 class="astro-6oxbqcst"${$$addAttribute($$definedVars, "style")}>foo</h1><h2 class="astro-6oxbqcst"${$$addAttribute($$definedVars, "style")}>bar</h2>`;
}, undefined, undefined);
export default $$Component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {

Expand Down
5 changes: 3 additions & 2 deletions internal/printer/printer_css_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down
7 changes: 4 additions & 3 deletions internal/printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
19 changes: 14 additions & 5 deletions internal/transform/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
29 changes: 17 additions & 12 deletions internal/transform/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand All @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface TransformOptions {
* @experimental
*/
renderScript?: boolean;
experimentalScriptOrder?: boolean;
}

export type ConvertToTSXOptions = Pick<
Expand Down
25 changes: 25 additions & 0 deletions packages/compiler/test/scripts/order.ts
Original file line number Diff line number Diff line change
@@ -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(
`
<script>console.log(1)</script>
<script>console.log(2)</script>`,
{
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();
13 changes: 7 additions & 6 deletions packages/compiler/test/styles/sass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Loading