From a90d99ee8cc3ad92d1b39d73df1f7301011ee970 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Mon, 19 Feb 2024 21:35:42 +0800 Subject: [PATCH] Add renderScript option (#969) --- .changeset/gold-apples-shout.md | 5 ++ cmd/astro-wasm/astro-wasm.go | 6 ++ internal/node.go | 4 ++ internal/parser.go | 2 + internal/printer/print-to-js.go | 21 +++++++ internal/printer/printer.go | 3 + internal/printer/printer_test.go | 81 ++++++++++++++++++++++++--- internal/transform/transform.go | 8 ++- packages/compiler/src/shared/types.ts | 6 ++ 9 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 .changeset/gold-apples-shout.md diff --git a/.changeset/gold-apples-shout.md b/.changeset/gold-apples-shout.md new file mode 100644 index 000000000..f80324b0f --- /dev/null +++ b/.changeset/gold-apples-shout.md @@ -0,0 +1,5 @@ +--- +'@astrojs/compiler': minor +--- + +Adds a new `renderScript` option to render non-inline script tags using a `renderScript` function from `internalURL`, instead of stripping the script entirely diff --git a/cmd/astro-wasm/astro-wasm.go b/cmd/astro-wasm/astro-wasm.go index ce652b850..2930d0ad7 100644 --- a/cmd/astro-wasm/astro-wasm.go +++ b/cmd/astro-wasm/astro-wasm.go @@ -126,6 +126,11 @@ func makeTransformOptions(options js.Value) transform.TransformOptions { scopedStyleStrategy = "where" } + renderScript := false + if jsBool(options.Get("renderScript")) { + renderScript = true + } + return transform.TransformOptions{ Filename: filename, NormalizedFilename: normalizedFilename, @@ -139,6 +144,7 @@ func makeTransformOptions(options js.Value) transform.TransformOptions { ScopedStyleStrategy: scopedStyleStrategy, TransitionsAnimationURL: transitionsAnimationURL, AnnotateSourceFile: annotateSourceFile, + RenderScript: renderScript, } } diff --git a/internal/node.go b/internal/node.go index 94fc37ea2..d0a4c59df 100644 --- a/internal/node.go +++ b/internal/node.go @@ -85,6 +85,9 @@ type Node struct { Expression bool Transition bool TransitionScope string + // Whether this node is a script that should be rendered with the `renderScript` runtime, + // so that the runtime handles how this is bundled and referenced. + HandledScript bool Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node @@ -233,6 +236,7 @@ func (n *Node) clone() *Node { Attr: make([]Attribute, len(n.Attr)), CustomElement: n.CustomElement, Component: n.Component, + HandledScript: n.HandledScript, Loc: n.Loc, } copy(m.Attr, n.Attr) diff --git a/internal/parser.go b/internal/parser.go index 2e72d3b7c..18275683a 100644 --- a/internal/parser.go +++ b/internal/parser.go @@ -389,6 +389,7 @@ func (p *parser) addExpression() { Expression: true, Component: false, CustomElement: false, + HandledScript: false, Loc: p.generateLoc(), }) } @@ -433,6 +434,7 @@ func (p *parser) addElement() { Fragment: isFragment(p.tok.Data), Component: isComponent(p.tok.Data), CustomElement: isCustomElement(p.tok.Data), + HandledScript: false, Loc: p.generateLoc(), }) } diff --git a/internal/printer/print-to-js.go b/internal/printer/print-to-js.go index c43b0d8b2..6fda4411b 100644 --- a/internal/printer/print-to-js.go +++ b/internal/printer/print-to-js.go @@ -62,6 +62,7 @@ type RenderOptions struct { cssLen int opts transform.TransformOptions printedMaybeHead *bool + scriptCount *int } type ExtractedStatement struct { @@ -71,6 +72,7 @@ type ExtractedStatement struct { func printToJs(p *printer, n *Node, cssLen int, opts transform.TransformOptions) PrintResult { printedMaybeHead := false + scriptCount := 0 render1(p, n, RenderOptions{ cssLen: cssLen, isRoot: true, @@ -78,6 +80,7 @@ func printToJs(p *printer, n *Node, cssLen int, opts transform.TransformOptions) depth: 0, opts: opts, printedMaybeHead: &printedMaybeHead, + scriptCount: &scriptCount, }) return PrintResult{ @@ -136,6 +139,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) } @@ -253,6 +257,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) if len(n.Loc) > 1 { p.addSourceMapping(loc.Loc{Start: n.Loc[1].Start - 3}) @@ -346,6 +351,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) // Print the closing of a tagged render function after @@ -369,6 +375,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { isClientOnly := isComponent && transform.HasAttr(n, "client:only") isSlot := n.DataAtom == atom.Slot isImplicit := false + isHandledScript := n.HandledScript for _, a := range n.Attr { if isSlot && a.Key == "is:inline" { isSlot = false @@ -386,6 +393,14 @@ func render1(p *printer, n *Node, opts RenderOptions) { p.print(fmt.Sprintf("${%s(%s,'%s',", RENDER_COMPONENT, RESULT, n.Data)) case isSlot: p.print(fmt.Sprintf("${%s(%s,%s[", RENDER_SLOT, RESULT, SLOTS)) + case isHandledScript: + // import '/src/pages/index.astro?astro&type=script&index=0&lang.ts'; + scriptUrl := fmt.Sprintf("%s?astro&type=script&index=%v&lang.ts", p.opts.Filename, *opts.scriptCount) + resolvedScriptUrl := transform.ResolveIdForMatch(scriptUrl, &p.opts) + escapedScriptUrl := escapeDoubleQuote(resolvedScriptUrl) + p.print(fmt.Sprintf("${%s(%s,\"%s\")}", RENDER_SCRIPT, RESULT, escapedScriptUrl)) + *opts.scriptCount++ + return case isImplicit: // do nothing default: @@ -528,6 +543,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) } } @@ -562,6 +578,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) } p.printTemplateLiteralClose() @@ -696,6 +713,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) } p.printTemplateLiteralClose() @@ -717,6 +735,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) if child.Type == ElementNode { p.printTemplateLiteralClose() @@ -736,6 +755,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) } p.printTemplateLiteralClose() @@ -748,6 +768,7 @@ func render1(p *printer, n *Node, opts RenderOptions) { opts: opts.opts, cssLen: opts.cssLen, printedMaybeHead: opts.printedMaybeHead, + scriptCount: opts.scriptCount, }) } } diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 5820288b6..89e687014 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -54,6 +54,7 @@ var SPREAD_ATTRIBUTES = "$$spreadAttributes" var DEFINE_STYLE_VARS = "$$defineStyleVars" var DEFINE_SCRIPT_VARS = "$$defineScriptVars" var CREATE_METADATA = "$$createMetadata" +var RENDER_SCRIPT = "$$renderScript" var METADATA = "$$metadata" var RESULT = "$$result" var SLOTS = "$$slots" @@ -137,6 +138,8 @@ func (p *printer) printInternalImports(importSpecifier string, opts *RenderOptio p.print("renderTransition as " + RENDER_TRANSITION + ",\n ") p.addNilSourceMapping() p.print("createTransitionScope as " + CREATE_TRANSITION_SCOPE + ",\n ") + p.addNilSourceMapping() + p.print("renderScript as " + RENDER_SCRIPT + ",\n ") // Only needed if using fallback `resolvePath` as it calls `$$metadata.resolvePath` if opts.opts.ResolvePath == nil { diff --git a/internal/printer/printer_test.go b/internal/printer/printer_test.go index ed24a6827..4a43ce71e 100644 --- a/internal/printer/printer_test.go +++ b/internal/printer/printer_test.go @@ -32,6 +32,7 @@ var INTERNAL_IMPORTS = fmt.Sprintf("import {\n %s\n} from \"%s\";\n", strings.J "defineScriptVars as " + DEFINE_SCRIPT_VARS, "renderTransition as " + RENDER_TRANSITION, "createTransitionScope as " + CREATE_TRANSITION_SCOPE, + "renderScript as " + RENDER_SCRIPT, "createMetadata as " + CREATE_METADATA, }, ",\n "), "http://localhost:3000/") var PRELUDE = fmt.Sprintf(`const $$Component = %s(async ($$result, $$props, %s) => { @@ -77,12 +78,13 @@ type metadata struct { } type testcase struct { - name string - source string - only bool - transitions bool - filename string - want want + name string + source string + only bool + transitions bool + transformOptions transform.TransformOptions + filename string + want want } type jsonTestcase struct { @@ -1483,6 +1485,69 @@ import Widget2 from '../components/Widget2.astro';`}, code: `${$$maybeRenderHead($$result)}
`, }, }, + { + name: "script (renderScript: true)", + source: `
`, + transformOptions: transform.TransformOptions{ + RenderScript: true, + }, + filename: "/src/pages/index.astro", + want: want{ + metadata: metadata{hoisted: []string{fmt.Sprintf(`{ type: 'inline', value: %sconsole.log("Hello");%s }`, BACKTICK, BACKTICK)}}, + code: `${$$maybeRenderHead($$result)}
${$$renderScript($$result,"/src/pages/index.astro?astro&type=script&index=0&lang.ts")}
`, + }, + }, + { + name: "script multiple (renderScript: true)", + source: `
`, + transformOptions: transform.TransformOptions{ + RenderScript: true, + }, + filename: "/src/pages/index.astro", + want: want{ + metadata: metadata{ + hoisted: []string{ + fmt.Sprintf(`{ type: 'inline', value: %sconsole.log("World");%s }`, BACKTICK, BACKTICK), + fmt.Sprintf(`{ type: 'inline', value: %sconsole.log("Hello");%s }`, BACKTICK, BACKTICK), + }, + }, + code: `${$$maybeRenderHead($$result)}
${$$renderScript($$result,"/src/pages/index.astro?astro&type=script&index=0&lang.ts")}${$$renderScript($$result,"/src/pages/index.astro?astro&type=script&index=1&lang.ts")}
`, + }, + }, + { + name: "script external (renderScript: true)", + source: `
`, + transformOptions: transform.TransformOptions{ + RenderScript: true, + }, + filename: "/src/pages/index.astro", + want: want{ + metadata: metadata{hoisted: []string{`{ type: 'external', src: './hello.js' }`}}, + code: `${$$maybeRenderHead($$result)}
${$$renderScript($$result,"/src/pages/index.astro?astro&type=script&index=0&lang.ts")}
`, + }, + }, + { + name: "script inline (renderScript: true)", + source: `
`, + transformOptions: transform.TransformOptions{ + RenderScript: true, + }, + want: want{ + code: `${$$maybeRenderHead($$result)}
`, + }, + }, + { + name: "script mixed handled and inline (renderScript: true)", + source: `
`, + transformOptions: transform.TransformOptions{ + RenderScript: true, + }, + filename: "/src/pages/index.astro", + want: want{ + metadata: metadata{hoisted: []string{fmt.Sprintf(`{ type: 'inline', value: %sconsole.log("Hello");%s }`, BACKTICK, BACKTICK)}}, + code: `${$$maybeRenderHead($$result)}
${$$renderScript($$result,"/src/pages/index.astro?astro&type=script&index=0&lang.ts")}
`, + }, + }, { name: "text after title expression", source: `a {expr} b`, @@ -3456,8 +3521,10 @@ const items = ["Dog", "Cat", "Platipus"]; hash := astro.HashString(code) transform.ExtractStyles(doc) + // combine from tt.transformOptions transformOptions := transform.TransformOptions{ - Scope: hash, + Scope: hash, + 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{ diff --git a/internal/transform/transform.go b/internal/transform/transform.go index ce5abcf04..a1299f3a7 100644 --- a/internal/transform/transform.go +++ b/internal/transform/transform.go @@ -31,6 +31,7 @@ type TransformOptions struct { ResolvePath func(string) string PreprocessStyle interface{} AnnotateSourceFile bool + RenderScript bool } func Transform(doc *astro.Node, opts TransformOptions, h *handler.Handler) *astro.Node { @@ -81,8 +82,10 @@ func Transform(doc *astro.Node, opts TransformOptions, h *handler.Handler) *astr NormalizeSetDirectives(doc, h) // Important! Remove scripts from original location *after* walking the doc - for _, script := range doc.Scripts { - script.Parent.RemoveChild(script) + if !opts.RenderScript { + for _, script := range doc.Scripts { + script.Parent.RemoveChild(script) + } } // If we've emptied out all the nodes, this was a Fragment that only contained hoisted elements @@ -389,6 +392,7 @@ func ExtractScript(doc *astro.Node, n *astro.Node, opts *TransformOptions, h *ha // prepend node to maintain authored order if shouldAdd { doc.Scripts = append([]*astro.Node{n}, doc.Scripts...) + n.HandledScript = true } } else { for _, attr := range n.Attr { diff --git a/packages/compiler/src/shared/types.ts b/packages/compiler/src/shared/types.ts index 6272dd8bc..8e2110805 100644 --- a/packages/compiler/src/shared/types.ts +++ b/packages/compiler/src/shared/types.ts @@ -57,6 +57,12 @@ export interface TransformOptions { resolvePath?: (specifier: string) => Promise; preprocessStyle?: (content: string, attrs: Record) => null | Promise; annotateSourceFile?: boolean; + /** + * Render script tags to be processed (e.g. script tags that have no attributes or only a `src` attribute) + * using a `renderScript` function from `internalURL`, instead of stripping the script entirely. + * @experimental + */ + renderScript?: boolean; } export type ConvertToTSXOptions = Pick;