@@ -4,10 +4,10 @@ import {
4
4
BlockStatement ,
5
5
CallExpression ,
6
6
ObjectPattern ,
7
- VariableDeclaration ,
8
7
ArrayPattern ,
9
8
Program ,
10
- VariableDeclarator
9
+ VariableDeclarator ,
10
+ Expression
11
11
} from '@babel/types'
12
12
import MagicString , { SourceMap } from 'magic-string'
13
13
import { walk } from 'estree-walker'
@@ -20,7 +20,7 @@ import {
20
20
walkFunctionParams
21
21
} from '@vue/compiler-core'
22
22
import { parse , ParserPlugin } from '@babel/parser'
23
- import { hasOwn } from '@vue/shared'
23
+ import { hasOwn , isArray , isString } from '@vue/shared'
24
24
25
25
const TO_VAR_SYMBOL = '$'
26
26
const TO_REF_SYMBOL = '$$'
@@ -71,7 +71,7 @@ export function transform(
71
71
plugins
72
72
} )
73
73
const s = new MagicString ( src )
74
- const res = transformAST ( ast . program , s )
74
+ const res = transformAST ( ast . program , s , 0 )
75
75
76
76
// inject helper imports
77
77
if ( res . importedHelpers . length ) {
@@ -106,16 +106,13 @@ export function transformAST(
106
106
local : string // local identifier, may be different
107
107
default ?: any
108
108
}
109
- > ,
110
- rewritePropsOnly = false
109
+ >
111
110
) : {
112
111
rootRefs : string [ ]
113
112
importedHelpers : string [ ]
114
113
} {
115
114
// TODO remove when out of experimental
116
- if ( ! rewritePropsOnly ) {
117
- warnExperimental ( )
118
- }
115
+ warnExperimental ( )
119
116
120
117
const importedHelpers = new Set < string > ( )
121
118
const rootScope : Scope = { }
@@ -139,7 +136,6 @@ export function transformAST(
139
136
}
140
137
141
138
function error ( msg : string , node : Node ) {
142
- if ( rewritePropsOnly ) return
143
139
const e = new Error ( msg )
144
140
; ( e as any ) . node = node
145
141
throw e
@@ -164,6 +160,15 @@ export function transformAST(
164
160
165
161
const registerRefBinding = ( id : Identifier ) => registerBinding ( id , true )
166
162
163
+ let tempVarCount = 0
164
+ function genTempVar ( ) {
165
+ return `__$temp_${ ++ tempVarCount } `
166
+ }
167
+
168
+ function snip ( node : Node ) {
169
+ return s . original . slice ( node . start ! + offset , node . end ! + offset )
170
+ }
171
+
167
172
function walkScope ( node : Program | BlockStatement , isRoot = false ) {
168
173
for ( const stmt of node . body ) {
169
174
if ( stmt . type === 'VariableDeclaration' ) {
@@ -180,9 +185,8 @@ export function transformAST(
180
185
) {
181
186
processRefDeclaration (
182
187
toVarCall ,
183
- decl . init as CallExpression ,
184
188
decl . id ,
185
- stmt
189
+ decl . init as CallExpression
186
190
)
187
191
} else {
188
192
const isProps =
@@ -212,9 +216,8 @@ export function transformAST(
212
216
213
217
function processRefDeclaration (
214
218
method : string ,
215
- call : CallExpression ,
216
219
id : VariableDeclarator [ 'id' ] ,
217
- statement : VariableDeclaration
220
+ call : CallExpression
218
221
) {
219
222
excludedIds . add ( call . callee as Identifier )
220
223
if ( method === TO_VAR_SYMBOL ) {
@@ -225,9 +228,9 @@ export function transformAST(
225
228
// single variable
226
229
registerRefBinding ( id )
227
230
} else if ( id . type === 'ObjectPattern' ) {
228
- processRefObjectPattern ( id , statement )
231
+ processRefObjectPattern ( id , call )
229
232
} else if ( id . type === 'ArrayPattern' ) {
230
- processRefArrayPattern ( id , statement )
233
+ processRefArrayPattern ( id , call )
231
234
}
232
235
} else {
233
236
// shorthands
@@ -247,15 +250,24 @@ export function transformAST(
247
250
248
251
function processRefObjectPattern (
249
252
pattern : ObjectPattern ,
250
- statement : VariableDeclaration
253
+ call : CallExpression ,
254
+ tempVar ?: string ,
255
+ path : PathSegment [ ] = [ ]
251
256
) {
257
+ if ( ! tempVar ) {
258
+ tempVar = genTempVar ( )
259
+ // const { x } = $(useFoo()) --> const __$temp_1 = useFoo()
260
+ s . overwrite ( pattern . start ! + offset , pattern . end ! + offset , tempVar )
261
+ }
262
+
252
263
for ( const p of pattern . properties ) {
253
264
let nameId : Identifier | undefined
265
+ let key : Expression | string | undefined
266
+ let defaultValue : Expression | undefined
254
267
if ( p . type === 'ObjectProperty' ) {
255
268
if ( p . key . start ! === p . value . start ! ) {
256
- // shorthand { foo } --> { foo: __foo }
269
+ // shorthand { foo }
257
270
nameId = p . key as Identifier
258
- s . appendLeft ( nameId . end ! + offset , `: __${ nameId . name } ` )
259
271
if ( p . value . type === 'Identifier' ) {
260
272
// avoid shorthand value identifier from being processed
261
273
excludedIds . add ( p . value )
@@ -265,72 +277,137 @@ export function transformAST(
265
277
) {
266
278
// { foo = 1 }
267
279
excludedIds . add ( p . value . left )
280
+ defaultValue = p . value . right
268
281
}
269
282
} else {
283
+ key = p . computed ? p . key : ( p . key as Identifier ) . name
270
284
if ( p . value . type === 'Identifier' ) {
271
- // { foo: bar } --> { foo: __bar }
285
+ // { foo: bar }
272
286
nameId = p . value
273
- s . prependRight ( nameId . start ! + offset , `__` )
274
287
} else if ( p . value . type === 'ObjectPattern' ) {
275
- processRefObjectPattern ( p . value , statement )
288
+ processRefObjectPattern ( p . value , call , tempVar , [ ... path , key ] )
276
289
} else if ( p . value . type === 'ArrayPattern' ) {
277
- processRefArrayPattern ( p . value , statement )
290
+ processRefArrayPattern ( p . value , call , tempVar , [ ... path , key ] )
278
291
} else if ( p . value . type === 'AssignmentPattern' ) {
279
- // { foo: bar = 1 } --> { foo: __bar = 1 }
280
- nameId = p . value . left as Identifier
281
- s . prependRight ( nameId . start ! + offset , `__` )
292
+ if ( p . value . left . type === 'Identifier' ) {
293
+ // { foo: bar = 1 }
294
+ nameId = p . value . left
295
+ defaultValue = p . value . right
296
+ } else if ( p . value . left . type === 'ObjectPattern' ) {
297
+ processRefObjectPattern ( p . value . left , call , tempVar , [
298
+ ...path ,
299
+ [ key , p . value . right ]
300
+ ] )
301
+ } else if ( p . value . left . type === 'ArrayPattern' ) {
302
+ processRefArrayPattern ( p . value . left , call , tempVar , [
303
+ ...path ,
304
+ [ key , p . value . right ]
305
+ ] )
306
+ } else {
307
+ // MemberExpression case is not possible here, ignore
308
+ }
282
309
}
283
310
}
284
311
} else {
285
- // rest element { ...foo } --> { ...__foo }
286
- nameId = p . argument as Identifier
287
- s . prependRight ( nameId . start ! + offset , `__` )
312
+ // rest element { ...foo }
313
+ error ( `reactivity destructure does not support rest elements.` , p )
288
314
}
289
315
if ( nameId ) {
290
316
registerRefBinding ( nameId )
291
- // append binding declarations after the parent statement
317
+ // inject toRef() after original replaced pattern
318
+ const source = pathToString ( tempVar , path )
319
+ const keyStr = isString ( key )
320
+ ? `'${ key } '`
321
+ : key
322
+ ? snip ( key )
323
+ : `'${ nameId . name } '`
324
+ const defaultStr = defaultValue ? `, ${ snip ( defaultValue ) } ` : ``
292
325
s . appendLeft (
293
- statement . end ! + offset ,
294
- `\nconst ${ nameId . name } = ${ helper ( 'shallowRef' ) } (__${ nameId . name } );`
326
+ call . end ! + offset ,
327
+ `,\n ${ nameId . name } = ${ helper (
328
+ 'toRef'
329
+ ) } (${ source } , ${ keyStr } ${ defaultStr } )`
295
330
)
296
331
}
297
332
}
298
333
}
299
334
300
335
function processRefArrayPattern (
301
336
pattern : ArrayPattern ,
302
- statement : VariableDeclaration
337
+ call : CallExpression ,
338
+ tempVar ?: string ,
339
+ path : PathSegment [ ] = [ ]
303
340
) {
304
- for ( const e of pattern . elements ) {
341
+ if ( ! tempVar ) {
342
+ // const [x] = $(useFoo()) --> const __$temp_1 = useFoo()
343
+ tempVar = genTempVar ( )
344
+ s . overwrite ( pattern . start ! + offset , pattern . end ! + offset , tempVar )
345
+ }
346
+
347
+ for ( let i = 0 ; i < pattern . elements . length ; i ++ ) {
348
+ const e = pattern . elements [ i ]
305
349
if ( ! e ) continue
306
350
let nameId : Identifier | undefined
351
+ let defaultValue : Expression | undefined
307
352
if ( e . type === 'Identifier' ) {
308
353
// [a] --> [__a]
309
354
nameId = e
310
355
} else if ( e . type === 'AssignmentPattern' ) {
311
- // [a = 1] --> [__a = 1]
356
+ // [a = 1]
312
357
nameId = e . left as Identifier
358
+ defaultValue = e . right
313
359
} else if ( e . type === 'RestElement' ) {
314
- // [...a] --> [...__a]
315
- nameId = e . argument as Identifier
360
+ // [...a]
361
+ error ( `reactivity destructure does not support rest elements.` , e )
316
362
} else if ( e . type === 'ObjectPattern' ) {
317
- processRefObjectPattern ( e , statement )
363
+ processRefObjectPattern ( e , call , tempVar , [ ... path , i ] )
318
364
} else if ( e . type === 'ArrayPattern' ) {
319
- processRefArrayPattern ( e , statement )
365
+ processRefArrayPattern ( e , call , tempVar , [ ... path , i ] )
320
366
}
321
367
if ( nameId ) {
322
368
registerRefBinding ( nameId )
323
- // prefix original
324
- s . prependRight ( nameId . start ! + offset , `__` )
325
- // append binding declarations after the parent statement
369
+ // inject toRef() after original replaced pattern
370
+ const source = pathToString ( tempVar , path )
371
+ const defaultStr = defaultValue ? `, ${ snip ( defaultValue ) } ` : ``
326
372
s . appendLeft (
327
- statement . end ! + offset ,
328
- `\nconst ${ nameId . name } = ${ helper ( 'shallowRef' ) } (__${ nameId . name } );`
373
+ call . end ! + offset ,
374
+ `,\n ${ nameId . name } = ${ helper (
375
+ 'toRef'
376
+ ) } (${ source } , ${ i } ${ defaultStr } )`
329
377
)
330
378
}
331
379
}
332
380
}
333
381
382
+ type PathSegmentAtom = Expression | string | number
383
+
384
+ type PathSegment =
385
+ | PathSegmentAtom
386
+ | [ PathSegmentAtom , Expression /* default value */ ]
387
+
388
+ function pathToString ( source : string , path : PathSegment [ ] ) : string {
389
+ if ( path . length ) {
390
+ for ( const seg of path ) {
391
+ if ( isArray ( seg ) ) {
392
+ source = `(${ source } ${ segToString ( seg [ 0 ] ) } || ${ snip ( seg [ 1 ] ) } )`
393
+ } else {
394
+ source += segToString ( seg )
395
+ }
396
+ }
397
+ }
398
+ return source
399
+ }
400
+
401
+ function segToString ( seg : PathSegmentAtom ) : string {
402
+ if ( typeof seg === 'number' ) {
403
+ return `[${ seg } ]`
404
+ } else if ( typeof seg === 'string' ) {
405
+ return `.${ seg } `
406
+ } else {
407
+ return snip ( seg )
408
+ }
409
+ }
410
+
334
411
function rewriteId (
335
412
scope : Scope ,
336
413
id : Identifier ,
@@ -341,10 +418,6 @@ export function transformAST(
341
418
const bindingType = scope [ id . name ]
342
419
if ( bindingType ) {
343
420
const isProp = bindingType === 'prop'
344
- if ( rewritePropsOnly && ! isProp ) {
345
- return true
346
- }
347
- // ref
348
421
if ( isStaticProperty ( parent ) && parent . shorthand ) {
349
422
// let binding used in a property shorthand
350
423
// { foo } -> { foo: foo.value }
@@ -498,7 +571,7 @@ function warnExperimental() {
498
571
return
499
572
}
500
573
warnOnce (
501
- `@vue/ref- transform is an experimental feature.\n` +
574
+ `Reactivity transform is an experimental feature.\n` +
502
575
`Experimental features may change behavior between patch versions.\n` +
503
576
`It is recommended to pin your vue dependencies to exact versions to avoid breakage.\n` +
504
577
`You can follow the proposal's status at ${ RFC_LINK } .`
0 commit comments