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
Why Not props ?
```jsx
const App = {
setup() {
const slots = {
a: () => <div>A</div>,
b: () => <span>B</span>
}
return () => <A vSlots={slots} />
}
}
```
## Who is using

View File

@ -13,7 +13,7 @@ interface Opts {
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 () => ({
name: 'babel-plugin-jsx',

View File

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

View File

@ -2,7 +2,7 @@ import * as t from '@babel/types';
import htmlTags from 'html-tags';
import svgTags from 'svg-tags';
import { NodePath } from '@babel/traverse';
import { State, ExcludesFalse } from './';
import { State, ExcludesBoolean } from './';
/**
* 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', () => {
test('static', () => {
const wrapper = shallowMount({