Skip to content

Commit 0be58ab

Browse files
TSX Sourcemaps (#518)
* Add tests for sourcemaps * chore: update lockfile * wip: improved tsx sourcemaps * feat(tsx): update sourcemappings * fix: remove print from token_test * chore: fix lint * chore: update loc tests * feat(sourcemaps): improve test coverage * chore: update wasm * chore: update wasm * chore: add changeset * fix(tsx): more accurate frontmatter sourcemaps * test: better debug output * fix(tsx): frontmatter mapping * fix(types): expose SourceMap type * fix(tsx): end tag position * fix(tsx): attribute space mapping * fix(tsx): always print prefix/suffix for scripts * feat(tsx): move semicolon to body open * feat(tsx): fix expression off-by-one-mapping, double end tag mapping * fix(tsx): improve expression attribute mapping * fix(tsx): improved handling of incomplete frontmatter * feat(tsx): support typed props, update tests * fix(tsx): ensure no props and scoped props are not supported * test(tsx): add polymorphic test * chore: format * fix(tsx): prop detection edge case * fix(tsx): better handling for unclosed tags * fix(tsx): always wrap with Fragment * fix(tsx): map newlines * fix: lint * chore: add comments * fix: shorthand attribute mapping * fix(tsx): update semicolon logic * fix(tsx): support template literal attributes * fix(tsx): better mapping for shorthand attribute * fix(tsx): shorthand = mapping * fix(tsx): improve script mapping * fix(tsx): improve script mapping * fix(tsx): improve mapping for newlines, end tags; add virtual astro:runtime mapping * fix(tsx): ensure last newline and eof are mapped * fix(tsx): update end tag mappings * chore: format * fix(tsx): attribute mapping * test(tsx): update test output * fix: windows handle EOF * chore: run wasm tests on Windows Co-authored-by: Princesseuh <princssdev@gmail.com> Co-authored-by: Nate Moore <nate@astro.build>
1 parent ed7bb47 commit 0be58ab

27 files changed

Lines changed: 1659 additions & 741 deletions

File tree

.changeset/twelve-waves-help.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/compiler': minor
3+
---
4+
5+
Improve sourcemap support for TSX output

.github/workflows/ci.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Test
22

33
on:
44
push:
5-
branches: ['main', 'next']
5+
branches: ["main", "next"]
66
pull_request:
7-
branches: ['main', 'next']
7+
branches: ["main", "next"]
88

99
# Automatically cancel in-progress actions on the same branch
1010
concurrency:
@@ -26,7 +26,10 @@ jobs:
2626
run: go test -v ./internal/...
2727

2828
test-wasm:
29-
runs-on: ubuntu-latest
29+
strategy:
30+
matrix:
31+
OS: [ubuntu-latest, windows-latest]
32+
runs-on: ${{ matrix.OS }}
3033
steps:
3134
- uses: actions/checkout@v2
3235

@@ -42,7 +45,7 @@ jobs:
4245
uses: actions/setup-node@v2
4346
with:
4447
node-version: 14
45-
cache: 'pnpm'
48+
cache: "pnpm"
4649

4750
- name: Build WASM
4851
run: make wasm
@@ -75,6 +78,6 @@ jobs:
7578
- uses: actions/setup-node@v2
7679
with:
7780
node-version: 14
78-
cache: 'pnpm'
81+
cache: "pnpm"
7982
- run: pnpm install
8083
- run: pnpm run lint

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ node_modules
44
*.wasm
55
/astro
66
debug.test
7+
packages/compiler/sourcemap.mjs

cmd/astro-wasm/astro-wasm.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,16 @@ func ConvertToTSX() interface{} {
223223
}
224224
result := printer.PrintToTSX(source, doc, transformOptions)
225225

226+
sourcemapString := createSourceMapString(source, result, transformOptions)
227+
code := string(result.Output)
228+
if transformOptions.SourceMap != "external" {
229+
inlineSourcemap := `//# sourceMappingURL=data:application/json;charset=utf-8;base64,` + base64.StdEncoding.EncodeToString([]byte(sourcemapString))
230+
code += "\n" + inlineSourcemap
231+
}
232+
226233
return vert.ValueOf(TSXResult{
227-
Code: string(result.Output),
228-
Map: createSourceMapString(source, result, transformOptions),
234+
Code: code,
235+
Map: sourcemapString,
229236
})
230237
})
231238
}
@@ -328,7 +335,7 @@ func Transform() interface{} {
328335
output = append(output, []byte(strings.TrimSpace(node.FirstChild.Data))...)
329336
}
330337
sourcemap := fmt.Sprintf(
331-
`{ "version": 3, "sources": ["%s"], "sourcesContent": [%s], "mappings": "%s", "names": [] }`,
338+
`{ "version": 3, "sources": ["%s", "astro:runtime"], "sourcesContent": [%s, "Please open an issue: https://astro.build/issues"], "mappings": "%s", "names": [] }`,
332339
transformOptions.Filename,
333340
string(sourcesContent),
334341
string(builder.GenerateChunk(output).Buffer),
@@ -408,8 +415,8 @@ func createSourceMapString(source string, result printer.PrintResult, transformO
408415
}
409416
return fmt.Sprintf(`{
410417
"version": 3,
411-
"sources": ["%s"],
412-
"sourcesContent": [%s],
418+
"sources": ["%s", "astro:runtime"],
419+
"sourcesContent": [%s, "Please open an issue: https://astro.build/issues"],
413420
"mappings": "%s",
414421
"names": []
415422
}`, sourcemap.Sources[0], sourcemap.SourcesContent[0], sourcemap.Mappings)

internal/js_scanner/js_scanner.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"fmt"
66
"io"
7+
"strings"
78

89
"github.com/iancoleman/strcase"
910
"github.com/tdewolff/parse/v2"
@@ -179,6 +180,162 @@ func HoistImports(source []byte) HoistedScripts {
179180
return HoistedScripts{Hoisted: imports, Body: body}
180181
}
181182

183+
type Props struct {
184+
Ident string
185+
Statement string
186+
Generics string
187+
}
188+
189+
func GetPropsType(source []byte) Props {
190+
defaultPropType := "Record<string, any>"
191+
ident := defaultPropType
192+
genericsIdents := make([]string, 0)
193+
generics := ""
194+
statement := ""
195+
196+
if !bytes.Contains(source, []byte("Props")) {
197+
return Props{
198+
Ident: ident,
199+
Statement: statement,
200+
Generics: generics,
201+
}
202+
}
203+
l := js.NewLexer(parse.NewInputBytes(source))
204+
i := 0
205+
pairs := make(map[byte]int)
206+
idents := make([]string, 0)
207+
208+
start := 0
209+
end := 0
210+
211+
outer:
212+
for {
213+
token, value := l.Next()
214+
215+
if token == js.DivToken || token == js.DivEqToken {
216+
lns := bytes.Split(source[i+1:], []byte{'\n'})
217+
if bytes.Contains(lns[0], []byte{'/'}) {
218+
token, value = l.RegExp()
219+
}
220+
}
221+
222+
if token == js.ErrorToken {
223+
if l.Err() != io.EOF {
224+
return Props{
225+
Ident: ident,
226+
}
227+
}
228+
break
229+
}
230+
231+
// Common delimeters. Track their length, then skip.
232+
if token == js.WhitespaceToken || token == js.LineTerminatorToken || token == js.SemicolonToken {
233+
i += len(value)
234+
continue
235+
}
236+
237+
if token == js.ExtendsToken {
238+
if bytes.Equal(value, []byte("extends")) {
239+
idents = append(idents, "extends")
240+
}
241+
i += len(value)
242+
continue
243+
}
244+
245+
if pairs['{'] == 0 && pairs['('] == 0 && pairs['['] == 0 && pairs['<'] == 1 && token == js.CommaToken {
246+
idents = make([]string, 0)
247+
i += len(value)
248+
continue
249+
}
250+
251+
if js.IsIdentifier(token) {
252+
if isKeyword(value) {
253+
i += len(value)
254+
continue
255+
}
256+
if pairs['<'] == 1 && pairs['{'] == 0 {
257+
foundExtends := false
258+
for _, id := range idents {
259+
if id == "extends" {
260+
foundExtends = true
261+
}
262+
}
263+
if !foundExtends {
264+
genericsIdents = append(genericsIdents, string(value))
265+
}
266+
i += len(value)
267+
continue
268+
}
269+
// Note: do not check that `pairs['{'] == 0` to support named imports
270+
if pairs['('] == 0 && pairs['['] == 0 && string(value) == "Props" {
271+
ident = "Props"
272+
}
273+
idents = append(idents, string(value))
274+
i += len(value)
275+
continue
276+
}
277+
278+
if bytes.ContainsAny(value, "<>") {
279+
if len(idents) > 0 && idents[len(idents)-1] == "Props" {
280+
start = i
281+
ident = "Props"
282+
idents = make([]string, 0)
283+
}
284+
for _, c := range value {
285+
if c == '<' {
286+
pairs['<']++
287+
i += len(value)
288+
continue
289+
}
290+
if c == '>' {
291+
pairs['<']--
292+
if pairs['<'] == 0 {
293+
end = i
294+
break outer
295+
}
296+
}
297+
}
298+
}
299+
300+
if token == js.QuestionToken || (pairs['{'] == 0 && token == js.ColonToken) {
301+
idents = make([]string, 0)
302+
idents = append(idents, "extends")
303+
}
304+
305+
// Track opening and closing braces
306+
if js.IsPunctuator(token) {
307+
if value[0] == '{' || value[0] == '(' || value[0] == '[' {
308+
idents = make([]string, 0)
309+
pairs[value[0]]++
310+
i += len(value)
311+
continue
312+
} else if value[0] == '}' {
313+
pairs['{']--
314+
if pairs['<'] == 0 && pairs['{'] == 0 && ident != defaultPropType {
315+
end = i
316+
break outer
317+
}
318+
} else if value[0] == ')' {
319+
pairs['(']--
320+
} else if value[0] == ']' {
321+
pairs['[']--
322+
}
323+
}
324+
325+
// Track our current position
326+
i += len(value)
327+
}
328+
if len(genericsIdents) > 0 && ident != defaultPropType {
329+
generics = fmt.Sprintf("<%s>", strings.Join(genericsIdents, ", "))
330+
statement = strings.TrimSpace(string(source[start:end]))
331+
}
332+
return Props{
333+
Ident: ident,
334+
Statement: statement,
335+
Generics: generics,
336+
}
337+
}
338+
182339
func isIdentifier(value []byte) bool {
183340
valid := true
184341
for i, b := range value {

internal/printer/print-to-js.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func render1(p *printer, n *Node, opts RenderOptions) {
105105

106106
// Root of the document, print all children
107107
if n.Type == DocumentNode {
108+
p.addNilSourceMapping()
108109
p.printInternalImports(p.opts.InternalURL)
109110
if opts.opts.StaticExtraction && n.FirstChild != nil && n.FirstChild.Type != FrontmatterNode {
110111
p.printCSSImports(opts.cssLen)
@@ -134,6 +135,7 @@ func render1(p *printer, n *Node, opts RenderOptions) {
134135

135136
for c := n.FirstChild; c != nil; c = c.NextSibling {
136137
if c.Type == TextNode {
138+
p.addNilSourceMapping()
137139
p.printInternalImports(p.opts.InternalURL)
138140

139141
if len(n.Loc) > 0 {

0 commit comments

Comments
 (0)