feat: vSlots

This commit is contained in:
Amour1688 2020-07-11 23:14:18 +08:00
parent e8861330ad
commit 17a0e56342
6 changed files with 91 additions and 16 deletions

View File

@ -156,7 +156,17 @@ const App = {
### 插槽 ### 插槽
目前功能没有想好怎么实现,欢迎在 issue 中讨论,可以先使用 `props` 来代替 ```jsx
const App = {
setup() {
const slots = {
a: () => <div>A</div>,
b: () => <span>B</span>
}
return () => <A vSlots={slots} />
}
}
```
## 谁在用 ## 谁在用

View File

@ -157,7 +157,17 @@ const App = {
### Slot ### Slot
Why Not props ? ```jsx
const App = {
setup() {
const slots = {
a: () => <div>A</div>,
b: () => <span>B</span>
}
return () => <A vSlots={slots} />
}
}
```
## Who is using ## Who is using

View File

@ -13,7 +13,7 @@ interface Opts {
compatibleProps?: boolean; compatibleProps?: boolean;
} }
export type ExcludesFalse = <T>(x: T | false) => x is T; export type ExcludesBoolean = <T>(x: T | false | true) => x is T;
export default () => ({ export default () => ({
name: 'babel-plugin-jsx', name: 'babel-plugin-jsx',

View File

@ -14,7 +14,7 @@ import {
isFragment, isFragment,
} from './utils'; } from './utils';
import { PatchFlags, PatchFlagNames } from './patchFlags'; import { PatchFlags, PatchFlagNames } from './patchFlags';
import { State, ExcludesFalse } from './'; import { State, ExcludesBoolean } from './';
const xlinkRE = /^xlink([A-Z])/; const xlinkRE = /^xlink([A-Z])/;
const onRE = /^on[^a-z]/; const onRE = /^on[^a-z]/;
@ -116,6 +116,7 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
const directives: t.ArrayExpression[] = []; const directives: t.ArrayExpression[] = [];
const dynamicPropNames = new Set(); const dynamicPropNames = new Set();
let slots: t.Identifier | t.Expression | null = null;
let patchFlag = 0; let patchFlag = 0;
if (isFragment(path.get('openingElement').get('name'))) { if (isFragment(path.get('openingElement').get('name'))) {
@ -126,6 +127,7 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
return { return {
tag, tag,
isComponent, isComponent,
slots,
props: t.nullLiteral(), props: t.nullLiteral(),
directives, directives,
patchFlag, patchFlag,
@ -201,7 +203,10 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
value: attributeValue, value: attributeValue,
}); });
if (directive) { if (directiveName === 'slots') {
slots = attributeValue;
return;
} else if (directive) {
directives.push(t.arrayExpression(directive)); directives.push(t.arrayExpression(directive));
} else { } else {
// must be v-model and is a component // must be v-model and is a component
@ -302,7 +307,7 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
[ [
...exps, ...exps,
!!objectProperties.length && t.objectExpression(objectProperties), !!objectProperties.length && t.objectExpression(objectProperties),
].filter(Boolean as any as ExcludesFalse), ].filter(Boolean as any as ExcludesBoolean),
); );
} else { } else {
// single no need for a mergeProps call // single no need for a mergeProps call
@ -316,6 +321,7 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
tag, tag,
props: propsExpression, props: propsExpression,
isComponent, isComponent,
slots,
directives, directives,
patchFlag, patchFlag,
dynamicPropNames, dynamicPropNames,
@ -377,6 +383,7 @@ const transformJSXElement = (
directives, directives,
patchFlag, patchFlag,
dynamicPropNames, dynamicPropNames,
slots
} = buildProps(path, state); } = buildProps(path, state);
const { scope: { bindings } } = path; const { scope: { bindings } } = path;
@ -403,18 +410,25 @@ const transformJSXElement = (
tag, tag,
// @ts-ignore // @ts-ignore
compatibleProps ? t.callExpression(state.get('compatibleProps'), [props]) : props, compatibleProps ? t.callExpression(state.get('compatibleProps'), [props]) : props,
!!children.length ? ( (children.length || slots) ? (
isComponent ? t.objectExpression([ isComponent && (children.length || slots)
t.objectProperty( ? t.objectExpression([
!!children.length && t.objectProperty(
t.identifier('default'), t.identifier('default'),
t.arrowFunctionExpression([], t.arrayExpression(children)) t.arrowFunctionExpression([], t.arrayExpression(children))
) ),
]) : t.arrayExpression(children) ...(slots ? (
t.isObjectExpression(slots)
? (slots as any as t.ObjectExpression).properties
: [t.spreadElement(slots as any)]
) : [])
].filter(Boolean as any as ExcludesBoolean))
: t.arrayExpression(children)
) : t.nullLiteral(), ) : t.nullLiteral(),
!!patchFlag && t.addComment(t.numericLiteral(patchFlag), 'trailing', ` ${flagNames} `, false), !!patchFlag && t.addComment(t.numericLiteral(patchFlag), 'trailing', ` ${flagNames} `, false),
!!dynamicPropNames.size !!dynamicPropNames.size
&& t.arrayExpression([...dynamicPropNames.keys()].map((name) => t.stringLiteral(name as string))), && t.arrayExpression([...dynamicPropNames.keys()].map((name) => t.stringLiteral(name as string))),
].filter(Boolean as any as ExcludesFalse)); ].filter(Boolean as any as ExcludesBoolean));
if (!directives.length) { if (!directives.length) {
return createVNode; return createVNode;

View File

@ -2,7 +2,7 @@ import * as t from '@babel/types';
import htmlTags from 'html-tags'; import htmlTags from 'html-tags';
import svgTags from 'svg-tags'; import svgTags from 'svg-tags';
import { NodePath } from '@babel/traverse'; import { NodePath } from '@babel/traverse';
import { State, ExcludesFalse } from './'; import { State, ExcludesBoolean } from './';
/** /**
* create Identifier * create Identifier
@ -270,7 +270,7 @@ const parseDirectives = (args: {
), ),
), ),
), ),
].filter(Boolean as any as ExcludesFalse) : undefined, ].filter(Boolean as any as ExcludesBoolean) : undefined,
}; };
}; };

View File

@ -263,6 +263,47 @@ describe('Transform JSX', () => {
}); });
}); });
describe('slots', () => {
test('with default', () => {
const A = (_, { slots }) => (
<>
{slots.default()}
{slots.foo('val')}
</>
);
const wrapper = mount({
setup() {
const slots = {
foo: (val) => <div>{val}</div>,
};
return () => <A vSlots={slots}><span>default</span></A>;
},
});
expect(wrapper.html()).toBe('<span>default</span><div>val</div>');
});
test('without default', () => {
const A = (_, { slots }) => (
<>
{slots.foo('foo')}
</>
);
const wrapper = mount({
setup() {
const slots = {
foo: (val) => <div>{val}</div>,
};
return () => <A vSlots={slots} />;
},
});
expect(wrapper.html()).toBe('<div>foo</div>');
});
});
describe('PatchFlags', () => { describe('PatchFlags', () => {
test('static', () => { test('static', () => {
const wrapper = shallowMount({ const wrapper = shallowMount({