11import { parseExpression } from '@babel/parser'
2+ import {
3+ isNodesEquivalent ,
4+ type Expression ,
5+ type Identifier ,
6+ type Node ,
7+ } from '@babel/types'
28import {
39 createSimpleExpression ,
10+ isStaticNode ,
411 walkIdentifiers ,
512 type SimpleExpressionNode ,
613} from '@vue/compiler-dom'
14+ import { extend , isGloballyAllowed } from '@vue/shared'
15+ import { walkAST } from 'ast-kit'
716import 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'
1019import { 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'
1329
1430/**
1531 * Flags to optimize vapor `createFor` runtime behavior, shared between the
@@ -98,7 +114,60 @@ export function genFor(
98114 idMap [ indexVar ] = null
99115 }
100116
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 )
102171 exitScope ( )
103172
104173 let flags = 0
@@ -123,6 +192,7 @@ export function genFor(
123192 flags ? String ( flags ) : undefined ,
124193 // todo: hydrationNode
125194 ) ,
195+ ...patternFrag ,
126196 ]
127197
128198 // construct a id -> accessor path map.
@@ -251,3 +321,229 @@ export function genFor(
251321 return idMap
252322 }
253323}
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