From 5790b83ed68a0239a12d1cd6c130727da147b476 Mon Sep 17 00:00:00 2001 From: Amour1688 <31695475+Amour1688@users.noreply.github.com> Date: Thu, 27 Aug 2020 20:01:11 +0800 Subject: [PATCH] chore: replace namespace imports with named imports (#67) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Replace namespace imports with named imports (#54) * Add .circleci/config.yml * ci: fix circleci error * fix: specifiers should be merged into a single importDeclaration Co-authored-by: 逆寒 <869732751@qq.com> Co-authored-by: tangjinzhou <415800467@qq.com> --- .circleci/config.yml | 28 +++++++++++ .github/workflows/test.yml | 19 -------- packages/babel-plugin-jsx/src/index.ts | 47 +++++++++++++++++++ .../babel-plugin-jsx/src/sugar-fragment.ts | 12 ++--- .../babel-plugin-jsx/src/transform-vue-jsx.ts | 19 +++----- packages/babel-plugin-jsx/src/utils.ts | 41 +++++++++++----- 6 files changed, 115 insertions(+), 51 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 .github/workflows/test.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..7ae06c2 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,28 @@ +version: 2.1 +jobs: + build: + docker: + # specify the version you desire here + - image: vuejs/ci + user: node + + working_directory: /home/node/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v2-dependencies-{{ checksum "yarn.lock" }} + + - run: yarn install --pure-lockfile + + - save_cache: + paths: + - node_modules + - ~/.cache/yarn + key: v2-dependencies-{{ checksum "yarn.lock" }} + + # run tests! + - run: yarn test diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index fc05404..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: test - -on: [push, pull_request] - -jobs: - setup: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v2 - - - name: install - run: yarn install - - - name: lint - run: yarn lint - - - name: test - run: yarn test diff --git a/packages/babel-plugin-jsx/src/index.ts b/packages/babel-plugin-jsx/src/index.ts index 151d769..d829ca0 100644 --- a/packages/babel-plugin-jsx/src/index.ts +++ b/packages/babel-plugin-jsx/src/index.ts @@ -1,6 +1,9 @@ import syntaxJsx from '@babel/plugin-syntax-jsx'; +import * as t from '@babel/types'; +import { NodePath } from '@babel/traverse'; import tranformVueJSX from './transform-vue-jsx'; import sugarFragment from './sugar-fragment'; +import { JSX_HELPER_KEY } from './utils'; export type State = { get: (name: string) => any; @@ -20,6 +23,50 @@ export default () => ({ name: 'babel-plugin-jsx', inherits: syntaxJsx, visitor: { + Program: { + exit(path: NodePath, state: State) { + const helpers: Set = state.get(JSX_HELPER_KEY); + if (!helpers) { + return; + } + + const body = path.get('body'); + const specifierNames = new Set(); + body + .filter((nodePath) => t.isImportDeclaration(nodePath.node) + && nodePath.node.source.value === 'vue') + .forEach((nodePath) => { + let shouldKeep = false; + const newSpecifiers = (nodePath.node as t.ImportDeclaration).specifiers + .filter((specifier) => { + if (t.isImportSpecifier(specifier)) { + const { imported, local } = specifier; + specifierNames.add(imported.name); + return local.name !== imported.name; + } if (t.isImportNamespaceSpecifier(specifier)) { + // should keep when `import * as Vue from 'vue'` + shouldKeep = true; + } + return false; + }); + + if (newSpecifiers.length) { + nodePath.replaceWith(t.importDeclaration(newSpecifiers, t.stringLiteral('vue'))); + } else if (!shouldKeep) { + nodePath.remove(); + } + }); + + const importedHelperKeys = new Set([...specifierNames, ...helpers]); + const specifiers: t.ImportSpecifier[] = [...importedHelperKeys].map( + (imported) => t.importSpecifier( + t.identifier(imported), t.identifier(imported), + ), + ); + const expression = t.importDeclaration(specifiers, t.stringLiteral('vue')); + path.unshiftContainer('body', expression); + }, + }, ...tranformVueJSX(), ...sugarFragment(), }, diff --git a/packages/babel-plugin-jsx/src/sugar-fragment.ts b/packages/babel-plugin-jsx/src/sugar-fragment.ts index bcf80a3..955edd5 100644 --- a/packages/babel-plugin-jsx/src/sugar-fragment.ts +++ b/packages/babel-plugin-jsx/src/sugar-fragment.ts @@ -1,9 +1,9 @@ import * as t from '@babel/types'; -import { addNamespace } from '@babel/helper-module-imports'; import { NodePath } from '@babel/traverse'; import { State } from '.'; +import { createIdentifier, FRAGMENT } from './utils'; -const transformFragment = (path: NodePath, Fragment: t.JSXMemberExpression) => { +const transformFragment = (path: NodePath, Fragment: t.JSXIdentifier) => { const children = path.get('children') || []; return t.jsxElement( t.jsxOpeningElement(Fragment, []), @@ -16,16 +16,10 @@ const transformFragment = (path: NodePath, Fragment: t.JSXMemberEx export default () => ({ JSXFragment: { enter(path: NodePath, state: State) { - if (!state.get('vue')) { - state.set('vue', addNamespace(path, 'vue')); - } path.replaceWith( transformFragment( path, - t.jsxMemberExpression( - t.jsxIdentifier(state.get('vue').name), - t.jsxIdentifier('Fragment'), - ), + t.jsxIdentifier(createIdentifier(state, FRAGMENT).name), ), ); }, diff --git a/packages/babel-plugin-jsx/src/transform-vue-jsx.ts b/packages/babel-plugin-jsx/src/transform-vue-jsx.ts index 0f419e4..fba592c 100644 --- a/packages/babel-plugin-jsx/src/transform-vue-jsx.ts +++ b/packages/babel-plugin-jsx/src/transform-vue-jsx.ts @@ -1,6 +1,5 @@ import * as t from '@babel/types'; import { NodePath } from '@babel/traverse'; -import { addNamespace } from '@babel/helper-module-imports'; import { createIdentifier, transformJSXSpreadChild, @@ -21,11 +20,11 @@ import { State } from '.'; const getChildren = ( paths: NodePath< t.JSXText - | t.JSXExpressionContainer - | t.JSXSpreadChild - | t.JSXElement - | t.JSXFragment - >[], + | t.JSXExpressionContainer + | t.JSXSpreadChild + | t.JSXElement + | t.JSXFragment + >[], state: State, ): t.Expression[] => paths .map((path) => { @@ -61,8 +60,8 @@ const getChildren = ( throw new Error(`getChildren: ${path.type} is not supported`); }).filter(((value: any) => ( value !== undefined - && value !== null - && !t.isJSXEmptyExpression(value) + && value !== null + && !t.isJSXEmptyExpression(value) )) as any); const transformJSXElement = ( @@ -123,15 +122,11 @@ const transformJSXElement = ( t.arrayExpression(directives), ]); }; - export { transformJSXElement }; export default () => ({ JSXElement: { exit(path: NodePath, state: State) { - if (!state.get('vue')) { - state.set('vue', addNamespace(path, 'vue')); - } path.replaceWith( transformJSXElement(path, state), ); diff --git a/packages/babel-plugin-jsx/src/utils.ts b/packages/babel-plugin-jsx/src/utils.ts index 549f2e6..5f1c501 100644 --- a/packages/babel-plugin-jsx/src/utils.ts +++ b/packages/babel-plugin-jsx/src/utils.ts @@ -5,15 +5,25 @@ import { NodePath } from '@babel/traverse'; import { State } from '.'; import SlotFlags from './slotFlags'; +const JSX_HELPER_KEY = 'JSX_HELPER_KEY'; +const FRAGMENT = 'Fragment'; /** * create Identifier + * @param path NodePath * @param state * @param id string * @returns MemberExpression */ const createIdentifier = ( state: State, id: string, -): t.MemberExpression => t.memberExpression(state.get('vue'), t.identifier(id)); +): t.Identifier => { + if (!state.get(JSX_HELPER_KEY)) { + state.set(JSX_HELPER_KEY, new Set()); + } + const helpers = state.get(JSX_HELPER_KEY); + helpers.add(id); + return t.identifier(id); +}; /** * Checks if string is describing a directive @@ -30,8 +40,15 @@ const isDirective = (src: string): boolean => src.startsWith('v-') const isFragment = ( path: NodePath, -): boolean => t.isJSXMemberExpression(path) - && (path.node as t.JSXMemberExpression).property.name === 'Fragment'; +): boolean => { + if (path.isJSXIdentifier()) { + return path.node.name === FRAGMENT; + } + if (path.isJSXMemberExpression()) { + return (path.node as t.JSXMemberExpression).property.name === FRAGMENT; + } + return false; +}; /** * Check if a Node is a component @@ -49,7 +66,7 @@ const checkIsComponent = (path: NodePath): boolean => { const tag = (namePath as NodePath).node.name; - return !htmlTags.includes(tag) && !svgTags.includes(tag); + return tag !== FRAGMENT && !htmlTags.includes(tag) && !svgTags.includes(tag); }; /** @@ -85,13 +102,13 @@ const getTag = ( if (namePath.isJSXIdentifier()) { const { name } = namePath.node; if (!htmlTags.includes(name) && !svgTags.includes(name)) { - return path.scope.hasBinding(name) - ? t.identifier(name) - : ( - state.opts.isCustomElement?.(name) + return (name === FRAGMENT + ? createIdentifier(state, FRAGMENT) + : path.scope.hasBinding(name) + ? t.identifier(name) + : state.opts.isCustomElement?.(name) ? t.stringLiteral(name) - : t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)]) - ); + : t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)])); } return t.stringLiteral(name); @@ -171,7 +188,7 @@ const transformJSXText = (path: NodePath): t.StringLiteral | null => const transformJSXExpressionContainer = ( path: NodePath, ): (t.Expression -) => path.get('expression').node as t.Expression; + ) => path.get('expression').node as t.Expression; /** * Transform JSXSpreadChild @@ -237,6 +254,8 @@ export { transformJSXSpreadChild, transformJSXExpressionContainer, isFragment, + FRAGMENT, walksScope, buildIIFE, + JSX_HELPER_KEY, };