diff --git a/.changeset/stale-steaks-brush.md b/.changeset/stale-steaks-brush.md new file mode 100644 index 000000000..e1dd493dd --- /dev/null +++ b/.changeset/stale-steaks-brush.md @@ -0,0 +1,21 @@ +--- +'@astrojs/compiler': minor +--- + +Add a new `annotateSourceFile` option. This option makes it so the compiler will annotate every element with its source file location. This is notably useful for dev tools to be able to provide features like a "Open in editor" button. This option is disabled by default. + +```html +
+ hello world +
+``` + +Results in: + +```html +
+ hello world +
+``` + +In Astro, this option is enabled only in development mode. diff --git a/cmd/astro-wasm/astro-wasm.go b/cmd/astro-wasm/astro-wasm.go index b9d6288d8..dda3bdd00 100644 --- a/cmd/astro-wasm/astro-wasm.go +++ b/cmd/astro-wasm/astro-wasm.go @@ -95,11 +95,17 @@ func makeTransformOptions(options js.Value) transform.TransformOptions { if jsBool(options.Get("resultScopedSlot")) { scopedSlot = true } + transitionsAnimationURL := jsString(options.Get("transitionsAnimationURL")) if transitionsAnimationURL == "" { transitionsAnimationURL = "astro/components/viewtransitions.css" } + annotateSourceFile := false + if jsBool(options.Get("annotateSourceFile")) { + annotateSourceFile = true + } + var resolvePath any = options.Get("resolvePath") var resolvePathFn func(string) string if resolvePath.(js.Value).Type() == js.TypeFunction { @@ -132,6 +138,7 @@ func makeTransformOptions(options js.Value) transform.TransformOptions { ResultScopedSlot: scopedSlot, ScopedStyleStrategy: scopedStyleStrategy, TransitionsAnimationURL: transitionsAnimationURL, + AnnotateSourceFile: annotateSourceFile, } } diff --git a/internal/printer/print-to-js.go b/internal/printer/print-to-js.go index fcf644104..ef15ad866 100644 --- a/internal/printer/print-to-js.go +++ b/internal/printer/print-to-js.go @@ -9,6 +9,7 @@ import ( "fmt" "sort" "strings" + "unicode" . "github.com/withastro/compiler/internal" "github.com/withastro/compiler/internal/handler" @@ -448,6 +449,25 @@ func render1(p *printer, n *Node, opts RenderOptions) { // Note: if we encounter "slot" NOT inside a component, that's fine // These should be preserved in the output p.printAttribute(a, n) + } else if a.Key == "data-astro-source-file" { + p.printAttribute(a, n) + var l []int + if n.FirstChild != nil && len(n.FirstChild.Loc) > 0 { + start := n.FirstChild.Loc[0].Start + if n.FirstChild.Type == TextNode { + start += len(n.Data) - len(strings.TrimLeftFunc(n.Data, unicode.IsSpace)) + } + l = p.builder.GetLineAndColumnForLocation(loc.Loc{Start: start}) + } else if len(n.Loc) > 0 { + l = p.builder.GetLineAndColumnForLocation(n.Loc[0]) + } + if len(l) > 0 { + p.printAttribute(Attribute{ + Key: "data-astro-source-loc", + Type: QuotedAttribute, + Val: fmt.Sprintf("%d:%d", l[0], l[1]), + }, n) + } p.addSourceMapping(n.Loc[0]) } else { p.printAttribute(a, n) diff --git a/internal/transform/scope-html.go b/internal/transform/scope-html.go index 15f0d1dfe..7094cbceb 100644 --- a/internal/transform/scope-html.go +++ b/internal/transform/scope-html.go @@ -5,6 +5,7 @@ import ( "strings" astro "github.com/withastro/compiler/internal" + "golang.org/x/net/html/atom" ) func ScopeElement(n *astro.Node, opts TransformOptions) { @@ -27,6 +28,14 @@ func AddDefineVars(n *astro.Node, values []string) bool { return false } +func AnnotateElement(n *astro.Node, opts TransformOptions) { + if n.Type == astro.ElementNode && !n.Component && !n.Fragment { + if _, noScope := NeverScopedElements[n.Data]; !noScope { + annotateElement(n, opts) + } + } +} + var NeverScopedElements map[string]bool = map[string]bool{ "Fragment": true, "base": true, @@ -48,6 +57,17 @@ var NeverScopedSelectors map[string]bool = map[string]bool{ ":root": true, } +func annotateElement(n *astro.Node, opts TransformOptions) { + if n.DataAtom == atom.Html { + return + } + n.Attr = append(n.Attr, astro.Attribute{ + Key: "data-astro-source-file", + Type: astro.QuotedAttribute, + Val: opts.Filename, + }) +} + func injectDefineVars(n *astro.Node, values []string) { definedVars := "$$definedVars" for i, attr := range n.Attr { diff --git a/internal/transform/transform.go b/internal/transform/transform.go index 9cf788d5c..f5388344d 100644 --- a/internal/transform/transform.go +++ b/internal/transform/transform.go @@ -30,6 +30,7 @@ type TransformOptions struct { TransitionsAnimationURL string ResolvePath func(string) string PreprocessStyle interface{} + AnnotateSourceFile bool } func Transform(doc *astro.Node, opts TransformOptions, h *handler.Handler) *astro.Node { @@ -59,6 +60,9 @@ func Transform(doc *astro.Node, opts TransformOptions, h *handler.Handler) *astr if n.DataAtom == a.Head && !IsImplicitNode(n) { doc.ContainsHead = true } + if opts.AnnotateSourceFile { + AnnotateElement(n, opts) + } }) if len(definedVars) > 0 && !didAddDefinedVars { for _, style := range doc.Styles { diff --git a/internal/transform/transform_test.go b/internal/transform/transform_test.go index 78ecf9718..e85ee351c 100644 --- a/internal/transform/transform_test.go +++ b/internal/transform/transform_test.go @@ -471,3 +471,48 @@ func TestCompactTransform(t *testing.T) { }) } } + +func TestAnnotation(t *testing.T) { + tests := []struct { + name string + source string + want string + }{ + { + name: "basic", + source: `
Hello world!
`, + want: `
Hello world!
`, + }, + { + name: "no components", + source: `Hello world!`, + want: `Hello world!`, + }, + { + name: "injects root", + source: ``, + want: ``, + }, + } + var b strings.Builder + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b.Reset() + doc, err := astro.Parse(strings.NewReader(tt.source)) + 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", + NormalizedFilename: "/src/pages/index.astro", + }, h) + astro.PrintToSource(&b, doc) + got := strings.TrimSpace(b.String()) + if tt.want != got { + t.Errorf("\nFAIL: %s\n want: %s\n got: %s", tt.name, tt.want, got) + } + }) + } +} diff --git a/packages/compiler/src/shared/types.ts b/packages/compiler/src/shared/types.ts index 360d10240..75af6f0a5 100644 --- a/packages/compiler/src/shared/types.ts +++ b/packages/compiler/src/shared/types.ts @@ -56,6 +56,7 @@ export interface TransformOptions { transitionsAnimationURL?: string; resolvePath?: (specifier: string) => Promise; preprocessStyle?: (content: string, attrs: Record) => null | Promise; + annotateSourceFile?: boolean; } export type ConvertToTSXOptions = Pick;