@@ -37,27 +37,40 @@ enum NodeTypes {
37
37
JS_RETURN_STATEMENT ,
38
38
}
39
39
40
+ interface ExpressionTrack {
41
+ type : NodeTypes
42
+ name ?: string
43
+ }
44
+
40
45
interface Expression {
46
+ track : ExpressionTrack [ ]
41
47
loc : SourceLocation
42
48
src : string
43
49
replacement ?: string
44
50
}
45
51
52
+ type VueTemplateNode =
53
+ | ParentNode
54
+ | ExpressionNode
55
+ | TemplateChildNode
56
+ | AttributeNode
57
+ | DirectiveNode
58
+
46
59
function handleNode (
47
- node :
48
- | ParentNode
49
- | ExpressionNode
50
- | TemplateChildNode
51
- | AttributeNode
52
- | DirectiveNode
53
- | undefined ,
60
+ node : VueTemplateNode | undefined ,
54
61
addExpression : ( ...expressions : Expression [ ] ) => void ,
62
+ track : ExpressionTrack [ ] ,
55
63
) {
56
64
if ( ! node ) {
57
65
return
58
66
}
59
67
60
- const search = ( node ?: ExpressionNode | TemplateChildNode | AttributeNode | DirectiveNode | TextNode ) => handleNode ( node , addExpression )
68
+ const currentTrack = [ ...track , node ]
69
+
70
+ const search = (
71
+ node ?: ExpressionNode | TemplateChildNode | AttributeNode
72
+ | DirectiveNode | TextNode ,
73
+ ) => handleNode ( node , addExpression , currentTrack )
61
74
62
75
switch ( node . type ) {
63
76
case NodeTypes . ROOT : {
@@ -83,7 +96,7 @@ function handleNode(
83
96
if ( node . ast === null || node . ast === false ) {
84
97
return
85
98
}
86
- addExpression ( { loc : node . loc , src : node . content } )
99
+ addExpression ( { loc : node . loc , src : node . content , track : currentTrack } )
87
100
return
88
101
}
89
102
case NodeTypes . INTERPOLATION : {
@@ -114,7 +127,7 @@ function handleNode(
114
127
return
115
128
}
116
129
117
- addExpression ( { loc : node . loc , src : node . loc . source } )
130
+ addExpression ( { loc : node . loc , src : node . loc . source , track : currentTrack } )
118
131
return
119
132
}
120
133
// case NodeTypes.IF:
@@ -126,21 +139,26 @@ function handleNode(
126
139
}
127
140
}
128
141
129
- export async function transpileVueTemplate ( content : string , root : RootNode , offset = 0 , transform : ( code : string ) => Promise < string > ) : Promise < string > {
142
+ export async function transpileVueTemplate (
143
+ content : string ,
144
+ root : RootNode ,
145
+ offset = 0 ,
146
+ transform : ( code : string ) => Promise < string > ,
147
+ ) : Promise < string > {
130
148
const { MagicString } = await import ( 'vue/compiler-sfc' )
131
149
const expressions : Expression [ ] = [ ]
132
150
133
- handleNode ( root , ( ...items ) => expressions . push ( ...items ) )
151
+ handleNode ( root , ( ...items ) => expressions . push ( ...items ) , [ ] )
134
152
135
153
if ( expressions . length === 0 ) {
136
154
return content
137
155
}
138
156
139
157
const s = new MagicString ( content )
140
158
141
- const transformMap = await transformJsSnippets ( expressions . map ( e => e . src ) , transform )
159
+ const transformMap = await transformJsSnippets ( expressions , transform )
142
160
for ( const item of expressions ) {
143
- item . replacement = transformMap . get ( item . src ) ?? item . src
161
+ item . replacement = transformMap . get ( item ) ?? item . src
144
162
145
163
const surrounding = getSurrounding (
146
164
content ,
@@ -210,42 +228,99 @@ function getSurrounding(code: string, start: number, end: number) {
210
228
: undefined
211
229
}
212
230
213
- async function transformJsSnippets ( codes : string [ ] , transform : ( code : string ) => Promise < string > ) : Promise < Map < string , string > > {
214
- const keyMap = new Map < string , string > ( )
215
- const resMap = new Map < string , string > ( )
216
- const codeSet = new Set < string > ( )
231
+ interface SnippetHandler {
232
+ key : ( node : Expression ) => string | null
233
+ prepare : ( node : Expression , id : number ) => string
234
+ parse : ( code : string , id : number ) => string | undefined
235
+ }
217
236
218
- for ( const code of codes ) {
219
- if ( codeSet . has ( code ) ) {
220
- continue
237
+ const defaultSnippetHandler : SnippetHandler = {
238
+ key : node => `default$:${ node . src } ` ,
239
+ prepare : ( node , id ) => `wrapper_${ id } (${ node . src } );` ,
240
+ parse : ( code ) => {
241
+ const wrapperRegex = / ^ ( w r a p p e r _ \d + ) \( ( [ \s \S ] * ?) \) ; $ /
242
+
243
+ const [ _ , wrapperName , res ] = code . match ( wrapperRegex ) ?? [ ]
244
+ if ( ! wrapperName || ! res ) {
245
+ return undefined
221
246
}
222
247
223
- codeSet . add ( code )
224
- keyMap . set ( `wrapper_${ keyMap . size } ` , code )
248
+ return res
249
+ } ,
250
+ }
251
+
252
+ const vSlotSnippetHandler : SnippetHandler = {
253
+ key : ( node ) => {
254
+ const secondLastTrack = node . track . at ( - 2 )
255
+ if ( secondLastTrack ?. type === NodeTypes . DIRECTIVE && secondLastTrack . name === 'slot' ) {
256
+ return `vSlot$:${ node . src } `
257
+ }
258
+ return null
259
+ } ,
260
+ prepare : ( node , id ) => `const ${ node . src } = wrapper_${ id } ();` ,
261
+ parse : ( code ) => {
262
+ const regex = / ^ ( c o n s t \s + ) ( \w + ) \s * = \s * w r a p p e r _ \d + \( \) ; $ /
263
+ const [ _ , res ] = code . match ( regex ) ?? [ ]
264
+ if ( ! res ) {
265
+ return undefined
266
+ }
267
+ return res
268
+ } ,
269
+ }
270
+
271
+ async function transformJsSnippets ( codes : Expression [ ] , transform : ( code : string ) => Promise < string > ) : Promise < WeakMap < Expression , string > > {
272
+ const keyMap = new WeakMap < Expression , string > ( )
273
+ const transformMap = new Map < string , { id : number , node : Expression , handler : SnippetHandler } > ( )
274
+ const snippetHandlers = [ vSlotSnippetHandler , defaultSnippetHandler ]
275
+ let id = 0
276
+ for ( const code of codes ) {
277
+ for ( const handler of snippetHandlers ) {
278
+ const key = handler . key ( code )
279
+ if ( ! key ) {
280
+ continue
281
+ }
282
+
283
+ keyMap . set ( code , key )
284
+ if ( transformMap . has ( key ) ) {
285
+ break
286
+ }
287
+
288
+ transformMap . set ( key , { id, node : code , handler } )
289
+ id += 1
290
+ break
291
+ }
225
292
}
226
293
294
+ const batchOrder = Array . from ( transformMap . entries ( ) )
295
+
227
296
// transform all snippets in a single file
228
297
const batchInputSplitter = `\nsplitter(${ Math . random ( ) } );\n`
229
- const batchInput = Array . from ( keyMap . entries ( ) ) . map ( ( [ wrapperName , raw ] ) => `${ wrapperName } (${ raw } );` ) . join ( batchInputSplitter )
298
+ const batchInput = batchOrder
299
+ . map ( ( [ _ , { node, handler } ] ) => handler . prepare ( node , id ) )
300
+ . join ( batchInputSplitter )
230
301
231
302
try {
232
303
const batchOutput = await transform ( batchInput )
304
+ const lines = batchOutput . split ( batchInputSplitter ) . map ( l => l . trim ( ) ) . filter ( l => ! ! l )
233
305
234
- const lines = batchOutput . trim ( ) . split ( batchInputSplitter )
235
- const wrapperRegex = / ^ ( w r a p p e r _ \d + ) \( ( [ \s \S ] * ?) \) ; $ /
236
- for ( const line of lines ) {
237
- const [ _ , wrapperName , res ] = line . match ( wrapperRegex ) ?? [ ]
238
- if ( ! wrapperName || ! res ) {
306
+ if ( lines . length !== batchOrder . length ) {
307
+ throw new Error ( '[vue-sfc-transform] Syntax Error' )
308
+ }
309
+
310
+ const resultMap = new Map < Expression , string > ( )
311
+ for ( let i = 0 ; i < batchOrder . length ; i ++ ) {
312
+ const line = lines [ i ] !
313
+ const [ _ , { id, handler, node } ] = batchOrder [ i ] !
314
+
315
+ const res = handler . parse ( line , id )
316
+ if ( ! res ) {
239
317
continue
240
318
}
241
319
242
- const raw = keyMap . get ( wrapperName )
243
- if ( raw ) {
244
- resMap . set ( raw , res )
245
- }
320
+ resultMap . set ( node , res )
246
321
}
247
322
248
- return resMap
323
+ return resultMap
249
324
}
250
325
catch ( error ) {
251
326
throw new Error ( '[vue-sfc-transform] Error parsing TypeScript expression in template' , { cause : error } )
0 commit comments