@@ -20,14 +20,20 @@ func getTSXPrefix() string {
2020 return "/* @jsxImportSource astro */\n \n "
2121}
2222
23- func PrintToTSX (sourcetext string , n * Node , opts transform.TransformOptions , h * handler.Handler ) PrintResult {
23+ type TSXOptions struct {
24+ IncludeScripts bool
25+ IncludeStyles bool
26+ }
27+
28+ func PrintToTSX (sourcetext string , n * Node , opts TSXOptions , transformOpts transform.TransformOptions , h * handler.Handler ) PrintResult {
2429 p := & printer {
2530 sourcetext : sourcetext ,
26- opts : opts ,
31+ opts : transformOpts ,
2732 builder : sourcemap .MakeChunkBuilder (nil , sourcemap .GenerateLineOffsetTables (sourcetext , len (strings .Split (sourcetext , "\n " )))),
2833 }
2934 p .print (getTSXPrefix ())
30- renderTsx (p , n )
35+ renderTsx (p , n , & opts )
36+
3137 return PrintResult {
3238 Output : p .output ,
3339 SourceMapChunk : p .builder .GenerateChunk (p .output ),
@@ -36,14 +42,147 @@ func PrintToTSX(sourcetext string, n *Node, opts transform.TransformOptions, h *
3642}
3743
3844type TSXRanges struct {
39- Frontmatter loc.TSXRange `js:"frontmatter"`
40- Body loc.TSXRange `js:"body"`
45+ Frontmatter loc.TSXRange `js:"frontmatter"`
46+ Body loc.TSXRange `js:"body"`
47+ Scripts []TSXExtractedTag `js:"scripts"`
48+ Styles []TSXExtractedTag `js:"styles"`
49+ }
50+
51+ var htmlEvents = map [string ]bool {
52+ "onabort" : true ,
53+ "onafterprint" : true ,
54+ "onauxclick" : true ,
55+ "onbeforematch" : true ,
56+ "onbeforeprint" : true ,
57+ "onbeforeunload" : true ,
58+ "onblur" : true ,
59+ "oncancel" : true ,
60+ "oncanplay" : true ,
61+ "oncanplaythrough" : true ,
62+ "onchange" : true ,
63+ "onclick" : true ,
64+ "onclose" : true ,
65+ "oncontextlost" : true ,
66+ "oncontextmenu" : true ,
67+ "oncontextrestored" : true ,
68+ "oncopy" : true ,
69+ "oncuechange" : true ,
70+ "oncut" : true ,
71+ "ondblclick" : true ,
72+ "ondrag" : true ,
73+ "ondragend" : true ,
74+ "ondragenter" : true ,
75+ "ondragleave" : true ,
76+ "ondragover" : true ,
77+ "ondragstart" : true ,
78+ "ondrop" : true ,
79+ "ondurationchange" : true ,
80+ "onemptied" : true ,
81+ "onended" : true ,
82+ "onerror" : true ,
83+ "onfocus" : true ,
84+ "onformdata" : true ,
85+ "onhashchange" : true ,
86+ "oninput" : true ,
87+ "oninvalid" : true ,
88+ "onkeydown" : true ,
89+ "onkeypress" : true ,
90+ "onkeyup" : true ,
91+ "onlanguagechange" : true ,
92+ "onload" : true ,
93+ "onloadeddata" : true ,
94+ "onloadedmetadata" : true ,
95+ "onloadstart" : true ,
96+ "onmessage" : true ,
97+ "onmessageerror" : true ,
98+ "onmousedown" : true ,
99+ "onmouseenter" : true ,
100+ "onmouseleave" : true ,
101+ "onmousemove" : true ,
102+ "onmouseout" : true ,
103+ "onmouseover" : true ,
104+ "onmouseup" : true ,
105+ "onoffline" : true ,
106+ "ononline" : true ,
107+ "onpagehide" : true ,
108+ "onpageshow" : true ,
109+ "onpaste" : true ,
110+ "onpause" : true ,
111+ "onplay" : true ,
112+ "onplaying" : true ,
113+ "onpopstate" : true ,
114+ "onprogress" : true ,
115+ "onratechange" : true ,
116+ "onrejectionhandled" : true ,
117+ "onreset" : true ,
118+ "onresize" : true ,
119+ "onscroll" : true ,
120+ "onscrollend" : true ,
121+ "onsecuritypolicyviolation" : true ,
122+ "onseeked" : true ,
123+ "onseeking" : true ,
124+ "onselect" : true ,
125+ "onslotchange" : true ,
126+ "onstalled" : true ,
127+ "onstorage" : true ,
128+ "onsubmit" : true ,
129+ "onsuspend" : true ,
130+ "ontimeupdate" : true ,
131+ "ontoggle" : true ,
132+ "onunhandledrejection" : true ,
133+ "onunload" : true ,
134+ "onvolumechange" : true ,
135+ "onwaiting" : true ,
136+ "onwheel" : true ,
137+ }
138+
139+ func getScriptTypeForNode (n Node ) string {
140+ if n .Attr == nil || len (n .Attr ) == 0 {
141+ return "processed-module"
142+ }
143+
144+ // If the script tag has `type="module"`, it's not processed, but it's still a module
145+ for _ , attr := range n .Attr {
146+ if attr .Key == "type" {
147+ if strings .Contains (attr .Val , "module" ) {
148+ return "module"
149+ }
150+
151+ if ScriptJSONMimeTypes [strings .ToLower (attr .Val )] {
152+ return "json"
153+ }
154+ }
155+
156+ }
157+
158+ // Otherwise, it's an inline script
159+ return "inline"
160+ }
161+
162+ type TSXExtractedTag struct {
163+ Loc loc.TSXRange `js:"position"`
164+ Type string `js:"type"`
165+ Content string `js:"content"`
41166}
42167
43168func isScript (p * astro.Node ) bool {
44169 return p .DataAtom == atom .Script
45170}
46171
172+ func isStyle (p * astro.Node ) bool {
173+ return p .DataAtom == atom .Style
174+ }
175+
176+ // Has is:raw attribute
177+ func isRawText (p * astro.Node ) bool {
178+ for _ , a := range p .Attr {
179+ if a .Key == "is:raw" {
180+ return true
181+ }
182+ }
183+ return false
184+ }
185+
47186var ScriptMimeTypes map [string ]bool = map [string ]bool {
48187 "module" : true ,
49188 "text/typescript" : true ,
@@ -52,6 +191,13 @@ var ScriptMimeTypes map[string]bool = map[string]bool{
52191 "application/node" : true ,
53192}
54193
194+ var ScriptJSONMimeTypes map [string ]bool = map [string ]bool {
195+ "application/json" : true ,
196+ "application/ld+json" : true ,
197+ "importmap" : true ,
198+ "speculationrules" : true ,
199+ }
200+
55201// This is not perfect (as in, you wouldn't use this to make a spec compliant parser), but it's good enough
56202// for the real world. Thankfully, JSX is also a bit more lax than JavaScript, so we can spare some work.
57203func isValidTSXAttribute (a Attribute ) bool {
@@ -95,20 +241,35 @@ type TextType uint32
95241
96242const (
97243 RawText TextType = iota
244+ Text
98245 ScriptText
246+ JsonScriptText
247+ StyleText
99248)
100249
101250func getTextType (n * astro.Node ) TextType {
102251 if script := n .Closest (isScript ); script != nil {
103252 attr := astro .GetAttribute (script , "type" )
104- if attr == nil || ( attr != nil && ScriptMimeTypes [strings .ToLower (attr .Val )]) {
253+ if attr == nil || ScriptMimeTypes [strings .ToLower (attr .Val )] {
105254 return ScriptText
106255 }
256+
257+ if attr != nil && ScriptJSONMimeTypes [strings .ToLower (attr .Val )] {
258+ return JsonScriptText
259+ }
260+ }
261+ if style := n .Closest (isStyle ); style != nil {
262+ return StyleText
263+ }
264+
265+ if n .Closest (isRawText ) != nil {
266+ return RawText
107267 }
108- return RawText
268+
269+ return Text
109270}
110271
111- func renderTsx (p * printer , n * Node ) {
272+ func renderTsx (p * printer , n * Node , o * TSXOptions ) {
112273 // Root of the document, print all children
113274 if n .Type == DocumentNode {
114275 source := []byte (p .sourcetext )
@@ -147,7 +308,7 @@ func renderTsx(p *printer, n *Node) {
147308
148309 hasChildren = true
149310 }
150- renderTsx (p , c )
311+ renderTsx (p , c , o )
151312 }
152313 p .addSourceMapping (loc.Loc {Start : len (p .sourcetext )})
153314 p .print ("\n " )
@@ -206,7 +367,7 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s`, propsI
206367 }
207368 p .printTextWithSourcemap (c .Data , c .Loc [0 ])
208369 } else {
209- renderTsx (p , c )
370+ renderTsx (p , c , o )
210371 }
211372 }
212373 if n .FirstChild != nil {
@@ -224,22 +385,27 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s`, propsI
224385
225386 switch n .Type {
226387 case TextNode :
227- if getTextType (n ) == ScriptText {
228- p .addNilSourceMapping ()
229- p .print ("\n {() => {" )
230- p .printTextWithSourcemap (n .Data , n .Loc [0 ])
388+ textType := getTextType (n )
389+ if textType == ScriptText {
231390 p .addNilSourceMapping ()
232- p .print ("}}\n " )
391+ if o .IncludeScripts {
392+ p .print ("\n {() => {" )
393+ p .printTextWithSourcemap (n .Data , n .Loc [0 ])
394+ p .addNilSourceMapping ()
395+ p .print ("}}\n " )
396+ }
233397 p .addSourceMapping (loc.Loc {Start : n .Loc [0 ].Start + len (n .Data )})
234- return
235- } else if strings .ContainsAny (n .Data , "{}<>'\" " ) && n .Data [0 ] != '<' {
236- p .addNilSourceMapping ()
237- p .print ("{`" )
238- p .printTextWithSourcemap (escapeText (n .Data ), n .Loc [0 ])
398+ } else if textType == StyleText || textType == JsonScriptText || textType == RawText {
239399 p .addNilSourceMapping ()
240- p .print ("`}" )
400+ if (textType == StyleText && o .IncludeStyles ) || textType == JsonScriptText || textType == RawText {
401+ p .print ("{`" )
402+ p .printTextWithSourcemap (escapeText (n .Data ), n .Loc [0 ])
403+ p .addNilSourceMapping ()
404+ p .print ("`}" )
405+ }
406+ p .addSourceMapping (loc.Loc {Start : n .Loc [0 ].Start + len (n .Data )})
241407 } else {
242- p .printTextWithSourcemap (n .Data , n .Loc [0 ])
408+ p .printEscapedJSXTextWithSourcemap (n .Data , n .Loc [0 ])
243409 }
244410 return
245411 case ElementNode :
@@ -284,7 +450,7 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s`, propsI
284450 p .addNilSourceMapping ()
285451 p .print (`<Fragment>` )
286452 }
287- renderTsx (p , c )
453+ renderTsx (p , c , o )
288454 if c .NextSibling == nil || c .NextSibling .Type == TextNode {
289455 p .addNilSourceMapping ()
290456 p .print (`</Fragment>` )
@@ -310,7 +476,7 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s`, propsI
310476 if isImplicit {
311477 // Render any child nodes
312478 for c := n .FirstChild ; c != nil ; c = c .NextSibling {
313- renderTsx (p , c )
479+ renderTsx (p , c , o )
314480 }
315481 return
316482 }
@@ -360,6 +526,12 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s`, propsI
360526 p .print (`"` )
361527 endLoc = a .ValLoc .Start
362528 }
529+ if _ , ok := htmlEvents [a .Key ]; ok {
530+ p .addTSXScript (a .ValLoc .Start - p .bytesToSkip , endLoc - p .bytesToSkip , a .Val , "event-attribute" )
531+ }
532+ if a .Key == "style" {
533+ p .addTSXStyle (a .ValLoc .Start - p .bytesToSkip , endLoc - p .bytesToSkip , a .Val , "style-attribute" )
534+ }
363535 case astro .EmptyAttribute :
364536 p .print (a .Key )
365537 endLoc = a .KeyLoc .Start + len (a .Key )
@@ -521,15 +693,27 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s, typeof %s`, propsI
521693 }
522694 p .print (">" )
523695
696+ startTagEnd := endLoc - p .bytesToSkip
697+
524698 // Render any child nodes
525699 for c := n .FirstChild ; c != nil ; c = c .NextSibling {
526- renderTsx (p , c )
700+ renderTsx (p , c , o )
527701 if len (c .Loc ) > 1 {
528702 endLoc = c .Loc [1 ].Start + len (c .Data ) + 1
529703 } else if len (c .Loc ) == 1 {
530704 endLoc = c .Loc [0 ].Start + len (c .Data )
531705 }
532706 }
707+
708+ if n .FirstChild != nil && (n .DataAtom == atom .Script || n .DataAtom == atom .Style ) {
709+ if n .DataAtom == atom .Script {
710+ p .addTSXScript (startTagEnd , endLoc - p .bytesToSkip , n .FirstChild .Data , getScriptTypeForNode (* n ))
711+ }
712+ if n .DataAtom == atom .Style {
713+ p .addTSXStyle (startTagEnd , endLoc - p .bytesToSkip , n .FirstChild .Data , "tag" )
714+ }
715+ }
716+
533717 // Special case because of trailing expression close in scripts
534718 if n .DataAtom == atom .Script {
535719 p .printf ("</%s>" , n .Data )
0 commit comments