Skip to content

Commit 3e69eba

Browse files
committed
feat(babel): support nested source maps
1 parent b9edbb7 commit 3e69eba

File tree

5 files changed

+96
-93
lines changed

5 files changed

+96
-93
lines changed

.changeset/slow-ties-lay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@vue-jsx-vapor/babel': patch
3+
---
4+
5+
support nested source maps

packages/babel/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
},
5757
"dependencies": {
5858
"@babel/core": "catalog:",
59-
"@babel/generator": "^7.26.5",
6059
"@babel/parser": "catalog:",
6160
"@babel/plugin-syntax-jsx": "^7.25.9",
6261
"@babel/traverse": "^7.26.7",

packages/babel/src/index.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,58 @@
11
// @ts-ignore
22
import SyntaxJSX from '@babel/plugin-syntax-jsx'
33
import { parse } from '@babel/parser'
4-
import { transformJSX } from './transform'
4+
import { isJSXElement, transformJSX } from './transform'
5+
import type { VisitNodeFunction } from '@babel/traverse'
6+
import type { JSXElement, JSXFragment, Node } from '@babel/types'
57
import type { CompilerOptions } from '@vue-jsx-vapor/compiler'
68
import type { Visitor } from '@babel/core'
79

8-
export type Opts = {
10+
export type Options = {
11+
filename: string
912
importSet: Set<string>
1013
delegateEventSet: Set<string>
1114
preambleMap: Map<string, string>
1215
preambleIndex: number
16+
rootCodes: string[]
1317
compile?: CompilerOptions
1418
}
1519

1620
export default (): {
1721
name: string
1822
inherits: any
19-
visitor: Visitor<{ filename: string; opts: Opts }>
23+
visitor: Visitor<Options>
2024
} => {
2125
return {
2226
name: 'Vue JSX Vapor',
2327
inherits: SyntaxJSX,
2428
visitor: {
25-
JSXElement: {
26-
exit: transformJSX,
27-
},
28-
JSXFragment: {
29-
exit: transformJSX,
30-
},
29+
JSXElement: transformJSX,
30+
JSXFragment: transformJSX,
3131
Program: {
32-
enter: (_, state) => {
33-
state.opts.importSet = new Set<string>()
34-
state.opts.delegateEventSet = new Set<string>()
35-
state.opts.preambleMap = new Map<string, string>()
36-
state.opts.preambleIndex = 0
32+
enter: (path, state) => {
33+
state.importSet = new Set<string>()
34+
state.delegateEventSet = new Set<string>()
35+
state.preambleMap = new Map<string, string>()
36+
state.preambleIndex = 0
37+
state.rootCodes = []
38+
const collectRoot: VisitNodeFunction<
39+
Node,
40+
JSXElement | JSXFragment
41+
> = (path) => {
42+
if (
43+
(path.parent?.type !== 'JSXExpressionContainer' &&
44+
!isJSXElement(path.parent)) ||
45+
path.parentPath.parent?.type === 'JSXAttribute'
46+
) {
47+
state.rootCodes.push(path.getSource())
48+
}
49+
}
50+
path.traverse({
51+
JSXElement: collectRoot,
52+
JSXFragment: collectRoot,
53+
})
3754
},
38-
exit: (
39-
path,
40-
{ opts: { delegateEventSet, importSet, preambleMap } },
41-
) => {
55+
exit: (path, { delegateEventSet, importSet, preambleMap }) => {
4256
const statements: string[] = []
4357

4458
if (delegateEventSet.size) {

packages/babel/src/transform.ts

Lines changed: 29 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,87 @@
11
import { compile } from '@vue-jsx-vapor/compiler'
2-
import generate from '@babel/generator'
32
import { parse } from '@babel/parser'
43
import { SourceMapConsumer } from 'source-map-js'
5-
import traverse, {
6-
type NodePath,
7-
type VisitNodeFunction,
8-
} from '@babel/traverse'
9-
import type { Opts } from '.'
4+
import traverse, { type VisitNodeFunction } from '@babel/traverse'
5+
import type { Options } from '.'
106
import type { JSXElement, JSXFragment, Node } from '@babel/types'
117

128
export const transformJSX: VisitNodeFunction<
13-
{
14-
filename: string
15-
opts: Opts
16-
},
9+
Options,
1710
JSXElement | JSXFragment
1811
> = (path, state) => {
1912
const { parent, node } = path
20-
if (
21-
!(
22-
parent?.type !== 'JSXExpressionContainer' &&
23-
!isJSXElement(parent) &&
24-
!isConditionalExpression(path.parentPath)
25-
)
26-
) {
13+
if (!(parent?.type !== 'JSXExpressionContainer' && !isJSXElement(parent))) {
2714
return
2815
}
2916

3017
let { code, vaporHelpers, preamble, map } = compile(
31-
' '.repeat(node.loc?.start.column || 0) + generate(node).code,
18+
state.rootCodes.shift()!,
3219
{
3320
mode: 'module',
3421
inline: true,
3522
isTS: state.filename?.endsWith('tsx'),
3623
filename: state.filename,
3724
sourceMap: true,
38-
...state.opts?.compile,
25+
...state?.compile,
3926
},
4027
)
41-
vaporHelpers.forEach((helper) => state.opts.importSet.add(helper))
28+
vaporHelpers.forEach((helper) => state.importSet.add(helper))
4229

4330
preamble = preamble.replaceAll(
4431
/(?<=const )t(?=(\d))/g,
45-
`_t${state.opts.preambleIndex}`,
32+
`_t${state.preambleIndex}`,
4633
)
4734
code = code
48-
.replaceAll(/(?<== )t(?=\d)/g, `_t${state.opts.preambleIndex}`)
35+
.replaceAll(/(?<== )t(?=\d)/g, `_t${state.preambleIndex}`)
4936
.replaceAll('_ctx: any', '')
5037
.replaceAll('$event: any', '$event')
51-
state.opts.preambleIndex++
38+
state.preambleIndex++
5239

5340
for (const [, key, value] of preamble.matchAll(
5441
/const (_t\d+) = (_template\(.*\))/g,
5542
)) {
56-
const result = state.opts.preambleMap.get(value)
43+
const result = state.preambleMap.get(value)
5744
if (result) {
5845
code = code.replaceAll(key, result)
5946
} else {
60-
state.opts.preambleMap.set(value, key)
47+
state.preambleMap.set(value, key)
6148
}
6249
}
6350

6451
for (const [, events] of preamble.matchAll(/_delegateEvents\((.*)\)/g)) {
65-
events
66-
.split(', ')
67-
.forEach((event: any) => state.opts.delegateEventSet.add(event))
52+
events.split(', ').forEach((event) => state.delegateEventSet.add(event))
6853
}
6954

7055
const ast = parse(code, {
7156
sourceFilename: state.filename,
57+
startLine: node.loc!.start.line - 1,
58+
plugins: ['jsx'],
7259
})
7360

7461
if (map) {
7562
const consumer = new SourceMapConsumer(map)
76-
const line = (node.loc?.start.line ?? 1) - 1
63+
const line = node.loc!.start.line - 1
7764
traverse(ast, {
7865
Identifier({ node: id }) {
79-
const originalLoc = consumer.originalPositionFor(id.loc!.start)
80-
if (originalLoc) {
81-
id.loc = {
82-
...id.loc!,
83-
start: {
84-
line: line + originalLoc.line,
85-
column: originalLoc.column,
86-
index: originalLoc.column,
87-
},
88-
end: {
89-
line: line + originalLoc.line,
90-
column: originalLoc.column + id.name.length,
91-
index: originalLoc.column + id.name.length,
92-
},
93-
}
66+
const originalLoc = consumer.originalPositionFor({
67+
...id.loc!.start,
68+
line: id.loc!.start.line - line + 1,
69+
})
70+
const column = originalLoc.line === 1 ? node.loc!.start.column : 0
71+
if (originalLoc.column) {
72+
id.loc!.start.line = line + originalLoc.line + (path.hub ? 0 : 1)
73+
id.loc!.start.column = column + originalLoc.column
74+
id.loc!.end.line = line + originalLoc.line
75+
id.loc!.end.column = column + originalLoc.column + id.name.length
9476
}
9577
},
9678
})
9779
}
98-
// console.dir({ a }, { depth: 112 })
99-
// path.replaceWith()
10080
path.replaceWith(ast.program.body[0])
101-
// path.replaceWithSourceString(code)
10281
}
10382

104-
function isJSXElement(node?: Node | null): node is JSXElement | JSXFragment {
83+
export function isJSXElement(
84+
node?: Node | null,
85+
): node is JSXElement | JSXFragment {
10586
return !!node && (node.type === 'JSXElement' || node.type === 'JSXFragment')
10687
}
107-
108-
function isConditionalExpression(path: NodePath<Node> | null): boolean {
109-
return !!(
110-
path &&
111-
(path?.type === 'LogicalExpression' ||
112-
path.type === 'ConditionalExpression') &&
113-
(path.parent.type === 'JSXExpressionContainer' ||
114-
(path.parent.type === 'ConditionalExpression' &&
115-
isConditionalExpression(path.parentPath)))
116-
)
117-
}

pnpm-lock.yaml

Lines changed: 30 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)