Skip to content

Commit 06eb0a3

Browse files
committed
feat: support AST node compilation
1 parent 688141b commit 06eb0a3

File tree

5 files changed

+92
-76
lines changed

5 files changed

+92
-76
lines changed

.changeset/afraid-suns-hope.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@vue-jsx-vapor/compiler': patch
3+
'@vue-jsx-vapor/babel': patch
4+
---
5+
6+
support AST node compilation

packages/babel/src/index.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// @ts-ignore
22
import SyntaxJSX from '@babel/plugin-syntax-jsx'
33
import { parse } from '@babel/parser'
4-
import { isJSXElement, transformJSX } from './transform'
5-
import type { NodePath, VisitNodeFunction } from '@babel/traverse'
4+
import { transformJSX } from './transform'
5+
import { isConditionalExpression, isJSXElement } from './utils'
6+
import type { VisitNodeFunction } from '@babel/traverse'
67
import type { JSXElement, JSXFragment, Node } from '@babel/types'
78
import type { CompilerOptions } from '@vue-jsx-vapor/compiler'
89
import type { Visitor } from '@babel/core'
@@ -13,7 +14,7 @@ export type Options = {
1314
delegateEventSet: Set<string>
1415
preambleMap: Map<string, string>
1516
preambleIndex: number
16-
rootCodes: string[]
17+
roots: { node: JSXElement | JSXFragment; source: string }[]
1718
compile?: CompilerOptions
1819
}
1920

@@ -34,7 +35,7 @@ export default (): {
3435
state.delegateEventSet = new Set<string>()
3536
state.preambleMap = new Map<string, string>()
3637
state.preambleIndex = 0
37-
state.rootCodes = []
38+
state.roots = []
3839
const collectRoot: VisitNodeFunction<
3940
Node,
4041
JSXElement | JSXFragment
@@ -45,17 +46,21 @@ export default (): {
4546
!isConditionalExpression(path.parentPath)) ||
4647
path.parentPath.parent?.type === 'JSXAttribute'
4748
) {
48-
state.rootCodes.push(path.getSource())
49+
state.roots.push({
50+
node: path.node,
51+
source: path.getSource(),
52+
})
4953
}
5054
}
5155
path.traverse({
5256
JSXElement: collectRoot,
5357
JSXFragment: collectRoot,
5458
})
5559
},
56-
exit: (path, { delegateEventSet, importSet, preambleMap }) => {
57-
const statements: string[] = []
60+
exit: (path, state) => {
61+
const { delegateEventSet, importSet, preambleMap } = state
5862

63+
const statements: string[] = []
5964
if (delegateEventSet.size) {
6065
statements.unshift(
6166
`_delegateEvents(${Array.from(delegateEventSet).join(', ')});`,
@@ -97,14 +102,3 @@ export default (): {
97102
},
98103
}
99104
}
100-
101-
export function isConditionalExpression(path: NodePath<Node> | null): boolean {
102-
return !!(
103-
path &&
104-
(path?.type === 'LogicalExpression' ||
105-
path.type === 'ConditionalExpression') &&
106-
(path.parent.type === 'JSXExpressionContainer' ||
107-
(path.parent.type === 'ConditionalExpression' &&
108-
isConditionalExpression(path.parentPath)))
109-
)
110-
}

packages/babel/src/transform.ts

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,36 @@ import { compile } from '@vue-jsx-vapor/compiler'
22
import { parse } from '@babel/parser'
33
import { SourceMapConsumer } from 'source-map-js'
44
import traverse, { type VisitNodeFunction } from '@babel/traverse'
5+
import { isJSXElement } from './utils'
56
import type { Options } from '.'
6-
import type { JSXElement, JSXFragment, Node } from '@babel/types'
7+
import type { JSXElement, JSXFragment } from '@babel/types'
78

89
export const transformJSX: VisitNodeFunction<
910
Options,
1011
JSXElement | JSXFragment
1112
> = (path, state) => {
12-
const { parent, node } = path
13-
if (!(parent?.type !== 'JSXExpressionContainer' && !isJSXElement(parent))) {
13+
if (
14+
!(
15+
path.parent?.type !== 'JSXExpressionContainer' &&
16+
!isJSXElement(path.parent)
17+
)
18+
) {
1419
return
1520
}
1621

17-
let { code, vaporHelpers, preamble, map } = compile(
18-
state.rootCodes.shift()!,
19-
{
20-
mode: 'module',
21-
inline: true,
22-
isTS: state.filename?.endsWith('tsx'),
23-
filename: state.filename,
24-
sourceMap: true,
25-
...state?.compile,
26-
},
27-
)
22+
const root = state.roots.shift()
23+
if (!root) return
24+
const isTS = state.filename?.endsWith('tsx')
25+
let { code, vaporHelpers, preamble, map } = compile(root.node, {
26+
mode: 'module',
27+
inline: true,
28+
isTS,
29+
filename: state.filename,
30+
sourceMap: true,
31+
source: ' '.repeat(root.node.start || 0) + root.source,
32+
...state?.compile,
33+
})
34+
2835
vaporHelpers.forEach((helper) => state.importSet.add(helper))
2936

3037
preamble = preamble.replaceAll(
@@ -54,34 +61,24 @@ export const transformJSX: VisitNodeFunction<
5461

5562
const ast = parse(code, {
5663
sourceFilename: state.filename,
57-
startLine: node.loc!.start.line - 1,
58-
plugins: ['jsx'],
64+
plugins: isTS ? ['jsx', 'typescript'] : ['jsx'],
5965
})
6066

6167
if (map) {
6268
const consumer = new SourceMapConsumer(map)
63-
const line = node.loc!.start.line - 1
6469
traverse(ast, {
6570
Identifier({ node: id }) {
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 (!id.loc) return
72+
const originalLoc = consumer.originalPositionFor(id.loc.start)
7173
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
74+
id.loc.start.line = originalLoc.line
75+
id.loc.start.column = originalLoc.column
76+
id.loc.end.line = originalLoc.line
77+
id.loc.end.column = originalLoc.column + id.name.length
7678
}
7779
},
7880
})
7981
}
80-
path.replaceWith(ast.program.body[0])
81-
}
8282

83-
export function isJSXElement(
84-
node?: Node | null,
85-
): node is JSXElement | JSXFragment {
86-
return !!node && (node.type === 'JSXElement' || node.type === 'JSXFragment')
83+
path.replaceWith(ast.program.body[0])
8784
}

packages/babel/src/utils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { NodePath } from '@babel/traverse'
2+
import type { JSXElement, JSXFragment, Node } from '@babel/types'
3+
4+
export function isConditionalExpression(path: NodePath<Node> | null): boolean {
5+
return !!(
6+
path &&
7+
(path?.type === 'LogicalExpression' ||
8+
path.type === 'ConditionalExpression') &&
9+
(path.parent.type === 'JSXExpressionContainer' ||
10+
(path.parent.type === 'ConditionalExpression' &&
11+
isConditionalExpression(path.parentPath)))
12+
)
13+
}
14+
15+
export function isJSXElement(
16+
node?: Node | null,
17+
): node is JSXElement | JSXFragment {
18+
return !!node && (node.type === 'JSXElement' || node.type === 'JSXFragment')
19+
}

packages/compiler/src/compile.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { transformVSlot } from './transforms/vSlot'
3232
import { transformVModel } from './transforms/vModel'
3333
import { transformVShow } from './transforms/vShow'
3434
import { transformVHtml } from './transforms/vHtml'
35-
import type { JSXElement, JSXFragment, Program } from '@babel/types'
35+
import type { Expression, JSXElement, JSXFragment } from '@babel/types'
3636

3737
export interface VaporCodegenResult
3838
extends Omit<BaseVaporCodegenResult, 'ast'> {
@@ -41,16 +41,11 @@ export interface VaporCodegenResult
4141

4242
// code/AST -> IR (transform) -> JS (generate)
4343
export function compile(
44-
source: string | Program,
44+
source: string | JSXElement | JSXFragment,
4545
options: CompilerOptions = {},
4646
): VaporCodegenResult {
4747
const onError = options.onError || defaultOnError
4848
const isModuleMode = options.mode === 'module'
49-
const __BROWSER__ = false
50-
/* istanbul ignore if */
51-
if (__BROWSER__ && isModuleMode) {
52-
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
53-
}
5449

5550
if (options.scopeId && !isModuleMode) {
5651
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
@@ -60,7 +55,7 @@ export function compile(
6055
prefixIdentifiers: false,
6156
expressionPlugins: options.expressionPlugins || ['jsx'],
6257
})
63-
if (!__BROWSER__ && options.isTS) {
58+
if (options.isTS) {
6459
const { expressionPlugins } = resolvedOptions
6560
if (!expressionPlugins.includes('typescript')) {
6661
resolvedOptions.expressionPlugins = [
@@ -70,27 +65,30 @@ export function compile(
7065
}
7166
}
7267

73-
const {
74-
body: [statement],
75-
} = isString(source)
76-
? parse(source, {
77-
sourceType: 'module',
78-
plugins: resolvedOptions.expressionPlugins,
79-
}).program
80-
: source
81-
let children!: JSXElement[] | JSXFragment['children']
82-
if (statement.type === 'ExpressionStatement') {
83-
children =
84-
statement.expression.type === 'JSXFragment'
85-
? statement.expression.children
86-
: statement.expression.type === 'JSXElement'
87-
? [statement.expression]
88-
: []
68+
let expression!: Expression
69+
if (isString(source)) {
70+
const {
71+
body: [statement],
72+
} = parse(source, {
73+
sourceType: 'module',
74+
plugins: resolvedOptions.expressionPlugins,
75+
}).program
76+
if (statement.type === 'ExpressionStatement') {
77+
expression = statement.expression
78+
}
79+
} else {
80+
expression = source
8981
}
82+
const children =
83+
expression.type === 'JSXFragment'
84+
? expression.children
85+
: expression.type === 'JSXElement'
86+
? [expression]
87+
: []
9088
const ast: RootNode = {
9189
type: IRNodeTypes.ROOT,
9290
children,
93-
source: isString(source) ? source : '', // TODO
91+
source: isString(source) ? source : options.source || '',
9492
components: [],
9593
directives: [],
9694
helpers: new Set(),
@@ -116,7 +114,9 @@ export function compile(
116114
return generate(ir as any, resolvedOptions) as unknown as VaporCodegenResult
117115
}
118116

119-
export type CompilerOptions = HackOptions<BaseCompilerOptions>
117+
export type CompilerOptions = HackOptions<BaseCompilerOptions> & {
118+
source?: string
119+
}
120120
export type TransformPreset = [
121121
NodeTransform[],
122122
Record<string, DirectiveTransform>,

0 commit comments

Comments
 (0)