From 3adc04db0f740cdc25ca2ac4000d184454998e77 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Thu, 14 Jan 2021 23:22:04 +0800 Subject: [PATCH 1/6] feat: should merge `ImportDeclaration` --- packages/babel-plugin-jsx/src/index.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/babel-plugin-jsx/src/index.ts b/packages/babel-plugin-jsx/src/index.ts index 3a20696a..487b37c8 100644 --- a/packages/babel-plugin-jsx/src/index.ts +++ b/packages/babel-plugin-jsx/src/index.ts @@ -131,6 +131,28 @@ export default ({ types }: typeof BabelCore) => ({ } } }, + exit(path: NodePath) { + const body = path.get('body') as NodePath[]; + const specifiersMap = new Map(); + + body.filter((nodePath) => t.isImportDeclaration(nodePath.node) + && nodePath.node.source.value === 'vue') + .forEach((nodePath) => { + const { specifiers } = nodePath.node as t.ImportDeclaration; + specifiers.forEach((specifier) => { + if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { + specifiersMap.set(specifier.imported.name, specifier); + } + }); + nodePath.remove(); + }); + + const specifiers = [...specifiersMap.keys()].map( + (imported) => specifiersMap.get(imported)!, + ); + // @ts-ignore + path.unshiftContainer('body', t.importDeclaration(specifiers, t.stringLiteral('vue'))); + }, }, }, }); From cab24a3bd35998e75241887a5b5d8765171f7f93 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Thu, 14 Jan 2021 23:24:45 +0800 Subject: [PATCH 2/6] feat: snap --- .../test/__snapshots__/snapshot.test.ts.snap | 88 +++++-------------- 1 file changed, 24 insertions(+), 64 deletions(-) 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 6c566a2e..f1e7ee20 100644 --- a/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap +++ b/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap @@ -1,9 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MereProps Order: MereProps Order 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { mergeProps as _mergeProps } from \\"vue\\"; -import { createTextVNode as _createTextVNode } from \\"vue\\"; +"import { createVNode as _createVNode, mergeProps as _mergeProps, createTextVNode as _createTextVNode } from \\"vue\\"; _createVNode(\\"button\\", _mergeProps({ \\"loading\\": true @@ -22,29 +20,24 @@ _createVNode(\\"div\\", { `; exports[`Without JSX should work: Without JSX should work 1`] = ` -"import { createVNode } from 'vue'; +"import { createVNode } from \\"vue\\"; createVNode('div', null, ['Without JSX should work']);" `; exports[`Without props: Without props 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { createTextVNode as _createTextVNode } from \\"vue\\"; +"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\"; _createVNode(\\"a\\", null, [_createTextVNode(\\"a\\")]);" `; exports[`custom directive: custom directive 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { resolveDirective as _resolveDirective } from \\"vue\\"; -import { resolveComponent as _resolveComponent } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, resolveDirective as _resolveDirective, 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\\"; +"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\"; _createVNode(_resolveComponent(\\"Badge\\"), null, { default: () => [slots.default()], @@ -53,9 +46,7 @@ _createVNode(_resolveComponent(\\"Badge\\"), null, { `; exports[`dynamic type in input: dynamic type in input 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { vModelDynamic as _vModelDynamic } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelDynamic as _vModelDynamic } from \\"vue\\"; _withDirectives(_createVNode(\\"input\\", { \\"type\\": type, @@ -64,9 +55,7 @@ _withDirectives(_createVNode(\\"input\\", { `; exports[`input[type="checkbox"]: input[type="checkbox"] 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { vModelCheckbox as _vModelCheckbox } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelCheckbox as _vModelCheckbox } from \\"vue\\"; _withDirectives(_createVNode(\\"input\\", { \\"type\\": \\"checkbox\\", @@ -75,10 +64,7 @@ _withDirectives(_createVNode(\\"input\\", { `; exports[`input[type="radio"]: input[type="radio"] 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { vModelRadio as _vModelRadio } from \\"vue\\"; -import { Fragment as _Fragment } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelRadio as _vModelRadio, Fragment as _Fragment } from \\"vue\\"; _createVNode(_Fragment, null, [_withDirectives(_createVNode(\\"input\\", { \\"type\\": \\"radio\\", @@ -94,9 +80,7 @@ _createVNode(_Fragment, null, [_withDirectives(_createVNode(\\"input\\", { `; exports[`input[type="text"] .lazy modifier: input[type="text"] .lazy modifier 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { vModelText as _vModelText } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelText as _vModelText } from \\"vue\\"; _withDirectives(_createVNode(\\"input\\", { \\"onUpdate:modelValue\\": $event => test = $event @@ -106,9 +90,7 @@ _withDirectives(_createVNode(\\"input\\", { `; exports[`input[type="text"]: input[type="text"] 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { vModelText as _vModelText } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelText as _vModelText } from \\"vue\\"; _withDirectives(_createVNode(\\"input\\", { \\"onUpdate:modelValue\\": $event => test = $event @@ -116,8 +98,7 @@ _withDirectives(_createVNode(\\"input\\", { `; exports[`override props multiple: multiple 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { resolveComponent as _resolveComponent } from \\"vue\\"; +"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\"; _createVNode(_resolveComponent(\\"A\\"), { \\"loading\\": true, @@ -138,8 +119,7 @@ _createVNode(\\"div\\", a, null);" `; exports[`passing object slots via JSX children multiple expressions: multiple expressions 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { resolveComponent as _resolveComponent } from \\"vue\\"; +"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\"; _createVNode(_resolveComponent(\\"A\\"), null, { default: () => [foo, bar], @@ -148,8 +128,7 @@ _createVNode(_resolveComponent(\\"A\\"), null, { `; exports[`passing object slots via JSX children single expression, function expression: single expression, function expression 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { resolveComponent as _resolveComponent } from \\"vue\\"; +"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\"; _createVNode(_resolveComponent(\\"A\\"), null, { default: () => \\"foo\\" @@ -157,11 +136,9 @@ _createVNode(_resolveComponent(\\"A\\"), null, { `; exports[`passing object slots via JSX children single expression, non-literal value: runtime check: single expression, non-literal value: runtime check 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { isVNode as _isVNode } from \\"vue\\"; -import { resolveComponent as _resolveComponent } from \\"vue\\"; +"let _slot; -let _slot; +import { createVNode as _createVNode, isVNode as _isVNode, resolveComponent as _resolveComponent } from \\"vue\\"; function _isSlot(s) { return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s); @@ -176,9 +153,7 @@ _createVNode(_resolveComponent(\\"A\\"), null, _isSlot(_slot = foo()) ? _slot : `; exports[`reassign variable as component: reassign variable as component 1`] = ` -"import { isVNode as _isVNode } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { defineComponent } from 'vue'; +"import { isVNode as _isVNode, createVNode as _createVNode, defineComponent } from \\"vue\\"; function _isSlot(s) { return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s); @@ -207,10 +182,7 @@ a = _createVNode(A, null, _isSlot(a) ? a : { `; exports[`select: select 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { vModelSelect as _vModelSelect } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { createTextVNode as _createTextVNode } from \\"vue\\"; +"import { withDirectives as _withDirectives, vModelSelect as _vModelSelect, createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\"; _withDirectives(_createVNode(\\"select\\", { \\"onUpdate:modelValue\\": $event => test = $event @@ -224,32 +196,25 @@ _withDirectives(_createVNode(\\"select\\", { `; exports[`should keep \`import * as Vue from "vue"\`: should keep \`import * as Vue from "vue"\` 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { createTextVNode as _createTextVNode } from \\"vue\\"; -import * as Vue from 'vue'; +"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\"; _createVNode(\\"div\\", null, [_createTextVNode(\\"Vue\\")]);" `; exports[`single no need for a mergeProps call: single no need for a mergeProps call 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { createTextVNode as _createTextVNode } from \\"vue\\"; +"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\"; _createVNode(\\"div\\", x, [_createTextVNode(\\"single\\")], 16);" `; exports[`specifiers should be merged into a single importDeclaration: specifiers should be merged into a single importDeclaration 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { createVNode, Fragment as _Fragment } from 'vue'; -import { vShow } from 'vue'; +"import { createVNode, Fragment as _Fragment, vShow } from \\"vue\\"; _createVNode(_Fragment, null, null);" `; exports[`textarea: textarea 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { vModelText as _vModelText } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelText as _vModelText } from \\"vue\\"; _withDirectives(_createVNode(\\"textarea\\", { \\"onUpdate:modelValue\\": $event => test = $event @@ -257,8 +222,7 @@ _withDirectives(_createVNode(\\"textarea\\", { `; exports[`use "model" as the prop name: use "model" as the prop name 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { resolveComponent as _resolveComponent } from \\"vue\\"; +"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\"; _createVNode(_resolveComponent(\\"C\\"), { \\"model\\": foo, @@ -267,10 +231,7 @@ _createVNode(_resolveComponent(\\"C\\"), { `; exports[`v-show: v-show 1`] = ` -"import { withDirectives as _withDirectives } from \\"vue\\"; -import { createVNode as _createVNode } from \\"vue\\"; -import { vShow as _vShow } from \\"vue\\"; -import { createTextVNode as _createTextVNode } from \\"vue\\"; +"import { withDirectives as _withDirectives, createVNode as _createVNode, vShow as _vShow, createTextVNode as _createTextVNode } from \\"vue\\"; _withDirectives(_createVNode(\\"div\\", null, [_createTextVNode(\\"vShow\\")], 512), [[_vShow, x]]);" `; @@ -284,8 +245,7 @@ _createVNode(\\"h1\\", { `; exports[`vModels: vModels 1`] = ` -"import { createVNode as _createVNode } from \\"vue\\"; -import { resolveComponent as _resolveComponent } from \\"vue\\"; +"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\"; _createVNode(_resolveComponent(\\"C\\"), { \\"modelValue\\": foo, From 453943968bf39860a62f47460f00c7659847f0e7 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Thu, 14 Jan 2021 23:26:34 +0800 Subject: [PATCH 3/6] fix: remove ts-ignore --- packages/babel-plugin-jsx/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/babel-plugin-jsx/src/index.ts b/packages/babel-plugin-jsx/src/index.ts index 487b37c8..1f4fa2ea 100644 --- a/packages/babel-plugin-jsx/src/index.ts +++ b/packages/babel-plugin-jsx/src/index.ts @@ -131,7 +131,7 @@ export default ({ types }: typeof BabelCore) => ({ } } }, - exit(path: NodePath) { + exit(path: NodePath) { const body = path.get('body') as NodePath[]; const specifiersMap = new Map(); @@ -150,7 +150,6 @@ export default ({ types }: typeof BabelCore) => ({ const specifiers = [...specifiersMap.keys()].map( (imported) => specifiersMap.get(imported)!, ); - // @ts-ignore path.unshiftContainer('body', t.importDeclaration(specifiers, t.stringLiteral('vue'))); }, }, From fac341ac97a21be18cf02eb1698d929ee18402b2 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sat, 16 Jan 2021 23:55:35 +0800 Subject: [PATCH 4/6] fix: `shouldRemove` flag --- .eslintrc.js | 1 + packages/babel-plugin-jsx/src/index.ts | 6 +++++- .../test/__snapshots__/snapshot.test.ts.snap | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index b927e2f1..c2e3591b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -32,6 +32,7 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': [0], '@typescript-eslint/no-explicit-any': [0], '@typescript-eslint/no-non-null-assertion': [0], + 'max-len': [0], }, settings: { 'import/resolver': { diff --git a/packages/babel-plugin-jsx/src/index.ts b/packages/babel-plugin-jsx/src/index.ts index 1f4fa2ea..c9303f01 100644 --- a/packages/babel-plugin-jsx/src/index.ts +++ b/packages/babel-plugin-jsx/src/index.ts @@ -139,12 +139,16 @@ export default ({ types }: typeof BabelCore) => ({ && nodePath.node.source.value === 'vue') .forEach((nodePath) => { const { specifiers } = nodePath.node as t.ImportDeclaration; + let shouldRemove = false; specifiers.forEach((specifier) => { if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { specifiersMap.set(specifier.imported.name, specifier); + shouldRemove = true; } }); - nodePath.remove(); + if (shouldRemove) { + nodePath.remove(); + } }); const specifiers = [...specifiersMap.keys()].map( 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 f1e7ee20..14462304 100644 --- a/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap +++ b/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap @@ -197,6 +197,7 @@ _withDirectives(_createVNode(\\"select\\", { exports[`should keep \`import * as Vue from "vue"\`: should keep \`import * as Vue from "vue"\` 1`] = ` "import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\"; +import * as Vue from 'vue'; _createVNode(\\"div\\", null, [_createTextVNode(\\"Vue\\")]);" `; From 70e2dd3597f340544a8339da326aec940a2aa1c3 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sun, 17 Jan 2021 00:11:47 +0800 Subject: [PATCH 5/6] fix: should merge generated imports only --- packages/babel-plugin-jsx/src/index.ts | 10 ++++++---- .../test/__snapshots__/snapshot.test.ts.snap | 10 +++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/babel-plugin-jsx/src/index.ts b/packages/babel-plugin-jsx/src/index.ts index c9303f01..f8663429 100644 --- a/packages/babel-plugin-jsx/src/index.ts +++ b/packages/babel-plugin-jsx/src/index.ts @@ -28,7 +28,7 @@ export interface VueJSXPluginOptions { export type ExcludesBoolean = (x: T | false | true) => x is T; -const hasJSX = (parentPath: NodePath) => { +const hasJSX = (parentPath: NodePath) => { let fileHasJSX = false; parentPath.traverse({ JSXElement(path) { // skip ts error @@ -51,7 +51,7 @@ export default ({ types }: typeof BabelCore) => ({ ...tranformVueJSX, ...sugarFragment, Program: { - enter(path: NodePath, state: State) { + enter(path: NodePath, state: State) { if (hasJSX(path)) { const importNames = [ 'createVNode', @@ -141,7 +141,7 @@ export default ({ types }: typeof BabelCore) => ({ const { specifiers } = nodePath.node as t.ImportDeclaration; let shouldRemove = false; specifiers.forEach((specifier) => { - if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { + if (!specifier.loc && t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { specifiersMap.set(specifier.imported.name, specifier); shouldRemove = true; } @@ -154,7 +154,9 @@ export default ({ types }: typeof BabelCore) => ({ const specifiers = [...specifiersMap.keys()].map( (imported) => specifiersMap.get(imported)!, ); - path.unshiftContainer('body', t.importDeclaration(specifiers, t.stringLiteral('vue'))); + if (specifiers) { + path.unshiftContainer('body', t.importDeclaration(specifiers, t.stringLiteral('vue'))); + } }, }, }, 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 14462304..819c9e21 100644 --- a/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap +++ b/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap @@ -20,7 +20,8 @@ _createVNode(\\"div\\", { `; exports[`Without JSX should work: Without JSX should work 1`] = ` -"import { createVNode } from \\"vue\\"; +"import \\"vue\\"; +import { createVNode } from 'vue'; createVNode('div', null, ['Without JSX should work']);" `; @@ -153,7 +154,8 @@ _createVNode(_resolveComponent(\\"A\\"), null, _isSlot(_slot = foo()) ? _slot : `; exports[`reassign variable as component: reassign variable as component 1`] = ` -"import { isVNode as _isVNode, createVNode as _createVNode, defineComponent } from \\"vue\\"; +"import { isVNode as _isVNode, createVNode as _createVNode } from \\"vue\\"; +import { defineComponent } from 'vue'; function _isSlot(s) { return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !_isVNode(s); @@ -209,7 +211,9 @@ _createVNode(\\"div\\", x, [_createTextVNode(\\"single\\")], 16);" `; exports[`specifiers should be merged into a single importDeclaration: specifiers should be merged into a single importDeclaration 1`] = ` -"import { createVNode, Fragment as _Fragment, vShow } from \\"vue\\"; +"import { createVNode as _createVNode } from \\"vue\\"; +import { createVNode, Fragment as _Fragment } from 'vue'; +import { vShow } from 'vue'; _createVNode(_Fragment, null, null);" `; From 9573d7f620999afaa9ff817a73c3ded55fc89729 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Sun, 17 Jan 2021 00:13:58 +0800 Subject: [PATCH 6/6] fix: should merge generated imports only --- packages/babel-plugin-jsx/src/index.ts | 2 +- .../babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/babel-plugin-jsx/src/index.ts b/packages/babel-plugin-jsx/src/index.ts index f8663429..9b026337 100644 --- a/packages/babel-plugin-jsx/src/index.ts +++ b/packages/babel-plugin-jsx/src/index.ts @@ -154,7 +154,7 @@ export default ({ types }: typeof BabelCore) => ({ const specifiers = [...specifiersMap.keys()].map( (imported) => specifiersMap.get(imported)!, ); - if (specifiers) { + if (specifiers.length) { path.unshiftContainer('body', t.importDeclaration(specifiers, t.stringLiteral('vue'))); } }, 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 819c9e21..f7085665 100644 --- a/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap +++ b/packages/babel-plugin-jsx/test/__snapshots__/snapshot.test.ts.snap @@ -20,8 +20,7 @@ _createVNode(\\"div\\", { `; exports[`Without JSX should work: Without JSX should work 1`] = ` -"import \\"vue\\"; -import { createVNode } from 'vue'; +"import { createVNode } from 'vue'; createVNode('div', null, ['Without JSX should work']);" `;