mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2024-11-10 09:39:14 +08:00
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:
parent
ebbd992ba0
commit
89afeac355
@ -1,3 +1,4 @@
|
||||
/* istanbul ignore next */
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
|
@ -14,6 +14,7 @@ import parseDirectives from './parseDirectives';
|
||||
import { PatchFlags } from './patchFlags';
|
||||
import { State, ExcludesBoolean } from '.';
|
||||
import { transformJSXElement } from './transform-vue-jsx';
|
||||
import SlotFlags from './slotFlags';
|
||||
|
||||
const xlinkRE = /^xlink([A-Z])/;
|
||||
const onRE = /^on[^a-z]/;
|
||||
@ -51,7 +52,7 @@ const transformJSXSpreadAttribute = (
|
||||
const { properties } = argument.node;
|
||||
if (!properties) {
|
||||
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);
|
||||
} else {
|
||||
@ -173,8 +174,8 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
||||
hasStyleBinding = true;
|
||||
} else if (
|
||||
name !== 'key'
|
||||
&& !isDirective(name)
|
||||
&& name !== 'on'
|
||||
&& !isDirective(name)
|
||||
&& name !== 'on'
|
||||
) {
|
||||
dynamicPropNames.add(name);
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ export type State = {
|
||||
|
||||
interface Opts {
|
||||
transformOn?: boolean;
|
||||
compatibleProps?: boolean;
|
||||
optimize?: boolean;
|
||||
isCustomElement?: (tag: string) => boolean;
|
||||
}
|
||||
|
24
packages/babel-plugin-jsx/src/slotFlags.ts
Normal file
24
packages/babel-plugin-jsx/src/slotFlags.ts
Normal 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;
|
@ -1,16 +1,17 @@
|
||||
import * as t from '@babel/types';
|
||||
import { NodePath } from '@babel/traverse';
|
||||
import { addDefault, addNamespace } from '@babel/helper-module-imports';
|
||||
import { addNamespace } from '@babel/helper-module-imports';
|
||||
import {
|
||||
createIdentifier,
|
||||
transformJSXSpreadChild,
|
||||
transformJSXText,
|
||||
transformJSXExpressionContainer,
|
||||
walksScope,
|
||||
buildIIFE,
|
||||
} from './utils';
|
||||
import buildProps from './buildProps';
|
||||
import { PatchFlags } from './patchFlags';
|
||||
import { State, ExcludesBoolean } from '.';
|
||||
import SlotFlags from './slotFlags';
|
||||
import { State } from '.';
|
||||
|
||||
/**
|
||||
* Get children from Array of JSX children
|
||||
@ -42,7 +43,7 @@ const getChildren = (
|
||||
const { name } = expression as t.Identifier;
|
||||
const { referencePaths = [] } = path.scope.getBinding(name) || {};
|
||||
referencePaths.forEach((referencePath) => {
|
||||
walksScope(referencePath, name);
|
||||
walksScope(referencePath, name, SlotFlags.DYNAMIC);
|
||||
});
|
||||
}
|
||||
|
||||
@ -79,45 +80,39 @@ const transformJSXElement = (
|
||||
slots,
|
||||
} = buildProps(path, state);
|
||||
|
||||
const useOptimate = path.getData('optimize') !== false;
|
||||
const { optimize = false } = state.opts;
|
||||
|
||||
const { compatibleProps = false, optimize = false } = state.opts;
|
||||
if (compatibleProps && !state.get('compatibleProps')) {
|
||||
state.set('compatibleProps', addDefault(
|
||||
path, '@ant-design-vue/babel-helper-vue-compatible-props', { nameHint: '_compatibleProps' },
|
||||
));
|
||||
}
|
||||
const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
|
||||
|
||||
// @ts-ignore
|
||||
const createVNode = t.callExpression(createIdentifier(state, optimize ? 'createVNode' : 'h'), [
|
||||
const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
|
||||
tag,
|
||||
// @ts-ignore
|
||||
compatibleProps ? t.callExpression(state.get('compatibleProps'), [props]) : props,
|
||||
props,
|
||||
(children.length || slots) ? (
|
||||
isComponent
|
||||
? t.objectExpression([
|
||||
!!children.length && t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression([], t.arrayExpression(children)),
|
||||
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, children))),
|
||||
),
|
||||
...(slots ? (
|
||||
t.isObjectExpression(slots)
|
||||
? (slots! as t.ObjectExpression).properties
|
||||
: [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.nullLiteral(),
|
||||
!!patchFlag && optimize && (
|
||||
useOptimate
|
||||
? t.numericLiteral(patchFlag)
|
||||
: t.numericLiteral(PatchFlags.BAIL)
|
||||
),
|
||||
!!patchFlag && optimize && t.numericLiteral(patchFlag),
|
||||
!!dynamicPropNames.size && optimize
|
||||
&& t.arrayExpression(
|
||||
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name as string)),
|
||||
),
|
||||
].filter(Boolean as any as ExcludesBoolean));
|
||||
].filter(Boolean as any));
|
||||
|
||||
if (!directives.length) {
|
||||
return createVNode;
|
||||
|
@ -3,6 +3,7 @@ import htmlTags from 'html-tags';
|
||||
import svgTags from 'svg-tags';
|
||||
import { NodePath } from '@babel/traverse';
|
||||
import { State } from '.';
|
||||
import SlotFlags from './slotFlags';
|
||||
|
||||
/**
|
||||
* create Identifier
|
||||
@ -181,15 +182,50 @@ const transformJSXSpreadChild = (
|
||||
path: NodePath<t.JSXSpreadChild>,
|
||||
): 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 (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 {
|
||||
createIdentifier,
|
||||
isDirective,
|
||||
@ -202,4 +238,5 @@ export {
|
||||
transformJSXExpressionContainer,
|
||||
isFragment,
|
||||
walksScope,
|
||||
buildIIFE,
|
||||
};
|
||||
|
@ -469,3 +469,30 @@ describe('variables outside slots', () => {
|
||||
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>');
|
||||
});
|
||||
|
@ -63,7 +63,7 @@ const compile = () => {
|
||||
console.clear();
|
||||
transform(src, {
|
||||
babelrc: false,
|
||||
plugins: [[babelPluginJSx, { transformOn: true }]],
|
||||
plugins: [[babelPluginJSx, { transformOn: true, optimize: true }]],
|
||||
ast: true,
|
||||
}, (err, result = {}) => {
|
||||
const res = result!;
|
||||
@ -87,18 +87,17 @@ compile();
|
||||
// update compile output when input changes
|
||||
editor.onDidChangeModelContent(debounce(compile));
|
||||
|
||||
function debounce<T extends (...args: any[]) => any>(
|
||||
function debounce<T extends(...args: any[]) => any>(
|
||||
fn: T,
|
||||
delay: number = 300
|
||||
): T {
|
||||
let prevTimer: number | null = null
|
||||
delay = 300): T {
|
||||
let prevTimer: number | null = null;
|
||||
return ((...args: any[]) => {
|
||||
if (prevTimer) {
|
||||
clearTimeout(prevTimer)
|
||||
clearTimeout(prevTimer);
|
||||
}
|
||||
prevTimer = window.setTimeout(() => {
|
||||
fn(...args)
|
||||
prevTimer = null
|
||||
}, delay)
|
||||
}) as any
|
||||
fn(...args);
|
||||
prevTimer = null;
|
||||
}, delay);
|
||||
}) as any;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user