1
1
import { parseExpression } from '@babel/parser'
2
+ import {
3
+ isNodesEquivalent ,
4
+ type Expression ,
5
+ type Identifier ,
6
+ type Node ,
7
+ } from '@babel/types'
2
8
import {
3
9
createSimpleExpression ,
10
+ isStaticNode ,
4
11
walkIdentifiers ,
5
12
type SimpleExpressionNode ,
6
13
} from '@vue/compiler-dom'
14
+ import { extend , isGloballyAllowed } from '@vue/shared'
15
+ import { walkAST } from 'ast-kit'
7
16
import type { CodegenContext } from '../generate'
8
- import type { ForIRNode } from '../ir'
9
- import { genBlock } from './block'
17
+ import type { BlockIRNode , ForIRNode , IREffect } from '../ir'
18
+ import { genBlockContent } from './block'
10
19
import { genExpression } from './expression'
11
- import { genCall , genMulti , NEWLINE , type CodeFragment } from './utils'
12
- import type { Identifier } from '@babel/types'
20
+ import { genOperation } from './operation'
21
+ import {
22
+ genCall ,
23
+ genMulti ,
24
+ INDENT_END ,
25
+ INDENT_START ,
26
+ NEWLINE ,
27
+ type CodeFragment ,
28
+ } from './utils'
13
29
14
30
/**
15
31
* Flags to optimize vapor `createFor` runtime behavior, shared between the
@@ -98,7 +114,60 @@ export function genFor(
98
114
idMap [ indexVar ] = null
99
115
}
100
116
101
- const blockFn = context . withId ( ( ) => genBlock ( render , context , args ) , idMap )
117
+ const { selectorPatterns, keyOnlyBindingPatterns } = matchPatterns (
118
+ render ,
119
+ keyProp ,
120
+ idMap ,
121
+ )
122
+ const patternFrag : CodeFragment [ ] = [ ]
123
+
124
+ for ( const [ i , { selector } ] of selectorPatterns . entries ( ) ) {
125
+ const selectorName = `_selector${ id } _${ i } `
126
+ patternFrag . push (
127
+ NEWLINE ,
128
+ `const ${ selectorName } = ` ,
129
+ ...genCall ( `n${ id } .useSelector` , [
130
+ `() => ` ,
131
+ ...genExpression ( selector , context ) ,
132
+ ] ) ,
133
+ )
134
+ }
135
+
136
+ const blockFn = context . withId ( ( ) => {
137
+ const frag : CodeFragment [ ] = [ ]
138
+ frag . push ( '(' , ...args , ') => {' , INDENT_START )
139
+ if ( selectorPatterns . length || keyOnlyBindingPatterns . length ) {
140
+ frag . push (
141
+ ...genBlockContent ( render , context , false , ( ) => {
142
+ const patternFrag : CodeFragment [ ] = [ ]
143
+
144
+ for ( const [ i , { effect } ] of selectorPatterns . entries ( ) ) {
145
+ patternFrag . push (
146
+ NEWLINE ,
147
+ `_selector${ id } _${ i } (() => {` ,
148
+ INDENT_START ,
149
+ )
150
+ for ( const oper of effect . operations ) {
151
+ patternFrag . push ( ...genOperation ( oper , context ) )
152
+ }
153
+ patternFrag . push ( INDENT_END , NEWLINE , `})` )
154
+ }
155
+
156
+ for ( const { effect } of keyOnlyBindingPatterns ) {
157
+ for ( const oper of effect . operations ) {
158
+ patternFrag . push ( ...genOperation ( oper , context ) )
159
+ }
160
+ }
161
+
162
+ return patternFrag
163
+ } ) ,
164
+ )
165
+ } else {
166
+ frag . push ( ...genBlockContent ( render , context ) )
167
+ }
168
+ frag . push ( INDENT_END , NEWLINE , '}' )
169
+ return frag
170
+ } , idMap )
102
171
exitScope ( )
103
172
104
173
let flags = 0
@@ -123,6 +192,7 @@ export function genFor(
123
192
flags ? String ( flags ) : undefined ,
124
193
// todo: hydrationNode
125
194
) ,
195
+ ...patternFrag ,
126
196
]
127
197
128
198
// construct a id -> accessor path map.
@@ -251,3 +321,229 @@ export function genFor(
251
321
return idMap
252
322
}
253
323
}
324
+
325
+ function matchPatterns (
326
+ render : BlockIRNode ,
327
+ keyProp : SimpleExpressionNode | undefined ,
328
+ idMap : Record < string , string | SimpleExpressionNode | null > ,
329
+ ) {
330
+ const selectorPatterns : NonNullable <
331
+ ReturnType < typeof matchSelectorPattern >
332
+ > [ ] = [ ]
333
+ const keyOnlyBindingPatterns : NonNullable <
334
+ ReturnType < typeof matchKeyOnlyBindingPattern >
335
+ > [ ] = [ ]
336
+
337
+ render . effect = render . effect . filter ( ( effect ) => {
338
+ if ( keyProp !== undefined ) {
339
+ const selector = matchSelectorPattern ( effect , keyProp . ast , idMap )
340
+ if ( selector ) {
341
+ selectorPatterns . push ( selector )
342
+ return false
343
+ }
344
+ const keyOnly = matchKeyOnlyBindingPattern ( effect , keyProp . ast )
345
+ if ( keyOnly ) {
346
+ keyOnlyBindingPatterns . push ( keyOnly )
347
+ return false
348
+ }
349
+ }
350
+
351
+ return true
352
+ } )
353
+
354
+ return {
355
+ keyOnlyBindingPatterns,
356
+ selectorPatterns,
357
+ }
358
+ }
359
+
360
+ function matchKeyOnlyBindingPattern (
361
+ effect : IREffect ,
362
+ keyAst : any ,
363
+ ) :
364
+ | {
365
+ effect : IREffect
366
+ }
367
+ | undefined {
368
+ // TODO: expressions can be multiple?
369
+ if ( effect . expressions . length === 1 ) {
370
+ const ast = effect . expressions [ 0 ] . ast
371
+ if (
372
+ typeof ast === 'object' &&
373
+ ast !== null &&
374
+ isKeyOnlyBinding ( ast , keyAst )
375
+ ) {
376
+ return { effect }
377
+ }
378
+ }
379
+ }
380
+
381
+ function matchSelectorPattern (
382
+ effect : IREffect ,
383
+ keyAst : any ,
384
+ idMap : Record < string , string | SimpleExpressionNode | null > ,
385
+ ) :
386
+ | {
387
+ effect : IREffect
388
+ selector : SimpleExpressionNode
389
+ }
390
+ | undefined {
391
+ // TODO: expressions can be multiple?
392
+ if ( effect . expressions . length === 1 ) {
393
+ const ast = effect . expressions [ 0 ] . ast
394
+ const offset = effect . expressions [ 0 ] . loc . start . offset
395
+ if ( typeof ast === 'object' && ast ) {
396
+ const matcheds : [ key : Expression , selector : Expression ] [ ] = [ ]
397
+
398
+ walkAST ( ast , {
399
+ enter ( node ) {
400
+ if (
401
+ typeof node === 'object' &&
402
+ node &&
403
+ node . type === 'BinaryExpression' &&
404
+ node . operator === '===' &&
405
+ node . left . type !== 'PrivateName'
406
+ ) {
407
+ const { left, right } = node
408
+ for ( const [ a , b ] of [
409
+ [ left , right ] ,
410
+ [ right , left ] ,
411
+ ] ) {
412
+ const aIsKey = isKeyOnlyBinding ( a , keyAst )
413
+ const bIsKey = isKeyOnlyBinding ( b , keyAst )
414
+ const bVars = analyzeVariableScopes ( b , idMap )
415
+ if ( aIsKey && ! bIsKey && ! bVars . locals . length ) {
416
+ matcheds . push ( [ a , b ] )
417
+ }
418
+ }
419
+ }
420
+ } ,
421
+ } )
422
+
423
+ if ( matcheds . length === 1 ) {
424
+ const [ key , selector ] = matcheds [ 0 ]
425
+ const content = effect . expressions [ 0 ] . content
426
+
427
+ let hasExtraId = false
428
+ const parentStackMap = new Map < Identifier , Node [ ] > ( )
429
+ const parentStack : Node [ ] = [ ]
430
+ walkIdentifiers (
431
+ ast ,
432
+ ( id ) => {
433
+ if ( id . start !== key . start && id . start !== selector . start ) {
434
+ hasExtraId = true
435
+ }
436
+ parentStackMap . set ( id , parentStack . slice ( ) )
437
+ } ,
438
+ false ,
439
+ parentStack ,
440
+ )
441
+
442
+ if ( ! hasExtraId ) {
443
+ const name = content . slice (
444
+ selector . start ! - offset ,
445
+ selector . end ! - offset ,
446
+ )
447
+ return {
448
+ effect,
449
+ // @ts -expect-error
450
+ selector : {
451
+ content : name ,
452
+ ast : extend ( { } , selector , {
453
+ start : 1 ,
454
+ end : name . length + 1 ,
455
+ } ) ,
456
+ loc : selector . loc as any ,
457
+ isStatic : false ,
458
+ } ,
459
+ }
460
+ }
461
+ }
462
+ }
463
+
464
+ const content = effect . expressions [ 0 ] . content
465
+ if (
466
+ typeof ast === 'object' &&
467
+ ast &&
468
+ ast . type === 'ConditionalExpression' &&
469
+ ast . test . type === 'BinaryExpression' &&
470
+ ast . test . operator === '===' &&
471
+ ast . test . left . type !== 'PrivateName' &&
472
+ isStaticNode ( ast . consequent ) &&
473
+ isStaticNode ( ast . alternate )
474
+ ) {
475
+ const left = ast . test . left
476
+ const right = ast . test . right
477
+ for ( const [ a , b ] of [
478
+ [ left , right ] ,
479
+ [ right , left ] ,
480
+ ] ) {
481
+ const aIsKey = isKeyOnlyBinding ( a , keyAst )
482
+ const bIsKey = isKeyOnlyBinding ( b , keyAst )
483
+ const bVars = analyzeVariableScopes ( b , idMap )
484
+ if ( aIsKey && ! bIsKey && ! bVars . locals . length ) {
485
+ return {
486
+ effect,
487
+ // @ts -expect-error
488
+ selector : {
489
+ content : content . slice ( b . start ! - offset , b . end ! - offset ) ,
490
+ ast : b ,
491
+ loc : b . loc as any ,
492
+ isStatic : false ,
493
+ } ,
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+ }
500
+
501
+ function analyzeVariableScopes (
502
+ ast : Node ,
503
+ idMap : Record < string , string | SimpleExpressionNode | null > ,
504
+ ) {
505
+ const globals : string [ ] = [ ]
506
+ const locals : string [ ] = [ ]
507
+
508
+ const ids : Identifier [ ] = [ ]
509
+ const parentStackMap = new Map < Identifier , Node [ ] > ( )
510
+ const parentStack : Node [ ] = [ ]
511
+ walkIdentifiers (
512
+ ast ,
513
+ ( id ) => {
514
+ ids . push ( id )
515
+ parentStackMap . set ( id , parentStack . slice ( ) )
516
+ } ,
517
+ false ,
518
+ parentStack ,
519
+ )
520
+
521
+ for ( const id of ids ) {
522
+ if ( isGloballyAllowed ( id . name ) ) {
523
+ continue
524
+ }
525
+ if ( idMap [ id . name ] ) {
526
+ locals . push ( id . name )
527
+ } else {
528
+ globals . push ( id . name )
529
+ }
530
+ }
531
+
532
+ return { globals, locals }
533
+ }
534
+
535
+ function isKeyOnlyBinding ( expr : Node , keyAst : any ) {
536
+ let only = true
537
+ walkAST ( expr , {
538
+ enter ( node ) {
539
+ if ( isNodesEquivalent ( node , keyAst ) ) {
540
+ this . skip ( )
541
+ return
542
+ }
543
+ if ( node . type === 'Identifier' ) {
544
+ only = false
545
+ }
546
+ } ,
547
+ } )
548
+ return only
549
+ }
0 commit comments