Skip to content

Commit 9ea52ee

Browse files
authored
feat: use addNamed import (#196)
1 parent 51f8eb0 commit 9ea52ee

9 files changed

Lines changed: 249 additions & 162 deletions

File tree

packages/babel-plugin-jsx/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"build": "tsc",
1616
"lint": "eslint 'src/*.ts'",
1717
"test": "npm run build && jest --coverage",
18-
"prepublish": "npm run build"
18+
"prepublishOnly": "npm run build"
1919
},
2020
"bugs": {
2121
"url": "https://github.com/vuejs/jsx-next/issues"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const transformJSXSpreadAttribute = (
5353
const { properties } = argument.node;
5454
if (!properties) {
5555
if (argument.isIdentifier()) {
56-
walksScope(nodePath, (argument.node as t.Identifier).name, SlotFlags.DYNAMIC);
56+
walksScope(nodePath, (argument as any).name, SlotFlags.DYNAMIC);
5757
}
5858
args.push(mergeProps ? argument.node : t.spreadElement(argument.node));
5959
} else if (mergeProps) {
Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import syntaxJsx from '@babel/plugin-syntax-jsx';
21
import * as t from '@babel/types';
2+
import * as BabelCore from '@babel/core';
3+
import syntaxJsx from '@babel/plugin-syntax-jsx';
4+
import { addNamed, isModule, addNamespace } from '@babel/helper-module-imports';
35
import { NodePath } from '@babel/traverse';
46
import tranformVueJSX from './transform-vue-jsx';
57
import sugarFragment from './sugar-fragment';
6-
import { JSX_HELPER_KEY } from './utils';
78

89
export type State = {
910
get: (name: string) => any;
@@ -20,59 +21,88 @@ export interface Opts {
2021

2122
export type ExcludesBoolean = <T>(x: T | false | true) => x is T;
2223

23-
export default () => ({
24+
const hasJSX = (parentPath: NodePath) => {
25+
let fileHasJSX = false;
26+
27+
parentPath.traverse({
28+
JSXElement(path) { // skip ts error
29+
fileHasJSX = true;
30+
path.stop();
31+
},
32+
JSXFragment(path) {
33+
fileHasJSX = true;
34+
path.stop();
35+
},
36+
});
37+
38+
return fileHasJSX;
39+
};
40+
41+
export default ({ types }: typeof BabelCore) => ({
2442
name: 'babel-plugin-jsx',
2543
inherits: syntaxJsx,
2644
visitor: {
45+
...tranformVueJSX,
46+
...sugarFragment,
2747
Program: {
28-
exit(path: NodePath<t.Program>, state: State) {
29-
const helpers: Set<string> = state.get(JSX_HELPER_KEY);
30-
if (!helpers) {
31-
return;
32-
}
33-
34-
const body = path.get('body');
35-
const specifierNames = new Set<string>();
36-
body
37-
.filter((nodePath) => t.isImportDeclaration(nodePath.node)
38-
&& nodePath.node.source.value === 'vue')
39-
.forEach((nodePath) => {
40-
let shouldKeep = false;
41-
const newSpecifiers = (nodePath.node as t.ImportDeclaration).specifiers
42-
.filter((specifier) => {
43-
if (t.isImportSpecifier(specifier)) {
44-
const { imported, local } = specifier;
45-
if (local.name === imported.name) {
46-
specifierNames.add(imported.name);
47-
return false;
48-
}
49-
return true;
48+
enter(path: NodePath, state: State) {
49+
if (hasJSX(path)) {
50+
const importNames = [
51+
'createVNode',
52+
'Fragment',
53+
'resolveComponent',
54+
'withDirectives',
55+
'vShow',
56+
'vModelSelect',
57+
'vModelText',
58+
'vModelCheckbox',
59+
'vModelRadio',
60+
'vModelText',
61+
'vModelDynamic',
62+
'resolveDirective',
63+
'mergeProps',
64+
'createTextVNode',
65+
];
66+
if (isModule(path)) {
67+
// import { createVNode } from "vue";
68+
const importMap: Record<string, t.Identifier> = {};
69+
importNames.forEach((name) => {
70+
state.set(name, () => {
71+
if (importMap[name]) {
72+
return types.cloneDeep(importMap[name]);
5073
}
51-
if (t.isImportNamespaceSpecifier(specifier)) {
52-
// should keep when `import * as Vue from 'vue'`
53-
shouldKeep = true;
74+
const identifier = addNamed(
75+
path,
76+
name,
77+
'vue',
78+
{
79+
ensureLiveReference: true,
80+
},
81+
);
82+
importMap[name] = identifier;
83+
return identifier;
84+
});
85+
});
86+
} else {
87+
// var _vue = require('vue');
88+
let sourceName = '';
89+
importNames.forEach((name) => {
90+
state.set(name, () => {
91+
if (!sourceName) {
92+
sourceName = addNamespace(
93+
path,
94+
'vue',
95+
{
96+
ensureLiveReference: true,
97+
},
98+
).name;
5499
}
55-
return false;
100+
return t.memberExpression(t.identifier(sourceName), t.identifier(name));
56101
});
57-
58-
if (newSpecifiers.length) {
59-
nodePath.replaceWith(t.importDeclaration(newSpecifiers, t.stringLiteral('vue')));
60-
} else if (!shouldKeep) {
61-
nodePath.remove();
62-
}
63-
});
64-
65-
const importedHelperKeys = new Set([...specifierNames, ...helpers]);
66-
const specifiers: t.ImportSpecifier[] = [...importedHelperKeys].map(
67-
(imported) => t.importSpecifier(
68-
t.identifier(imported), t.identifier(imported),
69-
),
70-
);
71-
const expression = t.importDeclaration(specifiers, t.stringLiteral('vue'));
72-
path.unshiftContainer('body', expression);
102+
});
103+
}
104+
}
73105
},
74106
},
75-
...tranformVueJSX(),
76-
...sugarFragment(),
77107
},
78108
});

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { NodePath } from '@babel/traverse';
33
import { State } from '.';
44
import { createIdentifier, FRAGMENT } from './utils';
55

6-
const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXIdentifier) => {
6+
const transformFragment = (
7+
path: NodePath<t.JSXElement>,
8+
Fragment: t.JSXIdentifier | t.JSXMemberExpression,
9+
) => {
710
const children = path.get('children') || [];
811
return t.jsxElement(
912
t.jsxOpeningElement(Fragment, []),
@@ -13,14 +16,21 @@ const transformFragment = (path: NodePath<t.JSXElement>, Fragment: t.JSXIdentifi
1316
);
1417
};
1518

16-
export default () => ({
19+
export default ({
1720
JSXFragment: {
1821
enter(path: NodePath<t.JSXElement>, state: State) {
22+
const fragmentCallee = createIdentifier(state, FRAGMENT);
1923
path.replaceWith(
20-
transformFragment(
24+
t.inherits(transformFragment(
2125
path,
22-
t.jsxIdentifier(createIdentifier(state, FRAGMENT).name),
23-
),
26+
t.isIdentifier(fragmentCallee)
27+
? t.jsxIdentifier(fragmentCallee.name)
28+
: t.jsxMemberExpression(
29+
t.jsxIdentifier((fragmentCallee.object as t.Identifier).name),
30+
t.jsxIdentifier((fragmentCallee.property as t.Identifier).name),
31+
),
32+
), path.node)
33+
,
2434
);
2535
},
2636
},

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from './utils';
1111
import buildProps from './buildProps';
1212
import SlotFlags from './slotFlags';
13-
import { State } from '.';
13+
import { State, ExcludesBoolean } from '.';
1414

1515
/**
1616
* Get children from Array of JSX children
@@ -52,7 +52,7 @@ const getChildren = (
5252
return transformJSXSpreadChild(path as NodePath<t.JSXSpreadChild>);
5353
}
5454
if (path.isCallExpression()) {
55-
return path.node;
55+
return (path as NodePath<t.CallExpression>).node;
5656
}
5757
if (path.isJSXElement()) {
5858
return transformJSXElement(path, state);
@@ -83,7 +83,6 @@ const transformJSXElement = (
8383

8484
const slotFlag = path.getData('slotFlag') || SlotFlags.STABLE;
8585

86-
// @ts-ignore
8786
const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
8887
tag,
8988
props,
@@ -111,7 +110,7 @@ const transformJSXElement = (
111110
&& t.arrayExpression(
112111
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name)),
113112
),
114-
].filter(Boolean as any));
113+
].filter(Boolean as unknown as ExcludesBoolean));
115114

116115
if (!directives.length) {
117116
return createVNode;
@@ -122,13 +121,14 @@ const transformJSXElement = (
122121
t.arrayExpression(directives),
123122
]);
124123
};
124+
125125
export { transformJSXElement };
126126

127-
export default () => ({
127+
export default ({
128128
JSXElement: {
129129
exit(path: NodePath<t.JSXElement>, state: State) {
130130
path.replaceWith(
131-
transformJSXElement(path, state),
131+
t.inherits(transformJSXElement(path, state), path.node),
132132
);
133133
},
134134
},

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,12 @@ const FRAGMENT = 'Fragment';
1111
* create Identifier
1212
* @param path NodePath
1313
* @param state
14-
* @param id string
14+
* @param name string
1515
* @returns MemberExpression
1616
*/
1717
const createIdentifier = (
18-
state: State, id: string,
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-
};
18+
state: State, name: string,
19+
): t.Identifier | t.MemberExpression => state.get(name)();
2720

2821
/**
2922
* Checks if string is describing a directive
@@ -42,10 +35,10 @@ const isFragment = (
4235
NodePath<t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName>,
4336
): boolean => {
4437
if (path.isJSXIdentifier()) {
45-
return path.node.name === FRAGMENT;
38+
return path.node.name.endsWith(FRAGMENT);
4639
}
4740
if (path.isJSXMemberExpression()) {
48-
return (path.node as t.JSXMemberExpression).property.name === FRAGMENT;
41+
return path.node.property.name.endsWith(FRAGMENT);
4942
}
5043
return false;
5144
};
@@ -66,7 +59,7 @@ const checkIsComponent = (path: NodePath<t.JSXOpeningElement>): boolean => {
6659

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

69-
return tag !== FRAGMENT && !htmlTags.includes(tag) && !svgTags.includes(tag);
62+
return !tag.endsWith(FRAGMENT) && !htmlTags.includes(tag) && !svgTags.includes(tag);
7063
};
7164

7265
/**

0 commit comments

Comments
 (0)