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
This commit is contained in:
Amour1688 2020-07-28 21:25:30 +08:00 committed by GitHub
parent ebbd992ba0
commit 89afeac355
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 122 additions and 39 deletions

View File

@ -1,3 +1,4 @@
/* istanbul ignore next */
module.exports = { module.exports = {
presets: [ presets: [
'@babel/preset-env', '@babel/preset-env',

View File

@ -14,6 +14,7 @@ import parseDirectives from './parseDirectives';
import { PatchFlags } from './patchFlags'; import { PatchFlags } from './patchFlags';
import { State, ExcludesBoolean } from '.'; import { State, ExcludesBoolean } from '.';
import { transformJSXElement } from './transform-vue-jsx'; import { transformJSXElement } from './transform-vue-jsx';
import SlotFlags from './slotFlags';
const xlinkRE = /^xlink([A-Z])/; const xlinkRE = /^xlink([A-Z])/;
const onRE = /^on[^a-z]/; const onRE = /^on[^a-z]/;
@ -51,7 +52,7 @@ const transformJSXSpreadAttribute = (
const { properties } = argument.node; const { properties } = argument.node;
if (!properties) { if (!properties) {
if (argument.isIdentifier()) { if (argument.isIdentifier()) {
walksScope(nodePath, (argument.node as t.Identifier).name); walksScope(nodePath, (argument.node as t.Identifier).name, SlotFlags.DYNAMIC);
} }
mergeArgs.push(argument.node); mergeArgs.push(argument.node);
} else { } else {
@ -173,8 +174,8 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
hasStyleBinding = true; hasStyleBinding = true;
} else if ( } else if (
name !== 'key' name !== 'key'
&& !isDirective(name) && !isDirective(name)
&& name !== 'on' && name !== 'on'
) { ) {
dynamicPropNames.add(name); dynamicPropNames.add(name);
} }

View File

@ -10,7 +10,6 @@ export type State = {
interface Opts { interface Opts {
transformOn?: boolean; transformOn?: boolean;
compatibleProps?: boolean;
optimize?: boolean; optimize?: boolean;
isCustomElement?: (tag: string) => boolean; isCustomElement?: (tag: string) => boolean;
} }

View File

@ -0,0 +1,24 @@
// https://github.com/vuejs/vue-next/blob/master/packages/shared/src/slotFlags.ts
const enum SlotFlags {
/**
* Stable slots that only reference slot props or context state. The slot
* can fully capture its own dependencies so when passed down the parent won't
* need to force the child to update.
*/
STABLE = 1,
/**
* Slots that reference scope variables (v-for or an outer slot prop), or
* has conditional structure (v-if, v-for). The parent will need to force
* the child to update because the slot does not fully capture its dependencies.
*/
DYNAMIC = 2,
/**
* `<slot/>` being forwarded into a child component. Whether the parent needs
* to update the child is dependent on what kind of slots the parent itself
* received. This has to be refined at runtime, when the child's vnode
* is being created (in `normalizeChildren`)
*/
FORWARDED = 3
}
export default SlotFlags;

View File

@ -1,16 +1,17 @@
import * as t from '@babel/types'; import * as t from '@babel/types';
import { NodePath } from '@babel/traverse'; import { NodePath } from '@babel/traverse';
import { addDefault, addNamespace } from '@babel/helper-module-imports'; import { addNamespace } from '@babel/helper-module-imports';
import { import {
createIdentifier, createIdentifier,
transformJSXSpreadChild, transformJSXSpreadChild,
transformJSXText, transformJSXText,
transformJSXExpressionContainer, transformJSXExpressionContainer,
walksScope, walksScope,
buildIIFE,
} from './utils'; } from './utils';
import buildProps from './buildProps'; import buildProps from './buildProps';
import { PatchFlags } from './patchFlags'; import SlotFlags from './slotFlags';
import { State, ExcludesBoolean } from '.'; import { State } from '.';
/** /**
* Get children from Array of JSX children * Get children from Array of JSX children
@ -42,7 +43,7 @@ const getChildren = (
const { name } = expression as t.Identifier; const { name } = expression as t.Identifier;
const { referencePaths = [] } = path.scope.getBinding(name) || {}; const { referencePaths = [] } = path.scope.getBinding(name) || {};
referencePaths.forEach((referencePath) => { referencePaths.forEach((referencePath) => {
walksScope(referencePath, name); walksScope(referencePath, name, SlotFlags.DYNAMIC);
}); });
} }
@ -79,45 +80,39 @@ const transformJSXElement = (
slots, slots,
} = buildProps(path, state); } = buildProps(path, state);
const useOptimate = path.getData('optimize') !== false; const { optimize = false } = state.opts;
const { compatibleProps = false, optimize = false } = state.opts; const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
if (compatibleProps && !state.get('compatibleProps')) {
state.set('compatibleProps', addDefault(
path, '@ant-design-vue/babel-helper-vue-compatible-props', { nameHint: '_compatibleProps' },
));
}
// @ts-ignore // @ts-ignore
const createVNode = t.callExpression(createIdentifier(state, optimize ? 'createVNode' : 'h'), [ const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
tag, tag,
// @ts-ignore props,
compatibleProps ? t.callExpression(state.get('compatibleProps'), [props]) : props,
(children.length || slots) ? ( (children.length || slots) ? (
isComponent isComponent
? t.objectExpression([ ? t.objectExpression([
!!children.length && t.objectProperty( !!children.length && t.objectProperty(
t.identifier('default'), t.identifier('default'),
t.arrowFunctionExpression([], t.arrayExpression(children)), t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, children))),
), ),
...(slots ? ( ...(slots ? (
t.isObjectExpression(slots) t.isObjectExpression(slots)
? (slots! as t.ObjectExpression).properties ? (slots! as t.ObjectExpression).properties
: [t.spreadElement(slots!)] : [t.spreadElement(slots!)]
) : []), ) : []),
].filter(Boolean as any as ExcludesBoolean)) optimize && t.objectProperty(
t.identifier('_'),
t.numericLiteral(slotFlag),
),
].filter(Boolean as any))
: t.arrayExpression(children) : t.arrayExpression(children)
) : t.nullLiteral(), ) : t.nullLiteral(),
!!patchFlag && optimize && ( !!patchFlag && optimize && t.numericLiteral(patchFlag),
useOptimate
? t.numericLiteral(patchFlag)
: t.numericLiteral(PatchFlags.BAIL)
),
!!dynamicPropNames.size && optimize !!dynamicPropNames.size && optimize
&& t.arrayExpression( && t.arrayExpression(
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name as string)), [...dynamicPropNames.keys()].map((name) => t.stringLiteral(name as string)),
), ),
].filter(Boolean as any as ExcludesBoolean)); ].filter(Boolean as any));
if (!directives.length) { if (!directives.length) {
return createVNode; return createVNode;

View File

@ -3,6 +3,7 @@ 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 } from '.'; import { State } from '.';
import SlotFlags from './slotFlags';
/** /**
* create Identifier * create Identifier
@ -181,15 +182,50 @@ const transformJSXSpreadChild = (
path: NodePath<t.JSXSpreadChild>, path: NodePath<t.JSXSpreadChild>,
): t.SpreadElement => t.spreadElement(path.get('expression').node); ): t.SpreadElement => t.spreadElement(path.get('expression').node);
const walksScope = (path: NodePath, name: string): void => { const walksScope = (path: NodePath, name: string, slotFlag: SlotFlags): void => {
if (path.scope.hasBinding(name) && path.parentPath) { if (path.scope.hasBinding(name) && path.parentPath) {
if (t.isJSXElement(path.parentPath.node)) { if (t.isJSXElement(path.parentPath.node)) {
path.parentPath.setData('optimize', false); path.parentPath.setData('slotFlag', slotFlag);
} }
walksScope(path.parentPath, name); walksScope(path.parentPath, name, slotFlag);
} }
}; };
const createInsertName = (path: NodePath, name: string): t.Identifier => {
if (path.scope.hasBinding(name)) {
return createInsertName(path, `_${name}`);
}
return t.identifier(name);
};
const buildIIFE = (path: NodePath<t.JSXElement>, children: t.Expression[]) => {
const { parentPath } = path;
if (t.isAssignmentExpression(parentPath)) {
const { left } = parentPath.node as t.AssignmentExpression;
if (t.isIdentifier(left)) {
return children.map((child) => {
if (t.isIdentifier(child) && child.name === left.name) {
const insertName = createInsertName(parentPath, `_${child.name}`);
parentPath.insertBefore(
t.variableDeclaration('const', [
t.variableDeclarator(
insertName,
t.callExpression(
t.functionExpression(null, [], t.blockStatement([t.returnStatement(child)])),
[],
),
),
]),
);
return insertName;
}
return child;
});
}
}
return children;
};
export { export {
createIdentifier, createIdentifier,
isDirective, isDirective,
@ -202,4 +238,5 @@ export {
transformJSXExpressionContainer, transformJSXExpressionContainer,
isFragment, isFragment,
walksScope, walksScope,
buildIIFE,
}; };

View File

@ -469,3 +469,30 @@ describe('variables outside slots', () => {
expect(wrapper.get('#textarea').element.innerHTML).toBe('1'); expect(wrapper.get('#textarea').element.innerHTML).toBe('1');
}); });
}); });
test('reassign variable as component should work', () => {
let a: any = 1;
const A = defineComponent({
setup(_, { slots }) {
return () => <span>{slots.default!()}</span>;
},
});
/* eslint-disable */
// @ts-ignore
const _a = 1;
// @ts-ignore
const __a = 2;
/* eslint-enable */
a = <A>{a}</A>;
const wrapper = mount({
render() {
return a;
},
});
expect(wrapper.html()).toBe('<span>1</span>');
});

View File

@ -63,7 +63,7 @@ const compile = () => {
console.clear(); console.clear();
transform(src, { transform(src, {
babelrc: false, babelrc: false,
plugins: [[babelPluginJSx, { transformOn: true }]], plugins: [[babelPluginJSx, { transformOn: true, optimize: true }]],
ast: true, ast: true,
}, (err, result = {}) => { }, (err, result = {}) => {
const res = result!; const res = result!;
@ -87,18 +87,17 @@ compile();
// update compile output when input changes // update compile output when input changes
editor.onDidChangeModelContent(debounce(compile)); editor.onDidChangeModelContent(debounce(compile));
function debounce<T extends (...args: any[]) => any>( function debounce<T extends(...args: any[]) => any>(
fn: T, fn: T,
delay: number = 300 delay = 300): T {
): T { let prevTimer: number | null = null;
let prevTimer: number | null = null
return ((...args: any[]) => { return ((...args: any[]) => {
if (prevTimer) { if (prevTimer) {
clearTimeout(prevTimer) clearTimeout(prevTimer);
} }
prevTimer = window.setTimeout(() => { prevTimer = window.setTimeout(() => {
fn(...args) fn(...args);
prevTimer = null prevTimer = null;
}, delay) }, delay);
}) as any }) as any;
} }