forget-api
Version:
无需封装 Axios,无需写接口请求函数,无需维护返回值类型,把这些琐碎重复的事情交给工具来处理,让精力聚焦在核心功能的实现上。
204 lines (195 loc) • 7.82 kB
JavaScript
/**
* forget-api@0.0.0-alpha.2
*
* @author Ambit Tsai <ambit_tsai@qq.com>
* @license MulanPSL-2.0
*/
;
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