Skip to content

Commit 6760d0b

Browse files
committed
feat: integrate with JSX plugin
1 parent 3a3c4f8 commit 6760d0b

File tree

10 files changed

+259
-172
lines changed

10 files changed

+259
-172
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"jsx"
1616
],
1717
"devDependencies": {
18+
"@babel/plugin-syntax-typescript": "^7.22.5",
1819
"@rollup/plugin-babel": "^6.0.3",
1920
"@types/babel__core": "^7.20.2",
2021
"@types/node": "^20.6.5",

packages/babel-plugin-jsx/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,21 @@
2424
],
2525
"dependencies": {
2626
"@babel/helper-module-imports": "^7.22.15",
27+
"@babel/helper-plugin-utils": "^7.22.5",
2728
"@babel/plugin-syntax-jsx": "^7.22.5",
2829
"@babel/template": "^7.22.15",
2930
"@babel/traverse": "^7.22.20",
3031
"@babel/types": "^7.22.19",
3132
"@vue/babel-helper-vue-transform-on": "workspace:^",
33+
"@vue/babel-plugin-resolve-type": "workspace:^",
3234
"camelcase": "^6.3.0",
3335
"html-tags": "^3.3.1",
3436
"svg-tags": "^1.0.0"
3537
},
3638
"devDependencies": {
3739
"@babel/core": "^7.22.20",
3840
"@babel/preset-env": "^7.22.20",
41+
"@types/babel__helper-plugin-utils": "^7.10.1",
3942
"@types/babel__template": "^7.4.2",
4043
"@types/babel__traverse": "^7.20.2",
4144
"@types/svg-tags": "^1.0.0",

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

+178-163
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import template from '@babel/template';
55
import syntaxJsx from '@babel/plugin-syntax-jsx';
66
// @ts-expect-error
77
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports';
8-
import { type NodePath } from '@babel/traverse';
8+
import { type NodePath, type Visitor } from '@babel/traverse';
9+
import ResolveType from '@vue/babel-plugin-resolve-type';
10+
import { declare } from '@babel/helper-plugin-utils';
911
import transformVueJSX from './transform-vue-jsx';
1012
import sugarFragment from './sugar-fragment';
1113
import type { State, VueJSXPluginOptions } from './interface';
@@ -31,181 +33,194 @@ const hasJSX = (parentPath: NodePath<t.Program>) => {
3133

3234
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
3335

34-
export default ({ types }: typeof BabelCore): BabelCore.PluginObj<State> => ({
35-
name: 'babel-plugin-jsx',
36-
inherits: syntaxJsx,
37-
visitor: {
38-
...transformVueJSX,
39-
...sugarFragment,
40-
Program: {
41-
enter(path, state) {
42-
if (hasJSX(path)) {
43-
const importNames = [
44-
'createVNode',
45-
'Fragment',
46-
'resolveComponent',
47-
'withDirectives',
48-
'vShow',
49-
'vModelSelect',
50-
'vModelText',
51-
'vModelCheckbox',
52-
'vModelRadio',
53-
'vModelText',
54-
'vModelDynamic',
55-
'resolveDirective',
56-
'mergeProps',
57-
'createTextVNode',
58-
'isVNode',
59-
];
60-
if (isModule(path)) {
61-
// import { createVNode } from "vue";
62-
const importMap: Record<string, t.Identifier> = {};
63-
importNames.forEach((name) => {
64-
state.set(name, () => {
65-
if (importMap[name]) {
66-
return types.cloneNode(importMap[name]);
67-
}
68-
const identifier = addNamed(path, name, 'vue', {
69-
ensureLiveReference: true,
36+
export default declare<VueJSXPluginOptions, BabelCore.PluginObj<State>>(
37+
(api, opt, dirname) => {
38+
const { types } = api;
39+
let resolveType: BabelCore.PluginObj<BabelCore.PluginPass> | undefined;
40+
if (opt.resolveType !== false) {
41+
if (typeof opt.resolveType === 'boolean') opt.resolveType = {};
42+
resolveType = ResolveType(api, opt.resolveType, dirname);
43+
}
44+
return {
45+
...(resolveType || {}),
46+
name: 'babel-plugin-jsx',
47+
inherits: syntaxJsx,
48+
visitor: {
49+
...(resolveType?.visitor as Visitor<State>),
50+
...transformVueJSX,
51+
...sugarFragment,
52+
Program: {
53+
enter(path, state) {
54+
if (hasJSX(path)) {
55+
const importNames = [
56+
'createVNode',
57+
'Fragment',
58+
'resolveComponent',
59+
'withDirectives',
60+
'vShow',
61+
'vModelSelect',
62+
'vModelText',
63+
'vModelCheckbox',
64+
'vModelRadio',
65+
'vModelText',
66+
'vModelDynamic',
67+
'resolveDirective',
68+
'mergeProps',
69+
'createTextVNode',
70+
'isVNode',
71+
];
72+
if (isModule(path)) {
73+
// import { createVNode } from "vue";
74+
const importMap: Record<string, t.Identifier> = {};
75+
importNames.forEach((name) => {
76+
state.set(name, () => {
77+
if (importMap[name]) {
78+
return types.cloneNode(importMap[name]);
79+
}
80+
const identifier = addNamed(path, name, 'vue', {
81+
ensureLiveReference: true,
82+
});
83+
importMap[name] = identifier;
84+
return identifier;
85+
});
7086
});
71-
importMap[name] = identifier;
72-
return identifier;
73-
});
74-
});
75-
const { enableObjectSlots = true } = state.opts;
76-
if (enableObjectSlots) {
77-
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
78-
if (importMap.runtimeIsSlot) {
79-
return importMap.runtimeIsSlot;
80-
}
81-
const { name: isVNodeName } = state.get(
82-
'isVNode'
83-
)() as t.Identifier;
84-
const isSlot = path.scope.generateUidIdentifier('isSlot');
85-
const ast = template.ast`
86-
function ${isSlot.name}(s) {
87-
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
88-
}
89-
`;
90-
const lastImport = (path.get('body') as NodePath[])
91-
.filter((p) => p.isImportDeclaration())
92-
.pop();
93-
if (lastImport) {
94-
lastImport.insertAfter(ast);
95-
}
96-
importMap.runtimeIsSlot = isSlot;
97-
return isSlot;
98-
});
99-
}
100-
} else {
101-
// var _vue = require('vue');
102-
let sourceName: t.Identifier;
103-
importNames.forEach((name) => {
104-
state.set(name, () => {
105-
if (!sourceName) {
106-
sourceName = addNamespace(path, 'vue', {
107-
ensureLiveReference: true,
87+
const { enableObjectSlots = true } = state.opts;
88+
if (enableObjectSlots) {
89+
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
90+
if (importMap.runtimeIsSlot) {
91+
return importMap.runtimeIsSlot;
92+
}
93+
const { name: isVNodeName } = state.get(
94+
'isVNode'
95+
)() as t.Identifier;
96+
const isSlot = path.scope.generateUidIdentifier('isSlot');
97+
const ast = template.ast`
98+
function ${isSlot.name}(s) {
99+
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
100+
}
101+
`;
102+
const lastImport = (path.get('body') as NodePath[])
103+
.filter((p) => p.isImportDeclaration())
104+
.pop();
105+
if (lastImport) {
106+
lastImport.insertAfter(ast);
107+
}
108+
importMap.runtimeIsSlot = isSlot;
109+
return isSlot;
108110
});
109111
}
110-
return t.memberExpression(sourceName, t.identifier(name));
111-
});
112-
});
112+
} else {
113+
// var _vue = require('vue');
114+
let sourceName: t.Identifier;
115+
importNames.forEach((name) => {
116+
state.set(name, () => {
117+
if (!sourceName) {
118+
sourceName = addNamespace(path, 'vue', {
119+
ensureLiveReference: true,
120+
});
121+
}
122+
return t.memberExpression(sourceName, t.identifier(name));
123+
});
124+
});
113125

114-
const helpers: Record<string, t.Identifier> = {};
126+
const helpers: Record<string, t.Identifier> = {};
115127

116-
const { enableObjectSlots = true } = state.opts;
117-
if (enableObjectSlots) {
118-
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
119-
if (helpers.runtimeIsSlot) {
120-
return helpers.runtimeIsSlot;
121-
}
122-
const isSlot = path.scope.generateUidIdentifier('isSlot');
123-
const { object: objectName } = state.get(
124-
'isVNode'
125-
)() as t.MemberExpression;
126-
const ast = template.ast`
127-
function ${isSlot.name}(s) {
128-
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${
129-
(objectName as t.Identifier).name
130-
}.isVNode(s));
131-
}
132-
`;
128+
const { enableObjectSlots = true } = state.opts;
129+
if (enableObjectSlots) {
130+
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
131+
if (helpers.runtimeIsSlot) {
132+
return helpers.runtimeIsSlot;
133+
}
134+
const isSlot = path.scope.generateUidIdentifier('isSlot');
135+
const { object: objectName } = state.get(
136+
'isVNode'
137+
)() as t.MemberExpression;
138+
const ast = template.ast`
139+
function ${isSlot.name}(s) {
140+
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${
141+
(objectName as t.Identifier).name
142+
}.isVNode(s));
143+
}
144+
`;
133145

134-
const nodePaths = path.get('body') as NodePath[];
135-
const lastImport = nodePaths
136-
.filter(
137-
(p) =>
138-
p.isVariableDeclaration() &&
139-
p.node.declarations.some(
140-
(d) => (d.id as t.Identifier)?.name === sourceName.name
146+
const nodePaths = path.get('body') as NodePath[];
147+
const lastImport = nodePaths
148+
.filter(
149+
(p) =>
150+
p.isVariableDeclaration() &&
151+
p.node.declarations.some(
152+
(d) =>
153+
(d.id as t.Identifier)?.name === sourceName.name
154+
)
141155
)
142-
)
143-
.pop();
144-
if (lastImport) {
145-
lastImport.insertAfter(ast);
156+
.pop();
157+
if (lastImport) {
158+
lastImport.insertAfter(ast);
159+
}
160+
return isSlot;
161+
});
146162
}
147-
return isSlot;
148-
});
149-
}
150-
}
151-
152-
const {
153-
opts: { pragma = '' },
154-
file,
155-
} = state;
163+
}
156164

157-
if (pragma) {
158-
state.set('createVNode', () => t.identifier(pragma));
159-
}
165+
const {
166+
opts: { pragma = '' },
167+
file,
168+
} = state;
160169

161-
if (file.ast.comments) {
162-
for (const comment of file.ast.comments) {
163-
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
164-
if (jsxMatches) {
165-
state.set('createVNode', () => t.identifier(jsxMatches[1]));
170+
if (pragma) {
171+
state.set('createVNode', () => t.identifier(pragma));
166172
}
167-
}
168-
}
169-
}
170-
},
171-
exit(path) {
172-
const body = path.get('body') as NodePath[];
173-
const specifiersMap = new Map<string, t.ImportSpecifier>();
174173

175-
body
176-
.filter(
177-
(nodePath) =>
178-
t.isImportDeclaration(nodePath.node) &&
179-
nodePath.node.source.value === 'vue'
180-
)
181-
.forEach((nodePath) => {
182-
const { specifiers } = nodePath.node as t.ImportDeclaration;
183-
let shouldRemove = false;
184-
specifiers.forEach((specifier) => {
185-
if (
186-
!specifier.loc &&
187-
t.isImportSpecifier(specifier) &&
188-
t.isIdentifier(specifier.imported)
189-
) {
190-
specifiersMap.set(specifier.imported.name, specifier);
191-
shouldRemove = true;
174+
if (file.ast.comments) {
175+
for (const comment of file.ast.comments) {
176+
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
177+
if (jsxMatches) {
178+
state.set('createVNode', () => t.identifier(jsxMatches[1]));
179+
}
180+
}
192181
}
193-
});
194-
if (shouldRemove) {
195-
nodePath.remove();
196182
}
197-
});
183+
},
184+
exit(path) {
185+
const body = path.get('body') as NodePath[];
186+
const specifiersMap = new Map<string, t.ImportSpecifier>();
198187

199-
const specifiers = [...specifiersMap.keys()].map(
200-
(imported) => specifiersMap.get(imported)!
201-
);
202-
if (specifiers.length) {
203-
path.unshiftContainer(
204-
'body',
205-
t.importDeclaration(specifiers, t.stringLiteral('vue'))
206-
);
207-
}
188+
body
189+
.filter(
190+
(nodePath) =>
191+
t.isImportDeclaration(nodePath.node) &&
192+
nodePath.node.source.value === 'vue'
193+
)
194+
.forEach((nodePath) => {
195+
const { specifiers } = nodePath.node as t.ImportDeclaration;
196+
let shouldRemove = false;
197+
specifiers.forEach((specifier) => {
198+
if (
199+
!specifier.loc &&
200+
t.isImportSpecifier(specifier) &&
201+
t.isIdentifier(specifier.imported)
202+
) {
203+
specifiersMap.set(specifier.imported.name, specifier);
204+
shouldRemove = true;
205+
}
206+
});
207+
if (shouldRemove) {
208+
nodePath.remove();
209+
}
210+
});
211+
212+
const specifiers = [...specifiersMap.keys()].map(
213+
(imported) => specifiersMap.get(imported)!
214+
);
215+
if (specifiers.length) {
216+
path.unshiftContainer(
217+
'body',
218+
t.importDeclaration(specifiers, t.stringLiteral('vue'))
219+
);
220+
}
221+
},
222+
},
208223
},
209-
},
210-
},
211-
});
224+
};
225+
}
226+
);

0 commit comments

Comments
 (0)