feat: support optional enableObjectSlots (#259)

* feat: user can toggle off the object slot syntax parser (#257)

* chore(deps-dev): bump vue from 3.0.0 to 3.0.5 (#246)

Bumps [vue](https://github.com/vuejs/vue) from 3.0.0 to 3.0.5.
- [Release notes](https://github.com/vuejs/vue/releases)
- [Commits](https://github.com/vuejs/vue/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* chore(deps-dev): bump @typescript-eslint/eslint-plugin (#244)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.9.1 to 4.11.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.11.1/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* chore(deps-dev): bump @typescript-eslint/parser from 4.9.1 to 4.11.1 (#245)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.9.1 to 4.11.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.11.1/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* feat: user can toggle off the object slot syntax parser

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

* feat: default value of enableObjectSlots should be true

Co-authored-by: 逆寒 <869732751@qq.com>
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
This commit is contained in:
Amour1688 2021-01-08 23:18:56 +08:00 committed by GitHub
parent 67d16001e6
commit 9238fab697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 53 deletions

View File

@ -18,6 +18,7 @@ export interface Opts {
optimize?: boolean; optimize?: boolean;
mergeProps?: boolean; mergeProps?: boolean;
isCustomElement?: (tag: string) => boolean; isCustomElement?: (tag: string) => boolean;
enableObjectSlots?: boolean;
} }
export type ExcludesBoolean = <T>(x: T | false | true) => x is T; export type ExcludesBoolean = <T>(x: T | false | true) => x is T;
@ -70,7 +71,7 @@ export default ({ types }: typeof BabelCore) => ({
importNames.forEach((name) => { importNames.forEach((name) => {
state.set(name, () => { state.set(name, () => {
if (importMap[name]) { if (importMap[name]) {
return types.cloneDeep(importMap[name]); return types.cloneNode(importMap[name]);
} }
const identifier = addNamed( const identifier = addNamed(
path, path,
@ -84,24 +85,27 @@ export default ({ types }: typeof BabelCore) => ({
return identifier; return identifier;
}); });
}); });
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => { const { enableObjectSlots = true } = state.opts;
if (importMap.runtimeIsSlot) { if (enableObjectSlots) {
return importMap.runtimeIsSlot; state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
} if (importMap.runtimeIsSlot) {
const { name: isVNodeName } = state.get('isVNode')(); return importMap.runtimeIsSlot;
const isSlot = path.scope.generateUidIdentifier('isSlot');
const ast = template.ast`
function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
} }
`; const { name: isVNodeName } = state.get('isVNode')();
const lastImport = (path.get('body') as NodePath[]).filter((p) => p.isImportDeclaration()).pop(); const isSlot = path.scope.generateUidIdentifier('isSlot');
if (lastImport) { const ast = template.ast`
lastImport.insertAfter(ast); function ${isSlot.name}(s) {
} return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
importMap.runtimeIsSlot = isSlot; }
return isSlot; `;
}); const lastImport = (path.get('body') as NodePath[]).filter((p) => p.isImportDeclaration()).pop();
if (lastImport) {
lastImport.insertAfter(ast);
}
importMap.runtimeIsSlot = isSlot;
return isSlot;
});
}
} else { } else {
// var _vue = require('vue'); // var _vue = require('vue');
let sourceName = ''; let sourceName = '';

View File

@ -82,10 +82,14 @@ const transformJSXElement = (
const { optimize = false } = state.opts; const { optimize = false } = state.opts;
const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE; const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
let VNodeChild; let VNodeChild;
if (children.length > 1 || slots) { if (children.length > 1 || slots) {
/*
<A v-slots={slots}>{a}{b}</A>
---> {{ default: () => [a, b], ...slots }}
---> {[a, b]}
*/
VNodeChild = isComponent ? t.objectExpression([ VNodeChild = isComponent ? t.objectExpression([
!!children.length && t.objectProperty( !!children.length && t.objectProperty(
t.identifier('default'), t.identifier('default'),
@ -102,51 +106,61 @@ const transformJSXElement = (
), ),
].filter(Boolean as any)) : t.arrayExpression(children); ].filter(Boolean as any)) : t.arrayExpression(children);
} else if (children.length === 1) { } else if (children.length === 1) {
/*
<A>{a}</A> or <A>{() => a}</A>
*/
const { enableObjectSlots = true } = state.opts;
const child = children[0]; const child = children[0];
const objectExpression = t.objectExpression([
t.objectProperty(
t.identifier('default'),
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [child]))),
),
optimize && t.objectProperty(
t.identifier('_'),
t.numericLiteral(slotFlag),
) as any,
].filter(Boolean));
if (t.isIdentifier(child)) { if (t.isIdentifier(child)) {
VNodeChild = t.conditionalExpression( VNodeChild = enableObjectSlots ? t.conditionalExpression(
t.callExpression(state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), [child]), t.callExpression(state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), [child]),
child, child,
t.objectExpression([ objectExpression,
t.objectProperty( ) : objectExpression;
t.identifier('default'),
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [child]))),
),
optimize && t.objectProperty(
t.identifier('_'),
t.numericLiteral(slotFlag),
) as any,
].filter(Boolean)),
);
} else if ( } else if (
t.isCallExpression(child) && child.loc && isComponent t.isCallExpression(child) && child.loc && isComponent
) { // the element was generated and doesn't have location information ) { // the element was generated and doesn't have location information
const { scope } = path; if (enableObjectSlots) {
const slotId = scope.generateUidIdentifier('slot'); const { scope } = path;
if (scope) { const slotId = scope.generateUidIdentifier('slot');
scope.push({ if (scope) {
id: slotId, scope.push({
kind: 'let', id: slotId,
}); kind: 'let',
} });
}
VNodeChild = t.conditionalExpression( const alternate = t.objectExpression([
t.callExpression(
state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(),
[t.assignmentExpression('=', slotId, child)],
),
slotId,
t.objectExpression([
t.objectProperty( t.objectProperty(
t.identifier('default'), t.identifier('default'),
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [slotId]))), t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [slotId]))),
), ), optimize && t.objectProperty(
optimize && t.objectProperty(
t.identifier('_'), t.identifier('_'),
t.numericLiteral(slotFlag), t.numericLiteral(slotFlag),
) as any, ) as any,
].filter(Boolean)), ].filter(Boolean));
); const assignment = t.assignmentExpression('=', slotId, child);
const condition = t.callExpression(
state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(),
[assignment],
);
VNodeChild = t.conditionalExpression(
condition,
slotId,
alternate,
);
} else {
VNodeChild = objectExpression;
}
} else if (t.isFunctionExpression(child) || t.isArrowFunctionExpression(child)) { } else if (t.isFunctionExpression(child) || t.isArrowFunctionExpression(child)) {
VNodeChild = t.objectExpression([ VNodeChild = t.objectExpression([
t.objectProperty( t.objectProperty(

View File

@ -42,6 +42,16 @@ import { resolveComponent as _resolveComponent } from \\"vue\\";
_withDirectives(_createVNode(_resolveComponent(\\"A\\"), null, null, 512), [[_resolveDirective(\\"cus\\"), x]]);" _withDirectives(_createVNode(_resolveComponent(\\"A\\"), null, null, 512), [[_resolveDirective(\\"cus\\"), x]]);"
`; `;
exports[`disable object slot syntax with defaultSlot: defaultSlot 1`] = `
"import { createVNode as _createVNode } from \\"vue\\";
import { resolveComponent as _resolveComponent } from \\"vue\\";
_createVNode(_resolveComponent(\\"Badge\\"), null, {
default: () => [slots.default()],
_: 1
});"
`;
exports[`dynamic type in input: dynamic type in input 1`] = ` exports[`dynamic type in input: dynamic type in input 1`] = `
"import { withDirectives as _withDirectives } from \\"vue\\"; "import { withDirectives as _withDirectives } from \\"vue\\";
import { createVNode as _createVNode } from \\"vue\\"; import { createVNode as _createVNode } from \\"vue\\";

View File

@ -155,7 +155,7 @@ tests.forEach((
test( test(
name, name,
async () => { async () => {
expect(await transpile(from, { optimize: true })).toMatchSnapshot(name); expect(await transpile(from, { optimize: true, enableObjectSlots: true })).toMatchSnapshot(name);
}, },
); );
}); });
@ -205,7 +205,26 @@ slotsTests.forEach(({
test( test(
`passing object slots via JSX children ${name}`, `passing object slots via JSX children ${name}`,
async () => { async () => {
expect(await transpile(from, { optimize: true })).toMatchSnapshot(name); expect(await transpile(from, { optimize: true, enableObjectSlots: true })).toMatchSnapshot(name);
},
);
});
const objectSlotsTests = [
{
name: 'defaultSlot',
from: '<Badge>{slots.default()}</Badge>',
},
];
objectSlotsTests.forEach(({
name, from,
}) => {
test(
`disable object slot syntax with ${name}`,
async () => {
expect(await transpile(from, { optimize: true, enableObjectSlots: false }))
.toMatchSnapshot(name);
}, },
); );
}); });