mirror of
				https://github.com/vuejs/babel-plugin-jsx.git
				synced 2025-11-04 11:22:19 +08:00 
			
		
		
		
	feat: vSlots
This commit is contained in:
		@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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({
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user