chore: emit dts by oxc

This commit is contained in:
三咲智子 Kevin Deng
2025-08-10 09:05:18 +08:00
parent d411ef3ba1
commit b937287732
3 changed files with 309 additions and 290 deletions

View File

@ -40,112 +40,118 @@ function interopDefault(m: any) {
const syntaxJsx = /*#__PURE__*/ interopDefault(_syntaxJsx); const syntaxJsx = /*#__PURE__*/ interopDefault(_syntaxJsx);
const template = /*#__PURE__*/ interopDefault(_template); const template = /*#__PURE__*/ interopDefault(_template);
export default declare<VueJSXPluginOptions, BabelCore.PluginObj<State>>( const plugin: (
(api, opt, dirname) => { api: object,
const { types } = api; options: VueJSXPluginOptions | null | undefined,
let resolveType: BabelCore.PluginObj<BabelCore.PluginPass> | undefined; dirname: string
if (opt.resolveType) { ) => BabelCore.PluginObj<State> = declare<
if (typeof opt.resolveType === 'boolean') opt.resolveType = {}; VueJSXPluginOptions,
resolveType = ResolveType(api, opt.resolveType, dirname); BabelCore.PluginObj<State>
} >((api, opt, dirname) => {
return { const { types } = api;
...(resolveType || {}), let resolveType: BabelCore.PluginObj<BabelCore.PluginPass> | undefined;
name: 'babel-plugin-jsx', if (opt.resolveType) {
inherits: /*#__PURE__*/ interopDefault(syntaxJsx), if (typeof opt.resolveType === 'boolean') opt.resolveType = {};
visitor: { resolveType = ResolveType(api, opt.resolveType, dirname);
...(resolveType?.visitor as Visitor<State>), }
...transformVueJSX, return {
...sugarFragment, ...(resolveType || {}),
Program: { name: 'babel-plugin-jsx',
enter(path, state) { inherits: /*#__PURE__*/ interopDefault(syntaxJsx),
if (hasJSX(path)) { visitor: {
const importNames = [ ...(resolveType?.visitor as Visitor<State>),
'createVNode', ...transformVueJSX,
'Fragment', ...sugarFragment,
'resolveComponent', Program: {
'withDirectives', enter(path, state) {
'vShow', if (hasJSX(path)) {
'vModelSelect', const importNames = [
'vModelText', 'createVNode',
'vModelCheckbox', 'Fragment',
'vModelRadio', 'resolveComponent',
'vModelText', 'withDirectives',
'vModelDynamic', 'vShow',
'resolveDirective', 'vModelSelect',
'mergeProps', 'vModelText',
'createTextVNode', 'vModelCheckbox',
'isVNode', 'vModelRadio',
]; 'vModelText',
if (isModule(path)) { 'vModelDynamic',
// import { createVNode } from "vue"; 'resolveDirective',
const importMap: Record< 'mergeProps',
string, 'createTextVNode',
t.MemberExpression | t.Identifier 'isVNode',
> = {}; ];
importNames.forEach((name) => { if (isModule(path)) {
state.set(name, () => { // import { createVNode } from "vue";
if (importMap[name]) { const importMap: Record<
return types.cloneNode(importMap[name]); string,
} t.MemberExpression | t.Identifier
const identifier = addNamed(path, name, 'vue', { > = {};
ensureLiveReference: true, importNames.forEach((name) => {
}); state.set(name, () => {
importMap[name] = identifier; if (importMap[name]) {
return identifier; return types.cloneNode(importMap[name]);
}
const identifier = addNamed(path, name, 'vue', {
ensureLiveReference: true,
}); });
importMap[name] = identifier;
return identifier;
}); });
const { enableObjectSlots = true } = state.opts; });
if (enableObjectSlots) { const { enableObjectSlots = true } = state.opts;
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => { if (enableObjectSlots) {
if (importMap.runtimeIsSlot) { state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
return importMap.runtimeIsSlot; if (importMap.runtimeIsSlot) {
} return importMap.runtimeIsSlot;
const { name: isVNodeName } = state.get( }
'isVNode' const { name: isVNodeName } = state.get(
)() as t.Identifier; 'isVNode'
const isSlot = path.scope.generateUidIdentifier('isSlot'); )() as t.Identifier;
const ast = template.ast` const isSlot = path.scope.generateUidIdentifier('isSlot');
const ast = template.ast`
function ${isSlot.name}(s) { function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s)); return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
} }
`; `;
const lastImport = (path.get('body') as NodePath[]) const lastImport = (path.get('body') as NodePath[])
.filter((p) => p.isImportDeclaration()) .filter((p) => p.isImportDeclaration())
.pop(); .pop();
if (lastImport) { if (lastImport) {
lastImport.insertAfter(ast); lastImport.insertAfter(ast);
} }
importMap.runtimeIsSlot = isSlot; importMap.runtimeIsSlot = isSlot;
return 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,
});
}
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; const { enableObjectSlots = true } = state.opts;
if (enableObjectSlots) { if (enableObjectSlots) {
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => { state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
if (helpers.runtimeIsSlot) { if (helpers.runtimeIsSlot) {
return helpers.runtimeIsSlot; return helpers.runtimeIsSlot;
} }
const isSlot = path.scope.generateUidIdentifier('isSlot'); const isSlot = path.scope.generateUidIdentifier('isSlot');
const { object: objectName } = state.get( const { object: objectName } = state.get(
'isVNode' 'isVNode'
)() as t.MemberExpression; )() as t.MemberExpression;
const ast = template.ast` const ast = template.ast`
function ${isSlot.name}(s) { function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${ return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${
(objectName as t.Identifier).name (objectName as t.Identifier).name
@ -153,46 +159,47 @@ export default declare<VueJSXPluginOptions, BabelCore.PluginObj<State>>(
} }
`; `;
const nodePaths = path.get('body') as NodePath[]; const nodePaths = path.get('body') as NodePath[];
const lastImport = nodePaths const lastImport = nodePaths
.filter( .filter(
(p) => (p) =>
p.isVariableDeclaration() && p.isVariableDeclaration() &&
p.node.declarations.some( p.node.declarations.some(
(d) => (d) =>
(d.id as t.Identifier)?.name === sourceName.name (d.id as t.Identifier)?.name === sourceName.name
) )
) )
.pop(); .pop();
if (lastImport) { if (lastImport) {
lastImport.insertAfter(ast); lastImport.insertAfter(ast);
}
return isSlot;
});
}
}
const {
opts: { pragma = '' },
file,
} = state;
if (pragma) {
state.set('createVNode', () => t.identifier(pragma));
}
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]));
} }
return isSlot;
});
}
}
const {
opts: { pragma = '' },
file,
} = state;
if (pragma) {
state.set('createVNode', () => t.identifier(pragma));
}
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]));
} }
} }
} }
}, }
}, },
}, },
}; },
} };
); });
export default plugin;

View File

@ -12,173 +12,182 @@ import { declare } from '@babel/helper-plugin-utils';
export { SimpleTypeResolveOptions as Options }; export { SimpleTypeResolveOptions as Options };
export default declare<SimpleTypeResolveOptions>(({ types: t }, options) => { const plugin: (
let ctx: SimpleTypeResolveContext | undefined; api: object,
let helpers: Set<string> | undefined; options: SimpleTypeResolveOptions | null | undefined,
dirname: string
) => BabelCore.PluginObj<BabelCore.PluginPass> =
declare<SimpleTypeResolveOptions>(({ types: t }, options) => {
let ctx: SimpleTypeResolveContext | undefined;
let helpers: Set<string> | undefined;
return { return {
name: 'babel-plugin-resolve-type', name: 'babel-plugin-resolve-type',
pre(file) { pre(file) {
const filename = file.opts.filename || 'unknown.js'; const filename = file.opts.filename || 'unknown.js';
helpers = new Set(); helpers = new Set();
ctx = { ctx = {
filename: filename, filename: filename,
source: file.code, source: file.code,
options, options,
ast: file.ast.program.body, ast: file.ast.program.body,
isCE: false, isCE: false,
error(msg, node) { error(msg, node) {
throw new Error( throw new Error(
`[@vue/babel-plugin-resolve-type] ${msg}\n\n${filename}\n${codeFrameColumns( `[@vue/babel-plugin-resolve-type] ${msg}\n\n${filename}\n${codeFrameColumns(
file.code, file.code,
{ {
start: { start: {
line: node.loc!.start.line, line: node.loc!.start.line,
column: node.loc!.start.column + 1, column: node.loc!.start.column + 1,
}, },
end: { end: {
line: node.loc!.end.line, line: node.loc!.end.line,
column: node.loc!.end.column + 1, column: node.loc!.end.column + 1,
}, },
} }
)}` )}`
); );
}, },
helper(key) { helper(key) {
helpers!.add(key); helpers!.add(key);
return `_${key}`; return `_${key}`;
}, },
getString(node) { getString(node) {
return file.code.slice(node.start!, node.end!); return file.code.slice(node.start!, node.end!);
}, },
propsTypeDecl: undefined, propsTypeDecl: undefined,
propsRuntimeDefaults: undefined, propsRuntimeDefaults: undefined,
propsDestructuredBindings: {}, propsDestructuredBindings: {},
emitsTypeDecl: undefined, emitsTypeDecl: undefined,
}; };
},
visitor: {
CallExpression(path) {
if (!ctx) {
throw new Error(
'[@vue/babel-plugin-resolve-type] context is not loaded.'
);
}
const { node } = path;
if (!t.isIdentifier(node.callee, { name: 'defineComponent' })) return;
if (!checkDefineComponent(path)) return;
const comp = node.arguments[0];
if (!comp || !t.isFunction(comp)) return;
let options = node.arguments[1];
if (!options) {
options = t.objectExpression([]);
node.arguments.push(options);
}
node.arguments[1] = processProps(comp, options) || options;
node.arguments[1] = processEmits(comp, node.arguments[1]) || options;
}, },
VariableDeclarator(path) { visitor: {
inferComponentName(path); CallExpression(path) {
if (!ctx) {
throw new Error(
'[@vue/babel-plugin-resolve-type] context is not loaded.'
);
}
const { node } = path;
if (!t.isIdentifier(node.callee, { name: 'defineComponent' })) return;
if (!checkDefineComponent(path)) return;
const comp = node.arguments[0];
if (!comp || !t.isFunction(comp)) return;
let options = node.arguments[1];
if (!options) {
options = t.objectExpression([]);
node.arguments.push(options);
}
node.arguments[1] = processProps(comp, options) || options;
node.arguments[1] = processEmits(comp, node.arguments[1]) || options;
},
VariableDeclarator(path) {
inferComponentName(path);
},
}, },
}, post(file) {
post(file) { for (const helper of helpers!) {
for (const helper of helpers!) { addNamed(file.path, `_${helper}`, 'vue');
addNamed(file.path, `_${helper}`, 'vue'); }
},
};
function inferComponentName(
path: BabelCore.NodePath<BabelCore.types.VariableDeclarator>
) {
const id = path.get('id');
const init = path.get('init');
if (!id || !id.isIdentifier() || !init || !init.isCallExpression())
return;
if (!init.get('callee')?.isIdentifier({ name: 'defineComponent' }))
return;
if (!checkDefineComponent(init)) return;
const nameProperty = t.objectProperty(
t.identifier('name'),
t.stringLiteral(id.node.name)
);
const { arguments: args } = init.node;
if (args.length === 0) return;
if (args.length === 1) {
init.node.arguments.push(t.objectExpression([]));
} }
}, args[1] = addProperty(t, args[1], nameProperty);
};
function inferComponentName(
path: BabelCore.NodePath<BabelCore.types.VariableDeclarator>
) {
const id = path.get('id');
const init = path.get('init');
if (!id || !id.isIdentifier() || !init || !init.isCallExpression()) return;
if (!init.get('callee')?.isIdentifier({ name: 'defineComponent' })) return;
if (!checkDefineComponent(init)) return;
const nameProperty = t.objectProperty(
t.identifier('name'),
t.stringLiteral(id.node.name)
);
const { arguments: args } = init.node;
if (args.length === 0) return;
if (args.length === 1) {
init.node.arguments.push(t.objectExpression([]));
}
args[1] = addProperty(t, args[1], nameProperty);
}
function processProps(
comp: BabelCore.types.Function,
options:
| BabelCore.types.ArgumentPlaceholder
| BabelCore.types.SpreadElement
| BabelCore.types.Expression
) {
const props = comp.params[0];
if (!props) return;
if (props.type === 'AssignmentPattern') {
ctx!.propsTypeDecl = getTypeAnnotation(props.left);
ctx!.propsRuntimeDefaults = props.right;
} else {
ctx!.propsTypeDecl = getTypeAnnotation(props);
} }
if (!ctx!.propsTypeDecl) return; function processProps(
comp: BabelCore.types.Function,
options:
| BabelCore.types.ArgumentPlaceholder
| BabelCore.types.SpreadElement
| BabelCore.types.Expression
) {
const props = comp.params[0];
if (!props) return;
const runtimeProps = extractRuntimeProps(ctx!); if (props.type === 'AssignmentPattern') {
if (!runtimeProps) { ctx!.propsTypeDecl = getTypeAnnotation(props.left);
return; ctx!.propsRuntimeDefaults = props.right;
} else {
ctx!.propsTypeDecl = getTypeAnnotation(props);
}
if (!ctx!.propsTypeDecl) return;
const runtimeProps = extractRuntimeProps(ctx!);
if (!runtimeProps) {
return;
}
const ast = parseExpression(runtimeProps);
return addProperty(
t,
options,
t.objectProperty(t.identifier('props'), ast)
);
} }
const ast = parseExpression(runtimeProps); function processEmits(
return addProperty( comp: BabelCore.types.Function,
t, options:
options, | BabelCore.types.ArgumentPlaceholder
t.objectProperty(t.identifier('props'), ast) | BabelCore.types.SpreadElement
); | BabelCore.types.Expression
} ) {
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
if (
!setupCtx ||
!t.isTSTypeReference(setupCtx) ||
!t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
)
return;
function processEmits( const emitType = setupCtx.typeParameters?.params[0];
comp: BabelCore.types.Function, if (!emitType) return;
options:
| BabelCore.types.ArgumentPlaceholder
| BabelCore.types.SpreadElement
| BabelCore.types.Expression
) {
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
if (
!setupCtx ||
!t.isTSTypeReference(setupCtx) ||
!t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
)
return;
const emitType = setupCtx.typeParameters?.params[0]; ctx!.emitsTypeDecl = emitType;
if (!emitType) return; const runtimeEmits = extractRuntimeEmits(ctx!);
ctx!.emitsTypeDecl = emitType; const ast = t.arrayExpression(
const runtimeEmits = extractRuntimeEmits(ctx!); Array.from(runtimeEmits).map((e) => t.stringLiteral(e))
);
return addProperty(
t,
options,
t.objectProperty(t.identifier('emits'), ast)
);
}
});
const ast = t.arrayExpression( export default plugin;
Array.from(runtimeEmits).map((e) => t.stringLiteral(e))
);
return addProperty(
t,
options,
t.objectProperty(t.identifier('emits'), ast)
);
}
});
function getTypeAnnotation(node: BabelCore.types.Node) { function getTypeAnnotation(node: BabelCore.types.Node) {
if ( if (

View File

@ -1,10 +1,13 @@
import { defineConfig } from 'tsdown'; import { defineConfig } from 'tsdown';
export default defineConfig({ export default defineConfig({
workspace: ['./packages/babel-plugin-jsx', './packages/babel-plugin-resolve-type'], workspace: [
'./packages/babel-plugin-jsx',
'./packages/babel-plugin-resolve-type',
],
entry: ['src/index.ts'], entry: ['src/index.ts'],
format: ['cjs', 'esm'], format: ['cjs', 'esm'],
dts: true, dts: { oxc: true },
target: 'es2015', target: 'es2015',
platform: 'neutral', platform: 'neutral',
}); });