UNPKG

forget-api

Version:

无需封装 Axios,无需写接口请求函数,无需维护返回值类型,把这些琐碎重复的事情交给工具来处理,让精力聚焦在核心功能的实现上。

204 lines (195 loc) 7.82 kB
/** * forget-api@0.0.0-alpha.2 * * @author Ambit Tsai <ambit_tsai@qq.com> * @license MulanPSL-2.0 */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var path = require('node:path'); var fs = require('node:fs'); var typescript = require('typescript'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); function generateRoute(filePath, type = 'dist') { const pathProps = path__default["default"].parse(filePath); const config = getRootConfig(); const ctorName = pathProps.name.replace(config.suffix, ''); const targetPath = pathProps.dir.endsWith(path__default["default"].sep + ctorName) ? pathProps.dir : pathProps.dir + path__default["default"].sep + ctorName; const routeRoot = path__default["default"].resolve(getProjectRoot(), config[type === 'dist' ? 'routeDistRoot' : 'routeSrcRoot']); return '/' + path__default["default"].relative(routeRoot, targetPath).replace(/\\/g, '/'); } const CONFIG_FILE_NAME = 'full-stack.json'; function getRootConfig() { const configPath = path__default["default"].resolve(getProjectRoot(), CONFIG_FILE_NAME); return require(configPath); } function getProjectRoot() { const config = getCurrentConfig(); return config.root ? path__default["default"].resolve(process.cwd(), config.root) : process.cwd(); } function getCurrentConfig() { const configPath = path__default["default"].resolve(process.cwd(), CONFIG_FILE_NAME); return require(configPath); } function vitePluginForNest({ alias = '@@api', baseUrl = '', } = {}) { if (!alias.endsWith('/')) { alias = alias + '/'; } const SUFFIX = '?raw&_api'; return { name: 'vite-plugin-nest', resolveId(source, importer) { if (!source.startsWith(alias)) { return; } const currentDir = typescript.sys.getCurrentDirectory(); const configPath = typescript.findConfigFile(currentDir, typescript.sys.fileExists); if (!configPath) { throw new Error('cannot find a valid "tsconfig.json"'); } const { config: { compilerOptions }, } = typescript.readConfigFile(configPath, (path) => fs__default["default"].readFileSync(path, 'utf8')); delete compilerOptions.moduleResolution; // FIXME: moduleResolution 不被识别 const compilerHost = typescript.createCompilerHost(compilerOptions); const { resolvedModule } = typescript.resolveModuleName(source, importer || '', compilerOptions, compilerHost); if (resolvedModule) { return (path__default["default"].resolve(currentDir, resolvedModule.resolvedFileName) + SUFFIX); } throw new Error(`cannot resolve "${source}", please add "${alias}*" to ${configPath}`); }, transform(code, id) { if (id.endsWith(SUFFIX)) { return { code: transformCode(id.replace(SUFFIX, ''), code.replace(/(\\r)|(\\n)/g, (m, p1, p2) => p1 ? '\r' : '\n'), baseUrl), map: { mappings: '' }, }; } }, }; } function transformCode(filePath, sourceText, baseUrl) { const sourceFile = typescript.createSourceFile(filePath, sourceText, typescript.ScriptTarget.ESNext); const ctorNode = findCtorNode(sourceFile); if (ctorNode) { let prefix = getPrefix(ctorNode); if (prefix === '##') { prefix = generateRoute(filePath, 'src'); } const apiConfig = getApiConfig(ctorNode); return [ `import { createApis } from 'forget-api/request';`, `export default createApis('${baseUrl + prefix}', ${JSON.stringify(apiConfig)})`, ].join('\n'); } throw new Error(`cannot find a valid controller in ${filePath}`); } function findCtorNode(sourceFile) { var _a; let ctorName = ''; const classNodes = []; for (const node of sourceFile.statements) { if (typescript.isExportAssignment(node) && typescript.isCallExpression(node.expression) && node.expression.expression.text === 'defineExpose') { const [firstParam] = node.expression.arguments; if (firstParam) { ctorName = firstParam.text; } break; } else if (typescript.isClassDeclaration(node)) { classNodes.push(node); } } for (const node of classNodes) { if (((_a = node.name) === null || _a === void 0 ? void 0 : _a.text) === ctorName) { return node; } } } function getPrefix(ctorNode) { for (const { expression } of ctorNode.decorators || []) { if (!(typescript.isCallExpression(expression) && typescript.isIdentifier(expression.expression) && expression.expression.text === 'Controller')) { continue; } const [firstParam] = expression.arguments; if (firstParam) { if (typescript.isObjectLiteralExpression(firstParam)) { // 入参是对象 for (const propNode of firstParam.properties) { if (typescript.isPropertyAssignment(propNode) && typescript.isIdentifier(propNode.name) && propNode.name.text === 'path') { return propNode.name.text; } } } else if (typescript.isStringLiteral(firstParam)) { // 入参是字符串 return firstParam.text; } else if (typescript.isIdentifier(firstParam) && firstParam.text === '__filename') { return '##'; } } break; } return ''; } const METHOD_DECORATORS = [ 'Delete', 'Get', 'Head', 'Options', 'Patch', 'Post', 'Put', ]; function getApiConfig(ctorNode) { const map = {}; for (const node of ctorNode.members) { if (!(typescript.isMethodDeclaration(node) && typescript.isIdentifier(node.name))) { continue; } const method = node.name.text; let httpMethod = ''; let path = ''; for (const { expression } of node.decorators || []) { if (!(typescript.isCallExpression(expression) && typescript.isIdentifier(expression.expression) && METHOD_DECORATORS.includes(expression.expression.text))) { continue; } httpMethod = expression.expression.text.toUpperCase(); const [firstParam] = expression.arguments; if (firstParam && typescript.isStringLiteral(firstParam)) { if (/[?+*()]/.test(firstParam.text)) { httpMethod = 'PATH_HAS_WILDCARD'; } else { const { text } = firstParam; path = text.startsWith('/') ? text : '/' + text; } } // TODO: 判断装饰器入参为 string[] undefined null 等 break; } if (!httpMethod) { httpMethod = 'GET'; // TODO: 考虑 filename 情况 } map[method] = [httpMethod, path]; } return map; } exports.vitePluginForNest = vitePluginForNest; //# sourceMappingURL=vite.js.map