Skip to content

Commit 7277fe4

Browse files
authoredMar 3, 2025··
fix: deoptimize slots when a directive is used (#638)
fixes #541 * fix: deoptimize slots when a directive is used * add test * feat: don't resolve directives in scope
1 parent a7607de commit 7277fe4

File tree

4 files changed

+114
-5
lines changed

4 files changed

+114
-5
lines changed
 

Diff for: ‎packages/babel-plugin-jsx/src/parseDirectives.ts

+5
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ const resolveDirective = (
184184
}
185185
return modelToUse;
186186
}
187+
const referenceName =
188+
'v' + directiveName[0].toUpperCase() + directiveName.slice(1);
189+
if (path.scope.references[referenceName]) {
190+
return t.identifier(referenceName);
191+
}
187192
return t.callExpression(createIdentifier(state, 'resolveDirective'), [
188193
t.stringLiteral(directiveName),
189194
]);

Diff for: ‎packages/babel-plugin-jsx/src/transform-vue-jsx.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,26 @@ const transformJSXElement = (
405405

406406
const { optimize = false } = state.opts;
407407

408-
const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
408+
// #541 - directives can't be resolved in optimized slots
409+
// all parents should be deoptimized
410+
if (
411+
directives.length &&
412+
directives.some(
413+
(d) =>
414+
d.elements?.[0]?.type === 'CallExpression' &&
415+
d.elements[0].callee.type === 'Identifier' &&
416+
d.elements[0].callee.name === '_resolveDirective'
417+
)
418+
) {
419+
let currentPath = path;
420+
while (currentPath.parentPath?.isJSXElement()) {
421+
currentPath = currentPath.parentPath;
422+
currentPath.setData('slotFlag', 0);
423+
}
424+
}
425+
426+
const slotFlag = path.getData('slotFlag') ?? SlotFlags.STABLE;
427+
const optimizeSlots = optimize && slotFlag !== 0;
409428
let VNodeChild;
410429

411430
if (children.length > 1 || slots) {
@@ -431,7 +450,7 @@ const transformJSXElement = (
431450
? (slots! as t.ObjectExpression).properties
432451
: [t.spreadElement(slots!)]
433452
: []),
434-
optimize &&
453+
optimizeSlots &&
435454
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
436455
].filter(Boolean as any)
437456
)
@@ -452,7 +471,7 @@ const transformJSXElement = (
452471
t.arrayExpression(buildIIFE(path, [child]))
453472
)
454473
),
455-
optimize &&
474+
optimizeSlots &&
456475
(t.objectProperty(
457476
t.identifier('_'),
458477
t.numericLiteral(slotFlag)
@@ -490,7 +509,7 @@ const transformJSXElement = (
490509
t.arrayExpression(buildIIFE(path, [slotId]))
491510
)
492511
),
493-
optimize &&
512+
optimizeSlots &&
494513
(t.objectProperty(
495514
t.identifier('_'),
496515
t.numericLiteral(slotFlag)
@@ -517,7 +536,7 @@ const transformJSXElement = (
517536
VNodeChild = t.objectExpression(
518537
[
519538
...child.properties,
520-
optimize &&
539+
optimizeSlots &&
521540
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
522541
].filter(Boolean as any)
523542
);

Diff for: ‎packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap

+44
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ _createVNode(_Fragment, null, [_withDirectives(_createVNode(_resolveComponent("A
6363
}]])]);"
6464
`;
6565
66+
exports[`directive in scope > directive in scope 1`] = `
67+
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withDirectives as _withDirectives } from "vue";
68+
const vXxx = {};
69+
_withDirectives(_createVNode(_resolveComponent("A"), null, null, 512), [[vXxx]]);"
70+
`;
71+
6672
exports[`disable object slot syntax with defaultSlot > defaultSlot 1`] = `
6773
"import { resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue";
6874
_createVNode(_resolveComponent("Badge"), null, {
@@ -153,6 +159,31 @@ exports[`override props single > single 1`] = `
153159
_createVNode("div", a, null);"
154160
`;
155161
162+
exports[`passing object slots via JSX children directive in slot > directive in slot 1`] = `
163+
"import { Fragment as _Fragment, resolveDirective as _resolveDirective, createVNode as _createVNode, withDirectives as _withDirectives, resolveComponent as _resolveComponent } from "vue";
164+
_createVNode(_Fragment, null, [_createVNode(_resolveComponent("A"), null, {
165+
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[_resolveDirective("xxx")]]), foo]
166+
}), _createVNode(_resolveComponent("A"), null, {
167+
default: () => [_createVNode(_resolveComponent("B"), null, {
168+
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[_resolveDirective("xxx")]]), foo]
169+
})]
170+
})]);"
171+
`;
172+
173+
exports[`passing object slots via JSX children directive in slot, in scope > directive in slot, in scope 1`] = `
174+
"import { Fragment as _Fragment, createVNode as _createVNode, withDirectives as _withDirectives, resolveComponent as _resolveComponent } from "vue";
175+
const vXxx = {};
176+
_createVNode(_Fragment, null, [_createVNode(_resolveComponent("A"), null, {
177+
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[vXxx]]), foo],
178+
_: 1
179+
}), _createVNode(_resolveComponent("A"), null, {
180+
default: () => [_createVNode(_resolveComponent("B"), null, {
181+
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[vXxx]]), foo],
182+
_: 1
183+
})]
184+
})]);"
185+
`;
186+
156187
exports[`passing object slots via JSX children multiple expressions > multiple expressions 1`] = `
157188
"import { resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue";
158189
_createVNode(_resolveComponent("A"), null, {
@@ -161,6 +192,19 @@ _createVNode(_resolveComponent("A"), null, {
161192
});"
162193
`;
163194
195+
exports[`passing object slots via JSX children no directive in slot > no directive in slot 1`] = `
196+
"import { Fragment as _Fragment, createVNode as _createVNode, resolveComponent as _resolveComponent } from "vue";
197+
_createVNode(_Fragment, null, [_createVNode(_resolveComponent("A"), null, {
198+
default: () => [_createVNode("div", null, null), foo],
199+
_: 1
200+
}), _createVNode(_resolveComponent("A"), null, {
201+
default: () => [_createVNode(_resolveComponent("B"), null, {
202+
default: () => [_createVNode("div", null, null), foo],
203+
_: 1
204+
})]
205+
})]);"
206+
`;
207+
164208
exports[`passing object slots via JSX children single expression, function expression > single expression, function expression 1`] = `
165209
"import { resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue";
166210
_createVNode(_resolveComponent("A"), null, {

Diff for: ‎packages/babel-plugin-jsx/test/snapshot.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ const transpile = (source: string, options: VueJSXPluginOptions = {}) =>
155155
</>
156156
`,
157157
},
158+
{
159+
name: 'directive in scope',
160+
from: `
161+
const vXxx = {};
162+
<A v-xxx />
163+
`,
164+
},
158165
{
159166
name: 'vModels',
160167
from: '<C v-models={[[foo, ["modifier"]], [bar, "bar", ["modifier1", "modifier2"]]]} />',
@@ -255,6 +262,40 @@ const slotsTests: Test[] = [
255262
<A>{foo()}</A>;
256263
`,
257264
},
265+
{
266+
name: 'no directive in slot',
267+
from: `
268+
<>
269+
<A><div />{foo}</A>
270+
<A>
271+
<B><div />{foo}</B>
272+
</A>
273+
</>
274+
`,
275+
},
276+
{
277+
name: 'directive in slot',
278+
from: `
279+
<>
280+
<A><div v-xxx />{foo}</A>
281+
<A>
282+
<B><div v-xxx />{foo}</B>
283+
</A>
284+
</>
285+
`,
286+
},
287+
{
288+
name: 'directive in slot, in scope',
289+
from: `
290+
const vXxx = {};
291+
<>
292+
<A><div v-xxx />{foo}</A>
293+
<A>
294+
<B><div v-xxx />{foo}</B>
295+
</A>
296+
</>
297+
`,
298+
},
258299
];
259300

260301
slotsTests.forEach(({ name, from }) => {

0 commit comments

Comments
 (0)
Please sign in to comment.