mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2025-04-24 18:42:32 +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 = {
|
module.exports = {
|
||||||
presets: [
|
presets: [
|
||||||
'@babel/preset-env',
|
'@babel/preset-env',
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
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 * 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;
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
@ -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>');
|
||||||
|
});
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user