Skip to content

Commit

Permalink
fix: deoptimize slots when a directive is used (#638)
Browse files Browse the repository at this point in the history
fixes #541

* fix: deoptimize slots when a directive is used

* add test

* feat: don't resolve directives in scope
  • Loading branch information
KaelWD authored Mar 3, 2025
1 parent a7607de commit 7277fe4
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 5 deletions.
5 changes: 5 additions & 0 deletions packages/babel-plugin-jsx/src/parseDirectives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ const resolveDirective = (
}
return modelToUse;
}
const referenceName =
'v' + directiveName[0].toUpperCase() + directiveName.slice(1);
if (path.scope.references[referenceName]) {
return t.identifier(referenceName);
}
return t.callExpression(createIdentifier(state, 'resolveDirective'), [
t.stringLiteral(directiveName),
]);
Expand Down
29 changes: 24 additions & 5 deletions packages/babel-plugin-jsx/src/transform-vue-jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,26 @@ const transformJSXElement = (

const { optimize = false } = state.opts;

const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
// #541 - directives can't be resolved in optimized slots
// all parents should be deoptimized
if (
directives.length &&
directives.some(
(d) =>
d.elements?.[0]?.type === 'CallExpression' &&
d.elements[0].callee.type === 'Identifier' &&
d.elements[0].callee.name === '_resolveDirective'
)
) {
let currentPath = path;
while (currentPath.parentPath?.isJSXElement()) {
currentPath = currentPath.parentPath;
currentPath.setData('slotFlag', 0);
}
}

const slotFlag = path.getData('slotFlag') ?? SlotFlags.STABLE;
const optimizeSlots = optimize && slotFlag !== 0;
let VNodeChild;

if (children.length > 1 || slots) {
Expand All @@ -431,7 +450,7 @@ const transformJSXElement = (
? (slots! as t.ObjectExpression).properties
: [t.spreadElement(slots!)]
: []),
optimize &&
optimizeSlots &&
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
].filter(Boolean as any)
)
Expand All @@ -452,7 +471,7 @@ const transformJSXElement = (
t.arrayExpression(buildIIFE(path, [child]))
)
),
optimize &&
optimizeSlots &&
(t.objectProperty(
t.identifier('_'),
t.numericLiteral(slotFlag)
Expand Down Expand Up @@ -490,7 +509,7 @@ const transformJSXElement = (
t.arrayExpression(buildIIFE(path, [slotId]))
)
),
optimize &&
optimizeSlots &&
(t.objectProperty(
t.identifier('_'),
t.numericLiteral(slotFlag)
Expand All @@ -517,7 +536,7 @@ const transformJSXElement = (
VNodeChild = t.objectExpression(
[
...child.properties,
optimize &&
optimizeSlots &&
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
].filter(Boolean as any)
);
Expand Down
44 changes: 44 additions & 0 deletions packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ _createVNode(_Fragment, null, [_withDirectives(_createVNode(_resolveComponent("A
}]])]);"
`;
exports[`directive in scope > directive in scope 1`] = `
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withDirectives as _withDirectives } from "vue";
const vXxx = {};
_withDirectives(_createVNode(_resolveComponent("A"), null, null, 512), [[vXxx]]);"
`;
exports[`disable object slot syntax with defaultSlot > defaultSlot 1`] = `
"import { resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue";
_createVNode(_resolveComponent("Badge"), null, {
Expand Down Expand Up @@ -153,6 +159,31 @@ exports[`override props single > single 1`] = `
_createVNode("div", a, null);"
`;
exports[`passing object slots via JSX children directive in slot > directive in slot 1`] = `
"import { Fragment as _Fragment, resolveDirective as _resolveDirective, createVNode as _createVNode, withDirectives as _withDirectives, resolveComponent as _resolveComponent } from "vue";
_createVNode(_Fragment, null, [_createVNode(_resolveComponent("A"), null, {
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[_resolveDirective("xxx")]]), foo]
}), _createVNode(_resolveComponent("A"), null, {
default: () => [_createVNode(_resolveComponent("B"), null, {
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[_resolveDirective("xxx")]]), foo]
})]
})]);"
`;
exports[`passing object slots via JSX children directive in slot, in scope > directive in slot, in scope 1`] = `
"import { Fragment as _Fragment, createVNode as _createVNode, withDirectives as _withDirectives, resolveComponent as _resolveComponent } from "vue";
const vXxx = {};
_createVNode(_Fragment, null, [_createVNode(_resolveComponent("A"), null, {
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[vXxx]]), foo],
_: 1
}), _createVNode(_resolveComponent("A"), null, {
default: () => [_createVNode(_resolveComponent("B"), null, {
default: () => [_withDirectives(_createVNode("div", null, null, 512), [[vXxx]]), foo],
_: 1
})]
})]);"
`;
exports[`passing object slots via JSX children multiple expressions > multiple expressions 1`] = `
"import { resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue";
_createVNode(_resolveComponent("A"), null, {
Expand All @@ -161,6 +192,19 @@ _createVNode(_resolveComponent("A"), null, {
});"
`;
exports[`passing object slots via JSX children no directive in slot > no directive in slot 1`] = `
"import { Fragment as _Fragment, createVNode as _createVNode, resolveComponent as _resolveComponent } from "vue";
_createVNode(_Fragment, null, [_createVNode(_resolveComponent("A"), null, {
default: () => [_createVNode("div", null, null), foo],
_: 1
}), _createVNode(_resolveComponent("A"), null, {
default: () => [_createVNode(_resolveComponent("B"), null, {
default: () => [_createVNode("div", null, null), foo],
_: 1
})]
})]);"
`;
exports[`passing object slots via JSX children single expression, function expression > single expression, function expression 1`] = `
"import { resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue";
_createVNode(_resolveComponent("A"), null, {
Expand Down
41 changes: 41 additions & 0 deletions packages/babel-plugin-jsx/test/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ const transpile = (source: string, options: VueJSXPluginOptions = {}) =>
</>
`,
},
{
name: 'directive in scope',
from: `
const vXxx = {};
<A v-xxx />
`,
},
{
name: 'vModels',
from: '<C v-models={[[foo, ["modifier"]], [bar, "bar", ["modifier1", "modifier2"]]]} />',
Expand Down Expand Up @@ -255,6 +262,40 @@ const slotsTests: Test[] = [
<A>{foo()}</A>;
`,
},
{
name: 'no directive in slot',
from: `
<>
<A><div />{foo}</A>
<A>
<B><div />{foo}</B>
</A>
</>
`,
},
{
name: 'directive in slot',
from: `
<>
<A><div v-xxx />{foo}</A>
<A>
<B><div v-xxx />{foo}</B>
</A>
</>
`,
},
{
name: 'directive in slot, in scope',
from: `
const vXxx = {};
<>
<A><div v-xxx />{foo}</A>
<A>
<B><div v-xxx />{foo}</B>
</A>
</>
`,
},
];

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

0 comments on commit 7277fe4

Please sign in to comment.