Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1c7865d

Browse files
authored
fix(compiler-vapor): preserve useId evaluation order before dynamic boundaries (#14782)
close #14781
1 parent 0506076 commit 1c7865d

13 files changed

Lines changed: 306 additions & 25 deletions

File tree

‎packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap‎

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
149149
`;
150150
151151
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
152-
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
152+
"import { resolveComponent as _resolveComponent, setProp as _setProp, renderEffect as _renderEffect, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, toDisplayString as _toDisplayString, setText as _setText, template as _template } from 'vue';
153153
const t0 = _template("<div :id=foo><Comp></Comp>{{ bar }}", false, true)
154154
const t1 = _template("<div> ")
155155
@@ -158,12 +158,10 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
158158
const n0 = t0()
159159
const n3 = t1()
160160
const n2 = _child(n3, 1)
161+
_renderEffect(() => _setProp(n3, "id", _ctx.foo))
161162
_setInsertionState(n3, 0, 0, true)
162163
const n1 = _createComponentWithFallback(_component_Comp)
163-
_renderEffect(() => {
164-
_setProp(n3, "id", _ctx.foo)
165-
_setText(n2, _toDisplayString(_ctx.bar))
166-
})
164+
_renderEffect(() => _setText(n2, _toDisplayString(_ctx.bar)))
167165
return [n0, n3]
168166
}"
169167
`;
@@ -212,6 +210,62 @@ export function render(_ctx) {
212210
}"
213211
`;
214212
213+
exports[`compile > execution order > does not flush later v-for effects before child component 1`] = `
214+
"import { resolveComponent as _resolveComponent, child as _child, next as _next, txt as _txt, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
215+
const t0 = _template("<div><span> </span><!><span> ")
216+
217+
export function render(_ctx, $props, $emit, $attrs, $slots) {
218+
const _component_Child = _resolveComponent("Child")
219+
let _selector0_0
220+
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
221+
const n6 = t0()
222+
const n2 = _child(n6)
223+
const n5 = _next(n2, 1)
224+
const n4 = _next(n5, 2)
225+
const x2 = _txt(n2)
226+
_setInsertionState(n6, n5, 1, true)
227+
const n3 = _createComponentWithFallback(_component_Child)
228+
const x4 = _txt(n4)
229+
_renderEffect(() => _setText(x4, _toDisplayString(_ctx.useId())))
230+
_selector0_0(() => {
231+
_setText(x2, _toDisplayString(_ctx.selected === _for_item0.value.id ? 'danger' : ''))
232+
})
233+
return n6
234+
}, (row) => (row.id), undefined, ({ createSelector }) => {
235+
_selector0_0 = createSelector(() => _ctx.selected)
236+
})
237+
return n0
238+
}"
239+
`;
240+
241+
exports[`compile > execution order > flushes parent props before creating child component 1`] = `
242+
"import { resolveComponent as _resolveComponent, setProp as _setProp, renderEffect as _renderEffect, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
243+
const t0 = _template("<div>", true)
244+
245+
export function render(_ctx, $props, $emit, $attrs, $slots) {
246+
const _component_Child = _resolveComponent("Child")
247+
const n1 = t0()
248+
_renderEffect(() => _setProp(n1, "id", _ctx.useId()))
249+
_setInsertionState(n1, null, 0, true)
250+
const n0 = _createComponentWithFallback(_component_Child)
251+
return n1
252+
}"
253+
`;
254+
255+
exports[`compile > execution order > flushes previous effects before creating child component 1`] = `
256+
"import { resolveComponent as _resolveComponent, txt as _txt, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
257+
const t0 = _template("<div> ")
258+
259+
export function render(_ctx, $props, $emit, $attrs, $slots) {
260+
const _component_Child = _resolveComponent("Child")
261+
const n0 = t0()
262+
const x0 = _txt(n0)
263+
_renderEffect(() => _setText(x0, "parent: " + _toDisplayString(_ctx.useId())))
264+
const n1 = _createComponentWithFallback(_component_Child)
265+
return [n0, n1]
266+
}"
267+
`;
268+
215269
exports[`compile > execution order > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = `
216270
"import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
217271
const t0 = _template("<div>", false, true)

‎packages/compiler-vapor/__tests__/compile.spec.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,54 @@ describe('compile', () => {
245245
)
246246
})
247247

248+
test('flushes previous effects before creating child component', () => {
249+
const code = compile(`<div>parent: {{ useId() }}</div><Child />`, {
250+
bindingMetadata: {
251+
useId: BindingTypes.SETUP_CONST,
252+
},
253+
})
254+
expect(code).matchSnapshot()
255+
expect(code).contains(
256+
`_renderEffect(() => _setText(x0, "parent: " + _toDisplayString(_ctx.useId())))
257+
const n1 = _createComponentWithFallback(_component_Child)`,
258+
)
259+
})
260+
261+
test('flushes parent props before creating child component', () => {
262+
const code = compile(`<div :id="useId()"><Child /></div>`, {
263+
bindingMetadata: {
264+
useId: BindingTypes.SETUP_CONST,
265+
},
266+
})
267+
expect(code).contains(
268+
`_renderEffect(() => _setProp(n1, "id", _ctx.useId()))
269+
_setInsertionState(n1, null, 0, true)
270+
const n0 = _createComponentWithFallback(_component_Child)`,
271+
)
272+
expect(code).matchSnapshot()
273+
})
274+
275+
test('does not flush later v-for effects before child component', () => {
276+
const code = compile(
277+
`<div v-for="row of rows" :key="row.id">
278+
<span>{{ selected === row.id ? 'danger' : '' }}</span>
279+
<Child />
280+
<span>{{ useId() }}</span>
281+
</div>`,
282+
{
283+
bindingMetadata: {
284+
useId: BindingTypes.SETUP_CONST,
285+
},
286+
},
287+
)
288+
expect(code).matchSnapshot()
289+
expect(code).contains(
290+
`const n3 = _createComponentWithFallback(_component_Child)
291+
const x4 = _txt(n4)
292+
_renderEffect(() => _setText(x4, _toDisplayString(_ctx.useId())))`,
293+
)
294+
})
295+
248296
describe('setInsertionState', () => {
249297
test('next, child and nthChild should be above the setInsertionState', () => {
250298
const code = compile(`

‎packages/compiler-vapor/src/generators/block.ts‎

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { BlockIRNode, CoreHelper } from '../ir'
1+
import type { BlockIRNode, CoreHelper, IRDynamicInfo } from '../ir'
2+
import { isBlockOperation } from '../ir'
23
import {
34
type CodeFragment,
45
DELIMITERS_ARRAY,
@@ -10,7 +11,11 @@ import {
1011
genMulti,
1112
} from './utils'
1213
import type { CodegenContext } from '../generate'
13-
import { genEffects, genOperations } from './operation'
14+
import {
15+
genEffects,
16+
genOperationWithInsertionState,
17+
genOperations,
18+
} from './operation'
1419
import { genChildren, genSelf } from './template'
1520
import { toValidAssetId } from '@vue/compiler-dom'
1621

@@ -61,17 +66,70 @@ export function genBlockContent(
6166
genResolveAssets('directive', 'resolveDirective')
6267
}
6368

69+
let operationIndex = 0
70+
let effectIndex = 0
71+
const flushPendingOperations = (
72+
operationEnd: number,
73+
effectEnd: number,
74+
push: (...items: CodeFragment[]) => number,
75+
) => {
76+
while (operationIndex < operationEnd) {
77+
push(
78+
...genOperationWithInsertionState(operation[operationIndex], context),
79+
)
80+
operationIndex++
81+
}
82+
83+
if (effectIndex < effectEnd) {
84+
push(...genEffects(effect.slice(effectIndex, effectEnd), context))
85+
effectIndex = effectEnd
86+
}
87+
}
88+
const flushBeforeDynamic = (
89+
dynamic: IRDynamicInfo,
90+
push: (...items: CodeFragment[]) => number,
91+
) => {
92+
const operation = dynamic.operation
93+
if (
94+
operation &&
95+
isBlockOperation(operation) &&
96+
operation.operationIndex !== undefined &&
97+
operation.effectIndex !== undefined
98+
) {
99+
flushPendingOperations(
100+
operation.operationIndex,
101+
operation.effectIndex,
102+
push,
103+
)
104+
}
105+
}
106+
64107
for (const child of dynamic.children) {
65-
push(...genSelf(child, context))
108+
flushBeforeDynamic(child, push)
109+
push(...genSelf(child, context, flushBeforeDynamic))
66110
}
67111
for (const child of dynamic.children) {
68112
if (!child.hasDynamicChild) {
69-
push(...genChildren(child, context, push, `n${child.id!}`))
113+
push(
114+
...genChildren(
115+
child,
116+
context,
117+
push,
118+
`n${child.id!}`,
119+
flushBeforeDynamic,
120+
),
121+
)
70122
}
71123
}
72124

73-
push(...genOperations(operation, context))
74-
push(...genEffects(effect, context, genEffectsExtraFrag))
125+
if (operationIndex < operation.length) {
126+
push(...genOperations(operation.slice(operationIndex), context))
127+
}
128+
if (effectIndex < effect.length) {
129+
push(...genEffects(effect.slice(effectIndex), context, genEffectsExtraFrag))
130+
} else if (genEffectsExtraFrag) {
131+
push(...genEffects([], context, genEffectsExtraFrag))
132+
}
75133

76134
push(NEWLINE, `return `)
77135

‎packages/compiler-vapor/src/generators/for.ts‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import {
66
import { genBlockContent } from './block'
77
import { genExpression } from './expression'
88
import type { CodegenContext } from '../generate'
9-
import type { BlockIRNode, ForIRNode, IREffect } from '../ir'
9+
import {
10+
type BlockIRNode,
11+
type ForIRNode,
12+
type IRDynamicInfo,
13+
type IREffect,
14+
isBlockOperation,
15+
} from '../ir'
1016
import {
1117
type CodeFragment,
1218
INDENT_END,
@@ -333,30 +339,63 @@ function matchPatterns(
333339
const keyOnlyBindingPatterns: NonNullable<
334340
ReturnType<typeof matchKeyOnlyBindingPattern>
335341
>[] = []
342+
const removedEffectIndexes: number[] = []
336343

337-
render.effect = render.effect.filter(effect => {
344+
render.effect = render.effect.filter((effect, index) => {
338345
if (keyProp !== undefined) {
339346
const selector = matchSelectorPattern(effect, keyProp.content, idMap)
340347
if (selector) {
341348
selectorPatterns.push(selector)
349+
removedEffectIndexes.push(index)
342350
return false
343351
}
344352
const keyOnly = matchKeyOnlyBindingPattern(effect, keyProp.content)
345353
if (keyOnly) {
346354
keyOnlyBindingPatterns.push(keyOnly)
355+
removedEffectIndexes.push(index)
347356
return false
348357
}
349358
}
350359

351360
return true
352361
})
353362

363+
if (removedEffectIndexes.length) {
364+
shiftEffectBoundaries(render.dynamic, removedEffectIndexes)
365+
}
366+
354367
return {
355368
keyOnlyBindingPatterns,
356369
selectorPatterns,
357370
}
358371
}
359372

373+
function shiftEffectBoundaries(
374+
dynamic: IRDynamicInfo,
375+
removedEffectIndexes: number[],
376+
): void {
377+
const operation = dynamic.operation
378+
if (
379+
operation &&
380+
isBlockOperation(operation) &&
381+
operation.effectIndex !== undefined
382+
) {
383+
let offset = 0
384+
for (const removedIndex of removedEffectIndexes) {
385+
if (removedIndex < operation.effectIndex) {
386+
offset++
387+
} else {
388+
break
389+
}
390+
}
391+
operation.effectIndex -= offset
392+
}
393+
394+
for (const child of dynamic.children) {
395+
shiftEffectBoundaries(child, removedEffectIndexes)
396+
}
397+
}
398+
360399
function matchKeyOnlyBindingPattern(
361400
effect: IREffect,
362401
key: string,

‎packages/compiler-vapor/src/generators/template.ts‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@ export function genTemplates(
4343
return result.join('')
4444
}
4545

46+
type FlushBeforeDynamic = (
47+
dynamic: IRDynamicInfo,
48+
push: (...items: CodeFragment[]) => number,
49+
) => void
50+
4651
export function genSelf(
4752
dynamic: IRDynamicInfo,
4853
context: CodegenContext,
54+
flushBeforeDynamic?: FlushBeforeDynamic,
4955
): CodeFragment[] {
5056
const [frag, push] = buildCodeFragment()
5157
const { id, template, operation, hasDynamicChild } = dynamic
@@ -60,7 +66,7 @@ export function genSelf(
6066
}
6167

6268
if (hasDynamicChild) {
63-
push(...genChildren(dynamic, context, push, `n${id}`))
69+
push(...genChildren(dynamic, context, push, `n${id}`, flushBeforeDynamic))
6470
}
6571

6672
return frag
@@ -71,6 +77,7 @@ export function genChildren(
7177
context: CodegenContext,
7278
pushBlock: (...items: CodeFragment[]) => number,
7379
from: string = `n${dynamic.id}`,
80+
flushBeforeDynamic?: FlushBeforeDynamic,
7481
): CodeFragment[] {
7582
const { helper } = context
7683
const [frag, push] = buildCodeFragment()
@@ -85,7 +92,8 @@ export function genChildren(
8592
}
8693

8794
if (child.flags & DynamicFlag.INSERT && child.template != null) {
88-
push(...genSelf(child, context))
95+
flushBeforeDynamic && flushBeforeDynamic(child, push)
96+
push(...genSelf(child, context, flushBeforeDynamic))
8997
continue
9098
}
9199

@@ -97,7 +105,8 @@ export function genChildren(
97105
: undefined
98106

99107
if (id === undefined && !child.hasDynamicChild) {
100-
push(...genSelf(child, context))
108+
flushBeforeDynamic && flushBeforeDynamic(child, push)
109+
push(...genSelf(child, context, flushBeforeDynamic))
101110
continue
102111
}
103112

@@ -150,15 +159,18 @@ export function genChildren(
150159
}
151160

152161
if (id === child.anchor && !child.hasDynamicChild) {
153-
push(...genSelf(child, context))
162+
flushBeforeDynamic && flushBeforeDynamic(child, push)
163+
push(...genSelf(child, context, flushBeforeDynamic))
154164
}
155165

156166
if (id !== undefined) {
157167
push(...genDirectivesForElement(id, context))
158168
}
159169

160170
prev = [variable, elementIndex]
161-
push(...genChildren(child, context, pushBlock, variable))
171+
push(
172+
...genChildren(child, context, pushBlock, variable, flushBeforeDynamic),
173+
)
162174
}
163175

164176
return frag

0 commit comments

Comments
 (0)