From 9238fab6978f497ec3ab9a6da8e450004408fbf0 Mon Sep 17 00:00:00 2001 From: Amour1688 <31695475+Amour1688@users.noreply.github.com> Date: Fri, 8 Jan 2021 23:18:56 +0800 Subject: [PATCH] feat: support optional `enableObjectSlots` (#259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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] 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] 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] 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> --- packages/babel-plugin-jsx/src/index.ts | 40 +++++----- .../babel-plugin-jsx/src/transform-vue-jsx.ts | 80 +++++++++++-------- .../test/__snapshots__/snapshot.test.ts.snap | 10 +++ .../babel-plugin-jsx/test/snapshot.test.ts | 23 +++++- 4 files changed, 100 insertions(+), 53 deletions(-) diff --git a/packages/babel-plugin-jsx/src/index.ts b/packages/babel-plugin-jsx/src/index.ts index 7bd62b2..ce1fe61 100644 --- a/packages/babel-plugin-jsx/src/index.ts +++ b/packages/babel-plugin-jsx/src/index.ts @@ -18,6 +18,7 @@ export interface Opts { optimize?: boolean; mergeProps?: boolean; isCustomElement?: (tag: string) => boolean; + enableObjectSlots?: boolean; } export type ExcludesBoolean = (x: T | false | true) => x is T; @@ -70,7 +71,7 @@ export default ({ types }: typeof BabelCore) => ({ importNames.forEach((name) => { state.set(name, () => { if (importMap[name]) { - return types.cloneDeep(importMap[name]); + return types.cloneNode(importMap[name]); } const identifier = addNamed( path, @@ -84,24 +85,27 @@ export default ({ types }: typeof BabelCore) => ({ return identifier; }); }); - state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => { - if (importMap.runtimeIsSlot) { - return importMap.runtimeIsSlot; - } - const { name: isVNodeName } = state.get('isVNode')(); - 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 { enableObjectSlots = true } = state.opts; + if (enableObjectSlots) { + state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => { + if (importMap.runtimeIsSlot) { + return importMap.runtimeIsSlot; } - `; - const lastImport = (path.get('body') as NodePath[]).filter((p) => p.isImportDeclaration()).pop(); - if (lastImport) { - lastImport.insertAfter(ast); - } - importMap.runtimeIsSlot = isSlot; - return isSlot; - }); + const { name: isVNodeName } = state.get('isVNode')(); + 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 lastImport = (path.get('body') as NodePath[]).filter((p) => p.isImportDeclaration()).pop(); + if (lastImport) { + lastImport.insertAfter(ast); + } + importMap.runtimeIsSlot = isSlot; + return isSlot; + }); + } } else { // var _vue = require('vue'); let sourceName = ''; diff --git a/packages/babel-plugin-jsx/src/transform-vue-jsx.ts b/packages/babel-plugin-jsx/src/transform-vue-jsx.ts index dda6796..7503d60 100644 --- a/packages/babel-plugin-jsx/src/transform-vue-jsx.ts +++ b/packages/babel-plugin-jsx/src/transform-vue-jsx.ts @@ -82,10 +82,14 @@ const transformJSXElement = ( const { optimize = false } = state.opts; const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE; - let VNodeChild; if (children.length > 1 || slots) { + /* + {a}{b} + ---> {{ default: () => [a, b], ...slots }} + ---> {[a, b]} + */ VNodeChild = isComponent ? t.objectExpression([ !!children.length && t.objectProperty( t.identifier('default'), @@ -102,51 +106,61 @@ const transformJSXElement = ( ), ].filter(Boolean as any)) : t.arrayExpression(children); } else if (children.length === 1) { + /* + {a} or {() => a} + */ + const { enableObjectSlots = true } = state.opts; 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)) { - VNodeChild = t.conditionalExpression( + VNodeChild = enableObjectSlots ? t.conditionalExpression( t.callExpression(state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), [child]), child, - 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)), - ); + objectExpression, + ) : objectExpression; } else if ( t.isCallExpression(child) && child.loc && isComponent ) { // the element was generated and doesn't have location information - const { scope } = path; - const slotId = scope.generateUidIdentifier('slot'); - if (scope) { - scope.push({ - id: slotId, - kind: 'let', - }); - } - - VNodeChild = t.conditionalExpression( - t.callExpression( - state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), - [t.assignmentExpression('=', slotId, child)], - ), - slotId, - t.objectExpression([ + if (enableObjectSlots) { + const { scope } = path; + const slotId = scope.generateUidIdentifier('slot'); + if (scope) { + scope.push({ + id: slotId, + kind: 'let', + }); + } + const alternate = t.objectExpression([ t.objectProperty( t.identifier('default'), t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [slotId]))), - ), - optimize && t.objectProperty( + ), optimize && t.objectProperty( t.identifier('_'), t.numericLiteral(slotFlag), ) 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)) { VNodeChild = t.objectExpression([ t.objectProperty( diff --git a/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap b/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap index 266c58c..6c566a2 100644 --- a/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap +++ b/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap @@ -42,6 +42,16 @@ import { resolveComponent as _resolveComponent } from \\"vue\\"; _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`] = ` "import { withDirectives as _withDirectives } from \\"vue\\"; import { createVNode as _createVNode } from \\"vue\\"; diff --git a/packages/babel-plugin-jsx/test/snapshot.test.ts b/packages/babel-plugin-jsx/test/snapshot.test.ts index 0a757b6..8011805 100644 --- a/packages/babel-plugin-jsx/test/snapshot.test.ts +++ b/packages/babel-plugin-jsx/test/snapshot.test.ts @@ -155,7 +155,7 @@ tests.forEach(( test( name, 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( `passing object slots via JSX children ${name}`, async () => { - expect(await transpile(from, { optimize: true })).toMatchSnapshot(name); + expect(await transpile(from, { optimize: true, enableObjectSlots: true })).toMatchSnapshot(name); + }, + ); +}); + +const objectSlotsTests = [ + { + name: 'defaultSlot', + from: '{slots.default()}', + }, +]; + +objectSlotsTests.forEach(({ + name, from, +}) => { + test( + `disable object slot syntax with ${name}`, + async () => { + expect(await transpile(from, { optimize: true, enableObjectSlots: false })) + .toMatchSnapshot(name); }, ); });