mirror of
				https://github.com/vuejs/babel-plugin-jsx.git
				synced 2025-11-04 19:32:18 +08:00 
			
		
		
		
	chore: replace namespace imports with named imports (#67)
* 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>
This commit is contained in:
		
							
								
								
									
										28
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
							
								
								
									
										19
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
					 | 
				
			||||||
@@ -1,6 +1,9 @@
 | 
				
			|||||||
import syntaxJsx from '@babel/plugin-syntax-jsx';
 | 
					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 tranformVueJSX from './transform-vue-jsx';
 | 
				
			||||||
import sugarFragment from './sugar-fragment';
 | 
					import sugarFragment from './sugar-fragment';
 | 
				
			||||||
 | 
					import { JSX_HELPER_KEY } from './utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type State = {
 | 
					export type State = {
 | 
				
			||||||
  get: (name: string) => any;
 | 
					  get: (name: string) => any;
 | 
				
			||||||
@@ -20,6 +23,50 @@ export default () => ({
 | 
				
			|||||||
  name: 'babel-plugin-jsx',
 | 
					  name: 'babel-plugin-jsx',
 | 
				
			||||||
  inherits: syntaxJsx,
 | 
					  inherits: syntaxJsx,
 | 
				
			||||||
  visitor: {
 | 
					  visitor: {
 | 
				
			||||||
 | 
					    Program: {
 | 
				
			||||||
 | 
					      exit(path: NodePath<t.Program>, state: State) {
 | 
				
			||||||
 | 
					        const helpers: Set<string> = state.get(JSX_HELPER_KEY);
 | 
				
			||||||
 | 
					        if (!helpers) {
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const body = path.get('body');
 | 
				
			||||||
 | 
					        const specifierNames = new Set<string>();
 | 
				
			||||||
 | 
					        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(),
 | 
					    ...tranformVueJSX(),
 | 
				
			||||||
    ...sugarFragment(),
 | 
					    ...sugarFragment(),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
import * as t from '@babel/types';
 | 
					import * as t from '@babel/types';
 | 
				
			||||||
import { addNamespace } from '@babel/helper-module-imports';
 | 
					 | 
				
			||||||
import { NodePath } from '@babel/traverse';
 | 
					import { NodePath } from '@babel/traverse';
 | 
				
			||||||
import { State } from '.';
 | 
					import { State } from '.';
 | 
				
			||||||
 | 
					import { createIdentifier, FRAGMENT } from './utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXMemberExpression) => {
 | 
					const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXIdentifier) => {
 | 
				
			||||||
  const children = path.get('children') || [];
 | 
					  const children = path.get('children') || [];
 | 
				
			||||||
  return t.jsxElement(
 | 
					  return t.jsxElement(
 | 
				
			||||||
    t.jsxOpeningElement(Fragment, []),
 | 
					    t.jsxOpeningElement(Fragment, []),
 | 
				
			||||||
@@ -16,16 +16,10 @@ const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXMemberEx
 | 
				
			|||||||
export default () => ({
 | 
					export default () => ({
 | 
				
			||||||
  JSXFragment: {
 | 
					  JSXFragment: {
 | 
				
			||||||
    enter(path: NodePath<t.JSXElement>, state: State) {
 | 
					    enter(path: NodePath<t.JSXElement>, state: State) {
 | 
				
			||||||
      if (!state.get('vue')) {
 | 
					 | 
				
			||||||
        state.set('vue', addNamespace(path, 'vue'));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      path.replaceWith(
 | 
					      path.replaceWith(
 | 
				
			||||||
        transformFragment(
 | 
					        transformFragment(
 | 
				
			||||||
          path,
 | 
					          path,
 | 
				
			||||||
          t.jsxMemberExpression(
 | 
					          t.jsxIdentifier(createIdentifier(state, FRAGMENT).name),
 | 
				
			||||||
            t.jsxIdentifier(state.get('vue').name),
 | 
					 | 
				
			||||||
            t.jsxIdentifier('Fragment'),
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
import * as t from '@babel/types';
 | 
					import * as t from '@babel/types';
 | 
				
			||||||
import { NodePath } from '@babel/traverse';
 | 
					import { NodePath } from '@babel/traverse';
 | 
				
			||||||
import { addNamespace } from '@babel/helper-module-imports';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  createIdentifier,
 | 
					  createIdentifier,
 | 
				
			||||||
  transformJSXSpreadChild,
 | 
					  transformJSXSpreadChild,
 | 
				
			||||||
@@ -21,11 +20,11 @@ import { State } from '.';
 | 
				
			|||||||
const getChildren = (
 | 
					const getChildren = (
 | 
				
			||||||
  paths: NodePath<
 | 
					  paths: NodePath<
 | 
				
			||||||
    t.JSXText
 | 
					    t.JSXText
 | 
				
			||||||
      | t.JSXExpressionContainer
 | 
					    | t.JSXExpressionContainer
 | 
				
			||||||
      | t.JSXSpreadChild
 | 
					    | t.JSXSpreadChild
 | 
				
			||||||
      | t.JSXElement
 | 
					    | t.JSXElement
 | 
				
			||||||
      | t.JSXFragment
 | 
					    | t.JSXFragment
 | 
				
			||||||
    >[],
 | 
					  >[],
 | 
				
			||||||
  state: State,
 | 
					  state: State,
 | 
				
			||||||
): t.Expression[] => paths
 | 
					): t.Expression[] => paths
 | 
				
			||||||
  .map((path) => {
 | 
					  .map((path) => {
 | 
				
			||||||
@@ -61,8 +60,8 @@ const getChildren = (
 | 
				
			|||||||
    throw new Error(`getChildren: ${path.type} is not supported`);
 | 
					    throw new Error(`getChildren: ${path.type} is not supported`);
 | 
				
			||||||
  }).filter(((value: any) => (
 | 
					  }).filter(((value: any) => (
 | 
				
			||||||
    value !== undefined
 | 
					    value !== undefined
 | 
				
			||||||
      && value !== null
 | 
					    && value !== null
 | 
				
			||||||
      && !t.isJSXEmptyExpression(value)
 | 
					    && !t.isJSXEmptyExpression(value)
 | 
				
			||||||
  )) as any);
 | 
					  )) as any);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const transformJSXElement = (
 | 
					const transformJSXElement = (
 | 
				
			||||||
@@ -123,15 +122,11 @@ const transformJSXElement = (
 | 
				
			|||||||
    t.arrayExpression(directives),
 | 
					    t.arrayExpression(directives),
 | 
				
			||||||
  ]);
 | 
					  ]);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export { transformJSXElement };
 | 
					export { transformJSXElement };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default () => ({
 | 
					export default () => ({
 | 
				
			||||||
  JSXElement: {
 | 
					  JSXElement: {
 | 
				
			||||||
    exit(path: NodePath<t.JSXElement>, state: State) {
 | 
					    exit(path: NodePath<t.JSXElement>, state: State) {
 | 
				
			||||||
      if (!state.get('vue')) {
 | 
					 | 
				
			||||||
        state.set('vue', addNamespace(path, 'vue'));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      path.replaceWith(
 | 
					      path.replaceWith(
 | 
				
			||||||
        transformJSXElement(path, state),
 | 
					        transformJSXElement(path, state),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,15 +5,25 @@ import { NodePath } from '@babel/traverse';
 | 
				
			|||||||
import { State } from '.';
 | 
					import { State } from '.';
 | 
				
			||||||
import SlotFlags from './slotFlags';
 | 
					import SlotFlags from './slotFlags';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const JSX_HELPER_KEY = 'JSX_HELPER_KEY';
 | 
				
			||||||
 | 
					const FRAGMENT = 'Fragment';
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * create Identifier
 | 
					 * create Identifier
 | 
				
			||||||
 | 
					 * @param path NodePath
 | 
				
			||||||
 * @param state
 | 
					 * @param state
 | 
				
			||||||
 * @param id string
 | 
					 * @param id string
 | 
				
			||||||
 * @returns MemberExpression
 | 
					 * @returns MemberExpression
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const createIdentifier = (
 | 
					const createIdentifier = (
 | 
				
			||||||
  state: State, id: string,
 | 
					  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
 | 
					 * Checks if string is describing a directive
 | 
				
			||||||
@@ -30,8 +40,15 @@ const isDirective = (src: string): boolean => src.startsWith('v-')
 | 
				
			|||||||
const isFragment = (
 | 
					const isFragment = (
 | 
				
			||||||
  path:
 | 
					  path:
 | 
				
			||||||
    NodePath<t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName>,
 | 
					    NodePath<t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName>,
 | 
				
			||||||
): boolean => t.isJSXMemberExpression(path)
 | 
					): boolean => {
 | 
				
			||||||
    && (path.node as t.JSXMemberExpression).property.name === 'Fragment';
 | 
					  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
 | 
					 * Check if a Node is a component
 | 
				
			||||||
@@ -49,7 +66,7 @@ const checkIsComponent = (path: NodePath<t.JSXOpeningElement>): boolean => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const tag = (namePath as NodePath<t.JSXIdentifier>).node.name;
 | 
					  const tag = (namePath as NodePath<t.JSXIdentifier>).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()) {
 | 
					  if (namePath.isJSXIdentifier()) {
 | 
				
			||||||
    const { name } = namePath.node;
 | 
					    const { name } = namePath.node;
 | 
				
			||||||
    if (!htmlTags.includes(name) && !svgTags.includes(name)) {
 | 
					    if (!htmlTags.includes(name) && !svgTags.includes(name)) {
 | 
				
			||||||
      return path.scope.hasBinding(name)
 | 
					      return (name === FRAGMENT
 | 
				
			||||||
        ? t.identifier(name)
 | 
					        ? createIdentifier(state, FRAGMENT)
 | 
				
			||||||
        : (
 | 
					        : path.scope.hasBinding(name)
 | 
				
			||||||
          state.opts.isCustomElement?.(name)
 | 
					          ? t.identifier(name)
 | 
				
			||||||
 | 
					          : state.opts.isCustomElement?.(name)
 | 
				
			||||||
            ? t.stringLiteral(name)
 | 
					            ? t.stringLiteral(name)
 | 
				
			||||||
            : t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)])
 | 
					            : t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)]));
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return t.stringLiteral(name);
 | 
					    return t.stringLiteral(name);
 | 
				
			||||||
@@ -171,7 +188,7 @@ const transformJSXText = (path: NodePath<t.JSXText>): t.StringLiteral | null =>
 | 
				
			|||||||
const transformJSXExpressionContainer = (
 | 
					const transformJSXExpressionContainer = (
 | 
				
			||||||
  path: NodePath<t.JSXExpressionContainer>,
 | 
					  path: NodePath<t.JSXExpressionContainer>,
 | 
				
			||||||
): (t.Expression
 | 
					): (t.Expression
 | 
				
			||||||
) => path.get('expression').node as t.Expression;
 | 
					  ) => path.get('expression').node as t.Expression;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Transform JSXSpreadChild
 | 
					 * Transform JSXSpreadChild
 | 
				
			||||||
@@ -237,6 +254,8 @@ export {
 | 
				
			|||||||
  transformJSXSpreadChild,
 | 
					  transformJSXSpreadChild,
 | 
				
			||||||
  transformJSXExpressionContainer,
 | 
					  transformJSXExpressionContainer,
 | 
				
			||||||
  isFragment,
 | 
					  isFragment,
 | 
				
			||||||
 | 
					  FRAGMENT,
 | 
				
			||||||
  walksScope,
 | 
					  walksScope,
 | 
				
			||||||
  buildIIFE,
 | 
					  buildIIFE,
 | 
				
			||||||
 | 
					  JSX_HELPER_KEY,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user