Skip to content

Commit 5790b83

Browse files
Amour1688逆寒tangjinzhou
authored
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: 逆寒 <[email protected]> Co-authored-by: tangjinzhou <[email protected]>
1 parent 86b5051 commit 5790b83

File tree

6 files changed

+115
-51
lines changed

6 files changed

+115
-51
lines changed

.circleci/config.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
version: 2.1
2+
jobs:
3+
build:
4+
docker:
5+
# specify the version you desire here
6+
- image: vuejs/ci
7+
user: node
8+
9+
working_directory: /home/node/repo
10+
11+
steps:
12+
- checkout
13+
14+
# Download and cache dependencies
15+
- restore_cache:
16+
keys:
17+
- v2-dependencies-{{ checksum "yarn.lock" }}
18+
19+
- run: yarn install --pure-lockfile
20+
21+
- save_cache:
22+
paths:
23+
- node_modules
24+
- ~/.cache/yarn
25+
key: v2-dependencies-{{ checksum "yarn.lock" }}
26+
27+
# run tests!
28+
- run: yarn test

.github/workflows/test.yml

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/babel-plugin-jsx/src/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import syntaxJsx from '@babel/plugin-syntax-jsx';
2+
import * as t from '@babel/types';
3+
import { NodePath } from '@babel/traverse';
24
import tranformVueJSX from './transform-vue-jsx';
35
import sugarFragment from './sugar-fragment';
6+
import { JSX_HELPER_KEY } from './utils';
47

58
export type State = {
69
get: (name: string) => any;
@@ -20,6 +23,50 @@ export default () => ({
2023
name: 'babel-plugin-jsx',
2124
inherits: syntaxJsx,
2225
visitor: {
26+
Program: {
27+
exit(path: NodePath<t.Program>, state: State) {
28+
const helpers: Set<string> = state.get(JSX_HELPER_KEY);
29+
if (!helpers) {
30+
return;
31+
}
32+
33+
const body = path.get('body');
34+
const specifierNames = new Set<string>();
35+
body
36+
.filter((nodePath) => t.isImportDeclaration(nodePath.node)
37+
&& nodePath.node.source.value === 'vue')
38+
.forEach((nodePath) => {
39+
let shouldKeep = false;
40+
const newSpecifiers = (nodePath.node as t.ImportDeclaration).specifiers
41+
.filter((specifier) => {
42+
if (t.isImportSpecifier(specifier)) {
43+
const { imported, local } = specifier;
44+
specifierNames.add(imported.name);
45+
return local.name !== imported.name;
46+
} if (t.isImportNamespaceSpecifier(specifier)) {
47+
// should keep when `import * as Vue from 'vue'`
48+
shouldKeep = true;
49+
}
50+
return false;
51+
});
52+
53+
if (newSpecifiers.length) {
54+
nodePath.replaceWith(t.importDeclaration(newSpecifiers, t.stringLiteral('vue')));
55+
} else if (!shouldKeep) {
56+
nodePath.remove();
57+
}
58+
});
59+
60+
const importedHelperKeys = new Set([...specifierNames, ...helpers]);
61+
const specifiers: t.ImportSpecifier[] = [...importedHelperKeys].map(
62+
(imported) => t.importSpecifier(
63+
t.identifier(imported), t.identifier(imported),
64+
),
65+
);
66+
const expression = t.importDeclaration(specifiers, t.stringLiteral('vue'));
67+
path.unshiftContainer('body', expression);
68+
},
69+
},
2370
...tranformVueJSX(),
2471
...sugarFragment(),
2572
},

packages/babel-plugin-jsx/src/sugar-fragment.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as t from '@babel/types';
2-
import { addNamespace } from '@babel/helper-module-imports';
32
import { NodePath } from '@babel/traverse';
43
import { State } from '.';
4+
import { createIdentifier, FRAGMENT } from './utils';
55

6-
const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXMemberExpression) => {
6+
const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXIdentifier) => {
77
const children = path.get('children') || [];
88
return t.jsxElement(
99
t.jsxOpeningElement(Fragment, []),
@@ -16,16 +16,10 @@ const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXMemberEx
1616
export default () => ({
1717
JSXFragment: {
1818
enter(path: NodePath<t.JSXElement>, state: State) {
19-
if (!state.get('vue')) {
20-
state.set('vue', addNamespace(path, 'vue'));
21-
}
2219
path.replaceWith(
2320
transformFragment(
2421
path,
25-
t.jsxMemberExpression(
26-
t.jsxIdentifier(state.get('vue').name),
27-
t.jsxIdentifier('Fragment'),
28-
),
22+
t.jsxIdentifier(createIdentifier(state, FRAGMENT).name),
2923
),
3024
);
3125
},

packages/babel-plugin-jsx/src/transform-vue-jsx.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as t from '@babel/types';
22
import { NodePath } from '@babel/traverse';
3-
import { addNamespace } from '@babel/helper-module-imports';
43
import {
54
createIdentifier,
65
transformJSXSpreadChild,
@@ -21,11 +20,11 @@ import { State } from '.';
2120
const getChildren = (
2221
paths: NodePath<
2322
t.JSXText
24-
| t.JSXExpressionContainer
25-
| t.JSXSpreadChild
26-
| t.JSXElement
27-
| t.JSXFragment
28-
>[],
23+
| t.JSXExpressionContainer
24+
| t.JSXSpreadChild
25+
| t.JSXElement
26+
| t.JSXFragment
27+
>[],
2928
state: State,
3029
): t.Expression[] => paths
3130
.map((path) => {
@@ -61,8 +60,8 @@ const getChildren = (
6160
throw new Error(`getChildren: ${path.type} is not supported`);
6261
}).filter(((value: any) => (
6362
value !== undefined
64-
&& value !== null
65-
&& !t.isJSXEmptyExpression(value)
63+
&& value !== null
64+
&& !t.isJSXEmptyExpression(value)
6665
)) as any);
6766

6867
const transformJSXElement = (
@@ -123,15 +122,11 @@ const transformJSXElement = (
123122
t.arrayExpression(directives),
124123
]);
125124
};
126-
127125
export { transformJSXElement };
128126

129127
export default () => ({
130128
JSXElement: {
131129
exit(path: NodePath<t.JSXElement>, state: State) {
132-
if (!state.get('vue')) {
133-
state.set('vue', addNamespace(path, 'vue'));
134-
}
135130
path.replaceWith(
136131
transformJSXElement(path, state),
137132
);

packages/babel-plugin-jsx/src/utils.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,25 @@ import { NodePath } from '@babel/traverse';
55
import { State } from '.';
66
import SlotFlags from './slotFlags';
77

8+
const JSX_HELPER_KEY = 'JSX_HELPER_KEY';
9+
const FRAGMENT = 'Fragment';
810
/**
911
* create Identifier
12+
* @param path NodePath
1013
* @param state
1114
* @param id string
1215
* @returns MemberExpression
1316
*/
1417
const createIdentifier = (
1518
state: State, id: string,
16-
): t.MemberExpression => t.memberExpression(state.get('vue'), t.identifier(id));
19+
): t.Identifier => {
20+
if (!state.get(JSX_HELPER_KEY)) {
21+
state.set(JSX_HELPER_KEY, new Set());
22+
}
23+
const helpers = state.get(JSX_HELPER_KEY);
24+
helpers.add(id);
25+
return t.identifier(id);
26+
};
1727

1828
/**
1929
* Checks if string is describing a directive
@@ -30,8 +40,15 @@ const isDirective = (src: string): boolean => src.startsWith('v-')
3040
const isFragment = (
3141
path:
3242
NodePath<t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName>,
33-
): boolean => t.isJSXMemberExpression(path)
34-
&& (path.node as t.JSXMemberExpression).property.name === 'Fragment';
43+
): boolean => {
44+
if (path.isJSXIdentifier()) {
45+
return path.node.name === FRAGMENT;
46+
}
47+
if (path.isJSXMemberExpression()) {
48+
return (path.node as t.JSXMemberExpression).property.name === FRAGMENT;
49+
}
50+
return false;
51+
};
3552

3653
/**
3754
* Check if a Node is a component
@@ -49,7 +66,7 @@ const checkIsComponent = (path: NodePath<t.JSXOpeningElement>): boolean => {
4966

5067
const tag = (namePath as NodePath<t.JSXIdentifier>).node.name;
5168

52-
return !htmlTags.includes(tag) && !svgTags.includes(tag);
69+
return tag !== FRAGMENT && !htmlTags.includes(tag) && !svgTags.includes(tag);
5370
};
5471

5572
/**
@@ -85,13 +102,13 @@ const getTag = (
85102
if (namePath.isJSXIdentifier()) {
86103
const { name } = namePath.node;
87104
if (!htmlTags.includes(name) && !svgTags.includes(name)) {
88-
return path.scope.hasBinding(name)
89-
? t.identifier(name)
90-
: (
91-
state.opts.isCustomElement?.(name)
105+
return (name === FRAGMENT
106+
? createIdentifier(state, FRAGMENT)
107+
: path.scope.hasBinding(name)
108+
? t.identifier(name)
109+
: state.opts.isCustomElement?.(name)
92110
? t.stringLiteral(name)
93-
: t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)])
94-
);
111+
: t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)]));
95112
}
96113

97114
return t.stringLiteral(name);
@@ -171,7 +188,7 @@ const transformJSXText = (path: NodePath<t.JSXText>): t.StringLiteral | null =>
171188
const transformJSXExpressionContainer = (
172189
path: NodePath<t.JSXExpressionContainer>,
173190
): (t.Expression
174-
) => path.get('expression').node as t.Expression;
191+
) => path.get('expression').node as t.Expression;
175192

176193
/**
177194
* Transform JSXSpreadChild
@@ -237,6 +254,8 @@ export {
237254
transformJSXSpreadChild,
238255
transformJSXExpressionContainer,
239256
isFragment,
257+
FRAGMENT,
240258
walksScope,
241259
buildIIFE,
260+
JSX_HELPER_KEY,
242261
};

0 commit comments

Comments
 (0)