Skip to content

Commit 89afeac

Browse files
authored
chore: mark slotflag and fix some bugs (#45)
* fix: reassign variable as component should work * feat: remove `compatibleProps` * feat: mark slotFlag * chore: istanbul ignore in babel.config.js * test: reassign variable as component should work
1 parent ebbd992 commit 89afeac

File tree

8 files changed

+122
-39
lines changed

8 files changed

+122
-39
lines changed

packages/babel-plugin-jsx/babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* istanbul ignore next */
12
module.exports = {
23
presets: [
34
'@babel/preset-env',

packages/babel-plugin-jsx/src/buildProps.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import parseDirectives from './parseDirectives';
1414
import { PatchFlags } from './patchFlags';
1515
import { State, ExcludesBoolean } from '.';
1616
import { transformJSXElement } from './transform-vue-jsx';
17+
import SlotFlags from './slotFlags';
1718

1819
const xlinkRE = /^xlink([A-Z])/;
1920
const onRE = /^on[^a-z]/;
@@ -51,7 +52,7 @@ const transformJSXSpreadAttribute = (
5152
const { properties } = argument.node;
5253
if (!properties) {
5354
if (argument.isIdentifier()) {
54-
walksScope(nodePath, (argument.node as t.Identifier).name);
55+
walksScope(nodePath, (argument.node as t.Identifier).name, SlotFlags.DYNAMIC);
5556
}
5657
mergeArgs.push(argument.node);
5758
} else {
@@ -173,8 +174,8 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
173174
hasStyleBinding = true;
174175
} else if (
175176
name !== 'key'
176-
&& !isDirective(name)
177-
&& name !== 'on'
177+
&& !isDirective(name)
178+
&& name !== 'on'
178179
) {
179180
dynamicPropNames.add(name);
180181
}

packages/babel-plugin-jsx/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ export type State = {
1010

1111
interface Opts {
1212
transformOn?: boolean;
13-
compatibleProps?: boolean;
1413
optimize?: boolean;
1514
isCustomElement?: (tag: string) => boolean;
1615
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// https://github.com/vuejs/vue-next/blob/master/packages/shared/src/slotFlags.ts
2+
const enum SlotFlags {
3+
/**
4+
* Stable slots that only reference slot props or context state. The slot
5+
* can fully capture its own dependencies so when passed down the parent won't
6+
* need to force the child to update.
7+
*/
8+
STABLE = 1,
9+
/**
10+
* Slots that reference scope variables (v-for or an outer slot prop), or
11+
* has conditional structure (v-if, v-for). The parent will need to force
12+
* the child to update because the slot does not fully capture its dependencies.
13+
*/
14+
DYNAMIC = 2,
15+
/**
16+
* `<slot/>` being forwarded into a child component. Whether the parent needs
17+
* to update the child is dependent on what kind of slots the parent itself
18+
* received. This has to be refined at runtime, when the child's vnode
19+
* is being created (in `normalizeChildren`)
20+
*/
21+
FORWARDED = 3
22+
}
23+
24+
export default SlotFlags;

packages/babel-plugin-jsx/src/transform-vue-jsx.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import * as t from '@babel/types';
22
import { NodePath } from '@babel/traverse';
3-
import { addDefault, addNamespace } from '@babel/helper-module-imports';
3+
import { addNamespace } from '@babel/helper-module-imports';
44
import {
55
createIdentifier,
66
transformJSXSpreadChild,
77
transformJSXText,
88
transformJSXExpressionContainer,
99
walksScope,
10+
buildIIFE,
1011
} from './utils';
1112
import buildProps from './buildProps';
12-
import { PatchFlags } from './patchFlags';
13-
import { State, ExcludesBoolean } from '.';
13+
import SlotFlags from './slotFlags';
14+
import { State } from '.';
1415

1516
/**
1617
* Get children from Array of JSX children
@@ -42,7 +43,7 @@ const getChildren = (
4243
const { name } = expression as t.Identifier;
4344
const { referencePaths = [] } = path.scope.getBinding(name) || {};
4445
referencePaths.forEach((referencePath) => {
45-
walksScope(referencePath, name);
46+
walksScope(referencePath, name, SlotFlags.DYNAMIC);
4647
});
4748
}
4849

@@ -79,45 +80,39 @@ const transformJSXElement = (
7980
slots,
8081
} = buildProps(path, state);
8182

82-
const useOptimate = path.getData('optimize') !== false;
83+
const { optimize = false } = state.opts;
8384

84-
const { compatibleProps = false, optimize = false } = state.opts;
85-
if (compatibleProps && !state.get('compatibleProps')) {
86-
state.set('compatibleProps', addDefault(
87-
path, '@ant-design-vue/babel-helper-vue-compatible-props', { nameHint: '_compatibleProps' },
88-
));
89-
}
85+
const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
9086

9187
// @ts-ignore
92-
const createVNode = t.callExpression(createIdentifier(state, optimize ? 'createVNode' : 'h'), [
88+
const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
9389
tag,
94-
// @ts-ignore
95-
compatibleProps ? t.callExpression(state.get('compatibleProps'), [props]) : props,
90+
props,
9691
(children.length || slots) ? (
9792
isComponent
9893
? t.objectExpression([
9994
!!children.length && t.objectProperty(
10095
t.identifier('default'),
101-
t.arrowFunctionExpression([], t.arrayExpression(children)),
96+
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, children))),
10297
),
10398
...(slots ? (
10499
t.isObjectExpression(slots)
105100
? (slots! as t.ObjectExpression).properties
106101
: [t.spreadElement(slots!)]
107102
) : []),
108-
].filter(Boolean as any as ExcludesBoolean))
103+
optimize && t.objectProperty(
104+
t.identifier('_'),
105+
t.numericLiteral(slotFlag),
106+
),
107+
].filter(Boolean as any))
109108
: t.arrayExpression(children)
110109
) : t.nullLiteral(),
111-
!!patchFlag && optimize && (
112-
useOptimate
113-
? t.numericLiteral(patchFlag)
114-
: t.numericLiteral(PatchFlags.BAIL)
115-
),
110+
!!patchFlag && optimize && t.numericLiteral(patchFlag),
116111
!!dynamicPropNames.size && optimize
117112
&& t.arrayExpression(
118113
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name as string)),
119114
),
120-
].filter(Boolean as any as ExcludesBoolean));
115+
].filter(Boolean as any));
121116

122117
if (!directives.length) {
123118
return createVNode;

packages/babel-plugin-jsx/src/utils.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import htmlTags from 'html-tags';
33
import svgTags from 'svg-tags';
44
import { NodePath } from '@babel/traverse';
55
import { State } from '.';
6+
import SlotFlags from './slotFlags';
67

78
/**
89
* create Identifier
@@ -181,15 +182,50 @@ const transformJSXSpreadChild = (
181182
path: NodePath<t.JSXSpreadChild>,
182183
): t.SpreadElement => t.spreadElement(path.get('expression').node);
183184

184-
const walksScope = (path: NodePath, name: string): void => {
185+
const walksScope = (path: NodePath, name: string, slotFlag: SlotFlags): void => {
185186
if (path.scope.hasBinding(name) && path.parentPath) {
186187
if (t.isJSXElement(path.parentPath.node)) {
187-
path.parentPath.setData('optimize', false);
188+
path.parentPath.setData('slotFlag', slotFlag);
188189
}
189-
walksScope(path.parentPath, name);
190+
walksScope(path.parentPath, name, slotFlag);
190191
}
191192
};
192193

194+
const createInsertName = (path: NodePath, name: string): t.Identifier => {
195+
if (path.scope.hasBinding(name)) {
196+
return createInsertName(path, `_${name}`);
197+
}
198+
return t.identifier(name);
199+
};
200+
201+
const buildIIFE = (path: NodePath<t.JSXElement>, children: t.Expression[]) => {
202+
const { parentPath } = path;
203+
if (t.isAssignmentExpression(parentPath)) {
204+
const { left } = parentPath.node as t.AssignmentExpression;
205+
if (t.isIdentifier(left)) {
206+
return children.map((child) => {
207+
if (t.isIdentifier(child) && child.name === left.name) {
208+
const insertName = createInsertName(parentPath, `_${child.name}`);
209+
parentPath.insertBefore(
210+
t.variableDeclaration('const', [
211+
t.variableDeclarator(
212+
insertName,
213+
t.callExpression(
214+
t.functionExpression(null, [], t.blockStatement([t.returnStatement(child)])),
215+
[],
216+
),
217+
),
218+
]),
219+
);
220+
return insertName;
221+
}
222+
return child;
223+
});
224+
}
225+
}
226+
return children;
227+
};
228+
193229
export {
194230
createIdentifier,
195231
isDirective,
@@ -202,4 +238,5 @@ export {
202238
transformJSXExpressionContainer,
203239
isFragment,
204240
walksScope,
241+
buildIIFE,
205242
};

packages/babel-plugin-jsx/test/index.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,30 @@ describe('variables outside slots', () => {
469469
expect(wrapper.get('#textarea').element.innerHTML).toBe('1');
470470
});
471471
});
472+
473+
test('reassign variable as component should work', () => {
474+
let a: any = 1;
475+
476+
const A = defineComponent({
477+
setup(_, { slots }) {
478+
return () => <span>{slots.default!()}</span>;
479+
},
480+
});
481+
482+
/* eslint-disable */
483+
// @ts-ignore
484+
const _a = 1;
485+
// @ts-ignore
486+
const __a = 2;
487+
/* eslint-enable */
488+
489+
a = <A>{a}</A>;
490+
491+
const wrapper = mount({
492+
render() {
493+
return a;
494+
},
495+
});
496+
497+
expect(wrapper.html()).toBe('<span>1</span>');
498+
});

packages/jsx-explorer/src/index.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const compile = () => {
6363
console.clear();
6464
transform(src, {
6565
babelrc: false,
66-
plugins: [[babelPluginJSx, { transformOn: true }]],
66+
plugins: [[babelPluginJSx, { transformOn: true, optimize: true }]],
6767
ast: true,
6868
}, (err, result = {}) => {
6969
const res = result!;
@@ -87,18 +87,17 @@ compile();
8787
// update compile output when input changes
8888
editor.onDidChangeModelContent(debounce(compile));
8989

90-
function debounce<T extends (...args: any[]) => any>(
90+
function debounce<T extends(...args: any[]) => any>(
9191
fn: T,
92-
delay: number = 300
93-
): T {
94-
let prevTimer: number | null = null
92+
delay = 300): T {
93+
let prevTimer: number | null = null;
9594
return ((...args: any[]) => {
9695
if (prevTimer) {
97-
clearTimeout(prevTimer)
96+
clearTimeout(prevTimer);
9897
}
9998
prevTimer = window.setTimeout(() => {
100-
fn(...args)
101-
prevTimer = null
102-
}, delay)
103-
}) as any
99+
fn(...args);
100+
prevTimer = null;
101+
}, delay);
102+
}) as any;
104103
}

0 commit comments

Comments
 (0)