UNPKG

@vue.ts/tsx-auto-props

Version:

Automatically add props definition for Vue 3 TSX.

96 lines (92 loc) 3.47 kB
import { createUnplugin } from "unplugin"; import { ensureLanguage, getLanguage } from "@vue.ts/language"; import { createFilter, normalizePath } from "@vue.ts/shared"; import MagicString from "magic-string"; import ts from "typescript"; import { join } from "node:path"; //#region src/core/utils.ts const resolveOptions = (rawOptions) => ({ include: rawOptions.include ?? ["**/*.vue", "**/*.tsx"], exclude: rawOptions.exclude ?? ["node_modules/**"], tsconfigPath: rawOptions.tsconfigPath ?? join(process.cwd(), "tsconfig.json") }); function getNodeAssignNode(node) { let parent = null; let variableList = null; let variable = null; while (node) { if (parent) { if (ts.isVariableDeclarationList(parent)) variableList = parent; if (ts.isVariableDeclaration(parent)) variable = parent; } parent = node; node = node.parent; } return { variableList, variable }; } //#endregion //#region src/core/transform.ts const generateDefineProps = (name, props) => `\n;Object.defineProperty(${name}, "props", { value: ${JSON.stringify(props)}, });`; const DEFINE_COMPONENT = "defineComponent"; function transform(code, id) { const s = new MagicString(code); const normalizedFilepath = normalizePath(id); const language = getLanguage(); const typeChecker = language.__internal__.typeChecker; const ast = language.getVirtualFileOrTsAst(normalizedFilepath); if (!ast) return; language.traverseAst(ast, (node) => { if (!ts.isCallExpression(node)) return; const identifier = node.expression; const symbol = typeChecker.getSymbolAtLocation(identifier); if (symbol?.declarations?.some((d) => ts.isImportSpecifier(d) && (d.propertyName ?? d.name)?.text === DEFINE_COMPONENT)) { const arg = node.arguments[0]; let setupFn; if (ts.isObjectLiteralExpression(arg)) { const props$1 = arg.properties.find((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.escapedText === "props"); const setup = arg.properties.find((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.escapedText === "setup"); if (!props$1 && setup && ts.isPropertyAssignment(setup)) { const value = setup.initializer; if (ts.isFunctionLike(value)) setupFn = value; } } else if (ts.isFunctionLike(arg)) setupFn = arg; else throw new Error("[@vue.ts/tsx-auto-props] Invalid defineComponent argument"); const props = setupFn?.parameters[0]; if (props) { const propsList = typeChecker.getTypeAtLocation(props).getProperties().map((p) => p.name); const { variableList, variable } = getNodeAssignNode(node); if (propsList.length > 0 && variableList && variable) s.appendRight(variableList.getEnd() + 1, generateDefineProps(variable.name.getText(ast), propsList)); } } }); return { code: s.toString(), map: s.generateMap({ hires: true }) }; } //#endregion //#region src/index.ts const unpluginFactory = (options = {}) => { const resolvedOptions = resolveOptions(options); const filter = createFilter(resolvedOptions.include, resolvedOptions.exclude); return { name: "@vue.ts/tsx-auto-props", buildStart() { const resolvedOptions$1 = resolveOptions(options); ensureLanguage(resolvedOptions$1.tsconfigPath); }, transform(code, id) { if (!filter(id)) return; return transform(code, id); } }; }; const unplugin = /* @__PURE__ */ createUnplugin(unpluginFactory); var src_default = unplugin; //#endregion export { src_default, unplugin, unpluginFactory };