Skip to content

Commit

Permalink
feat: integrate with JSX plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Sep 25, 2023
1 parent 3a3c4f8 commit 6760d0b
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 172 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"jsx"
],
"devDependencies": {
"@babel/plugin-syntax-typescript": "^7.22.5",
"@rollup/plugin-babel": "^6.0.3",
"@types/babel__core": "^7.20.2",
"@types/node": "^20.6.5",
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-plugin-jsx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@
],
"dependencies": {
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-jsx": "^7.22.5",
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.22.20",
"@babel/types": "^7.22.19",
"@vue/babel-helper-vue-transform-on": "workspace:^",
"@vue/babel-plugin-resolve-type": "workspace:^",
"camelcase": "^6.3.0",
"html-tags": "^3.3.1",
"svg-tags": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.22.20",
"@babel/preset-env": "^7.22.20",
"@types/babel__helper-plugin-utils": "^7.10.1",
"@types/babel__template": "^7.4.2",
"@types/babel__traverse": "^7.20.2",
"@types/svg-tags": "^1.0.0",
Expand Down
341 changes: 178 additions & 163 deletions packages/babel-plugin-jsx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import template from '@babel/template';
import syntaxJsx from '@babel/plugin-syntax-jsx';
// @ts-expect-error
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports';
import { type NodePath } from '@babel/traverse';
import { type NodePath, type Visitor } from '@babel/traverse';
import ResolveType from '@vue/babel-plugin-resolve-type';

Check failure on line 9 in packages/babel-plugin-jsx/src/index.ts

View workflow job for this annotation

GitHub Actions / Lint

Cannot find module '@vue/babel-plugin-resolve-type' or its corresponding type declarations.
import { declare } from '@babel/helper-plugin-utils';
import transformVueJSX from './transform-vue-jsx';
import sugarFragment from './sugar-fragment';
import type { State, VueJSXPluginOptions } from './interface';
Expand All @@ -31,181 +33,194 @@ const hasJSX = (parentPath: NodePath<t.Program>) => {

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

export default ({ types }: typeof BabelCore): BabelCore.PluginObj<State> => ({
name: 'babel-plugin-jsx',
inherits: syntaxJsx,
visitor: {
...transformVueJSX,
...sugarFragment,
Program: {
enter(path, state) {
if (hasJSX(path)) {
const importNames = [
'createVNode',
'Fragment',
'resolveComponent',
'withDirectives',
'vShow',
'vModelSelect',
'vModelText',
'vModelCheckbox',
'vModelRadio',
'vModelText',
'vModelDynamic',
'resolveDirective',
'mergeProps',
'createTextVNode',
'isVNode',
];
if (isModule(path)) {
// import { createVNode } from "vue";
const importMap: Record<string, t.Identifier> = {};
importNames.forEach((name) => {
state.set(name, () => {
if (importMap[name]) {
return types.cloneNode(importMap[name]);
}
const identifier = addNamed(path, name, 'vue', {
ensureLiveReference: true,
export default declare<VueJSXPluginOptions, BabelCore.PluginObj<State>>(
(api, opt, dirname) => {
const { types } = api;
let resolveType: BabelCore.PluginObj<BabelCore.PluginPass> | undefined;
if (opt.resolveType !== false) {
if (typeof opt.resolveType === 'boolean') opt.resolveType = {};
resolveType = ResolveType(api, opt.resolveType, dirname);
}
return {
...(resolveType || {}),
name: 'babel-plugin-jsx',
inherits: syntaxJsx,
visitor: {
...(resolveType?.visitor as Visitor<State>),
...transformVueJSX,
...sugarFragment,
Program: {
enter(path, state) {
if (hasJSX(path)) {
const importNames = [
'createVNode',
'Fragment',
'resolveComponent',
'withDirectives',
'vShow',
'vModelSelect',
'vModelText',
'vModelCheckbox',
'vModelRadio',
'vModelText',
'vModelDynamic',
'resolveDirective',
'mergeProps',
'createTextVNode',
'isVNode',
];
if (isModule(path)) {
// import { createVNode } from "vue";
const importMap: Record<string, t.Identifier> = {};
importNames.forEach((name) => {
state.set(name, () => {
if (importMap[name]) {
return types.cloneNode(importMap[name]);
}
const identifier = addNamed(path, name, 'vue', {
ensureLiveReference: true,
});
importMap[name] = identifier;
return identifier;
});
});
importMap[name] = identifier;
return identifier;
});
});
const { enableObjectSlots = true } = state.opts;
if (enableObjectSlots) {
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
if (importMap.runtimeIsSlot) {
return importMap.runtimeIsSlot;
}
const { name: isVNodeName } = state.get(
'isVNode'
)() as t.Identifier;
const isSlot = path.scope.generateUidIdentifier('isSlot');
const ast = template.ast`
function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
}
`;
const lastImport = (path.get('body') as NodePath[])
.filter((p) => p.isImportDeclaration())
.pop();
if (lastImport) {
lastImport.insertAfter(ast);
}
importMap.runtimeIsSlot = isSlot;
return isSlot;
});
}
} else {
// var _vue = require('vue');
let sourceName: t.Identifier;
importNames.forEach((name) => {
state.set(name, () => {
if (!sourceName) {
sourceName = addNamespace(path, 'vue', {
ensureLiveReference: true,
const { enableObjectSlots = true } = state.opts;
if (enableObjectSlots) {
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
if (importMap.runtimeIsSlot) {
return importMap.runtimeIsSlot;
}
const { name: isVNodeName } = state.get(
'isVNode'
)() as t.Identifier;
const isSlot = path.scope.generateUidIdentifier('isSlot');
const ast = template.ast`
function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
}
`;
const lastImport = (path.get('body') as NodePath[])
.filter((p) => p.isImportDeclaration())
.pop();
if (lastImport) {
lastImport.insertAfter(ast);
}
importMap.runtimeIsSlot = isSlot;
return isSlot;
});
}
return t.memberExpression(sourceName, t.identifier(name));
});
});
} else {
// var _vue = require('vue');
let sourceName: t.Identifier;
importNames.forEach((name) => {
state.set(name, () => {
if (!sourceName) {
sourceName = addNamespace(path, 'vue', {
ensureLiveReference: true,
});
}
return t.memberExpression(sourceName, t.identifier(name));
});
});

const helpers: Record<string, t.Identifier> = {};
const helpers: Record<string, t.Identifier> = {};

const { enableObjectSlots = true } = state.opts;
if (enableObjectSlots) {
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
if (helpers.runtimeIsSlot) {
return helpers.runtimeIsSlot;
}
const isSlot = path.scope.generateUidIdentifier('isSlot');
const { object: objectName } = state.get(
'isVNode'
)() as t.MemberExpression;
const ast = template.ast`
function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${
(objectName as t.Identifier).name
}.isVNode(s));
}
`;
const { enableObjectSlots = true } = state.opts;
if (enableObjectSlots) {
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
if (helpers.runtimeIsSlot) {
return helpers.runtimeIsSlot;
}
const isSlot = path.scope.generateUidIdentifier('isSlot');
const { object: objectName } = state.get(
'isVNode'
)() as t.MemberExpression;
const ast = template.ast`
function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${
(objectName as t.Identifier).name
}.isVNode(s));
}
`;

const nodePaths = path.get('body') as NodePath[];
const lastImport = nodePaths
.filter(
(p) =>
p.isVariableDeclaration() &&
p.node.declarations.some(
(d) => (d.id as t.Identifier)?.name === sourceName.name
const nodePaths = path.get('body') as NodePath[];
const lastImport = nodePaths
.filter(
(p) =>
p.isVariableDeclaration() &&
p.node.declarations.some(
(d) =>
(d.id as t.Identifier)?.name === sourceName.name
)
)
)
.pop();
if (lastImport) {
lastImport.insertAfter(ast);
.pop();
if (lastImport) {
lastImport.insertAfter(ast);
}
return isSlot;
});
}
return isSlot;
});
}
}

const {
opts: { pragma = '' },
file,
} = state;
}

if (pragma) {
state.set('createVNode', () => t.identifier(pragma));
}
const {
opts: { pragma = '' },
file,
} = state;

if (file.ast.comments) {
for (const comment of file.ast.comments) {
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
if (jsxMatches) {
state.set('createVNode', () => t.identifier(jsxMatches[1]));
if (pragma) {
state.set('createVNode', () => t.identifier(pragma));
}
}
}
}
},
exit(path) {
const body = path.get('body') as NodePath[];
const specifiersMap = new Map<string, t.ImportSpecifier>();

body
.filter(
(nodePath) =>
t.isImportDeclaration(nodePath.node) &&
nodePath.node.source.value === 'vue'
)
.forEach((nodePath) => {
const { specifiers } = nodePath.node as t.ImportDeclaration;
let shouldRemove = false;
specifiers.forEach((specifier) => {
if (
!specifier.loc &&
t.isImportSpecifier(specifier) &&
t.isIdentifier(specifier.imported)
) {
specifiersMap.set(specifier.imported.name, specifier);
shouldRemove = true;
if (file.ast.comments) {
for (const comment of file.ast.comments) {
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
if (jsxMatches) {
state.set('createVNode', () => t.identifier(jsxMatches[1]));
}
}
}
});
if (shouldRemove) {
nodePath.remove();
}
});
},
exit(path) {
const body = path.get('body') as NodePath[];
const specifiersMap = new Map<string, t.ImportSpecifier>();

const specifiers = [...specifiersMap.keys()].map(
(imported) => specifiersMap.get(imported)!
);
if (specifiers.length) {
path.unshiftContainer(
'body',
t.importDeclaration(specifiers, t.stringLiteral('vue'))
);
}
body
.filter(
(nodePath) =>
t.isImportDeclaration(nodePath.node) &&
nodePath.node.source.value === 'vue'
)
.forEach((nodePath) => {
const { specifiers } = nodePath.node as t.ImportDeclaration;
let shouldRemove = false;
specifiers.forEach((specifier) => {
if (
!specifier.loc &&
t.isImportSpecifier(specifier) &&
t.isIdentifier(specifier.imported)
) {
specifiersMap.set(specifier.imported.name, specifier);
shouldRemove = true;
}
});
if (shouldRemove) {
nodePath.remove();
}
});

const specifiers = [...specifiersMap.keys()].map(
(imported) => specifiersMap.get(imported)!
);
if (specifiers.length) {
path.unshiftContainer(
'body',
t.importDeclaration(specifiers, t.stringLiteral('vue'))
);
}
},
},
},
},
},
});
};
}
);
Loading

0 comments on commit 6760d0b

Please sign in to comment.