У меня проблема с тем, что определенный импорт не работает при прямой компиляции, но работает нормально после сохранения исходного кода в VS Code.
Если я удаляю папку node_modules/.cache
и выполняю команду:
yarn serve
Он компилируется нормально, но затем браузер выдает ошибку во время выполнения:
Uncaught TypeError: getParamNames is not a function
at eval (webpack-internal:///../api/src/clients/base.ts:116)
at Array.forEach (<anonymous>)
at exports.Remotable (webpack-internal:///../api/src/clients/base.ts:114)
at DecorateConstructor (webpack-internal:///../api/node_modules/reflect-metadata/Reflect.js:541)
at Object.decorate (webpack-internal:///../api/node_modules/reflect-metadata/Reflect.js:130)
at Module.__decorate (webpack-internal:///../api/node_modules/tslib/tslib.es6.js:78)
at eval (webpack-internal:///../api/src/clients/order.ts:26)
at Object.../api/src/clients/order.ts (app.js:909)
at __webpack_require__ (app.js:785)
at fn (app.js:151)
Если вы продолжаете компилировать приложение без изменений, ошибка повторяется, но затем, если вы измените base.ts
в VS Code, измените импорт на что-то неправильное, затем верните его обратно, он скомпилируется, и даже если вы закроете сервер и запустите его снова, ошибка не повторится.
Если Вы снова удаляете node_modules/.cache
или ожидаете его истечения, цикл перезапускается.
base.ts:
/*eslint prefer-spread: "off"*/
/* eslint-disable no-empty */
import * as getParamNames from 'get-param-names';
import * as NamedRouter from 'named-routes';
import { fromPairs, has, get, isString } from 'lodash';
import { Config } from '../../config';
import { RestClient } from 'typed-rest-client';
import 'reflect-metadata'
import { classToPlain } from 'class-transformer';
export interface ApiResult<T> {
status?: string;
data?: T;
error?;
}
// ATSTODO: Implementar conversão de retorno para classes específicas
export const LocalClient = (service) => {
return (Client) => {
const ParentClass = Object.getPrototypeOf(Client);
Object.getOwnPropertyNames(ParentClass.prototype).filter(s => s !== 'constructor').forEach(propName => {
if (ParentClass.prototype[propName] instanceof Function) {
if (has(Client.prototype, propName)) {
// Já tem uma implementação específica
return;
}
Client.prototype[propName] = (...args: any[]) => {
return service[propName].apply(service, args);
}
}
});
}
}
export const getHost = () => {
if (Config.dbConfig.port) {
return Config.dbConfig.host + ':' + Config.dbConfig.port;
} else {
return Config.dbConfig.host;
}
}
class RemotabeManager {
public metadata = {};
private getTargetName(target: any): string {
return isString(target) ? target : target.constructor.name;
}
public createTarget = (target: any) => {
const name = this.getTargetName(target);
let targetMetadata = this.metadata[name];
if (!targetMetadata) {
targetMetadata = {
name,
target,
methods: {}
}
this.metadata[name] = targetMetadata;
}
return targetMetadata;
}
public getTarget = (target: any) => {
const name = this.getTargetName(target);
return this.metadata[name];
}
public forMethod(target: any, propertyKey: string | symbol) {
const methods = this.createTarget(target).methods;
let method = methods[propertyKey];
if (!method) {
method = {
name: propertyKey,
path: `/rpc/${String(propertyKey)}`,
parameters: []
};
methods[propertyKey] = method;
}
return method;
}
public registerParam(target: any, propertyKey: string | symbol, parameterIndex: number, value) {
const method = this.forMethod(target, propertyKey);
const existingInfo = method.parameters[parameterIndex] || {};
method.parameters[parameterIndex] = { ...value, ...existingInfo };
}
}
const remotableMetadata = new RemotabeManager();
/**
* Decorador
* @param constructor
*/
export const Remotable = (constructor) => {
Object.getOwnPropertyNames(constructor.prototype)
.filter(s => s !== 'constructor' && constructor.prototype[s] instanceof Function)
.forEach(name => {
const method = constructor.prototype[name];
getParamNames(method).forEach((parameterName, parameterIndex) =>
remotableMetadata.registerParam(constructor.prototype, name, parameterIndex, { name: parameterName }));
});
}
// ATSTODO: Implementar tratamento de erros
// ATSTODO: Implementar suporte a outros métodos além de GET
/**
* Decorator
* @param Client
*/
export const Controller = (client, service) => {
return (Server) => {
const metadata = remotableMetadata.getTarget(client);
if (!metadata) {
throw new Error(`Não encontrou os metadados para o client ${client}`);
}
Object.entries(metadata.methods).forEach(([methodName, info]) => {
if (has(Server.prototype, methodName)) {
// Já existe uma implementação específica do método
return;
}
const method = service[methodName];
if (!method) {
throw new Error(`Método não encontrado: ${methodName}`);
}
Server.prototype[methodName] = async (req, res, next) => {
try {
const params = get(info, 'parameters').map(({ name }) => req.params[name] || req.query[name]);
const result = await method.apply(service, params);
res.status(200).json({
status: 'success',
data: classToPlain(result)
});
} catch (error) {
next(error);
}
};
});
}
}
/**
* Decorator
* @param clientInterface
*/
export const RemoteClient = (clientInterface, baseUrl) => {
const namedRouter = new NamedRouter();
return (Client) => {
const metadata = remotableMetadata.getTarget(clientInterface);
if (!metadata) {
throw new Error(`Não encontrou os metadados para o client ${clientInterface}`);
}
const restClient = new RestClient('resulth-web', getHost());
Object.entries(metadata.methods).forEach(([methodName, info]) => {
if (has(Client.prototype, methodName)) {
// Já existe uma implementação específica do método
return;
}
const routeName = `autoClient.${metadata.name}.${methodName}`;
// eslint-disable-next-line
namedRouter.add('get', (info as any).path, (req, res, next) => {}, { name: routeName });
Client.prototype[methodName] = async (...params) => {
const paramsObj = fromPairs(get(info, 'parameters').map(({ name }, idx) => [name, params[idx]]));
const url = namedRouter.build(routeName, paramsObj);
const searchParams = new URLSearchParams();
Object.entries(paramsObj).forEach(([k, v]) => v && searchParams.append(k, String(v)));
const fullPath = `/api/v1/${baseUrl}/${url}?${searchParams}`;
const res = await restClient.get<ApiResult<any>>(fullPath);
return res.result.data;
}
});
}
}
/**
* Decorador
* @param path
*/
export const Path = (path: string) => {
return (target: any, propertyKey: string) => {
remotableMetadata.forMethod(target, propertyKey).path = path;
}
}
/**
* Decorador
* @param name
*/
export const Param = (name: string) => {
return (target: any, propertyKey: string | symbol, parameterIndex: number) => {
remotableMetadata.registerParam(target, propertyKey, parameterIndex, { name });
}
}
package. json:
{
"name": "framework-ats",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"@rauschma/stringio": "^1.4.0",
"@types/lodash": "^4.14.149",
"ajv-i18n": "^3.5.0",
"core-js": "^3.4.4",
"lodash": "^4.17.15",
"node-firebird": "^0.8.9",
"typed-rest-client": "^1.7.1",
"typescript-ioc": "^1.2.6",
"v-money": "^0.8.1",
"vue": "^2.6.10",
"vue-class-component": "^7.2.2",
"vue-form-json-schema": "^2.5.0",
"vue-property-decorator": "^8.3.0",
"vue-router": "^3.1.5",
"vue-the-mask": "^0.11.1",
"vuetify": "^2.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-eslint": "^4.1.0",
"@vue/cli-plugin-router": "^4.1.0",
"@vue/cli-plugin-typescript": "^4.2.2",
"@vue/cli-service": "^4.1.0",
"@vue/eslint-config-typescript": "^4.0.0",
"@typescript-eslint/eslint-plugin": "^2.19.0",
"@typescript-eslint/parser": "^2.19.0",
"babel-eslint": "^10.0.3",
"electron": "^6.0.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"material-design-icons-iconfont": "^5.0.1",
"sass": "^1.19.0",
"sass-loader": "^8.0.0",
"typescript": "~3.7.5",
"vue-cli-plugin-electron-builder": "^1.4.4",
"vue-cli-plugin-vuetify": "^2.0.4",
"vue-template-compiler": "^2.6.10",
"vuetify-loader": "^1.3.0"
}
}
tsconfig. json:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"typeRoots": [
"./node_modules/@types",
"./node_modules/vuetify/types"
],
"types": [
"webpack-env",
"vuetify"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
vue .config. js:
module.exports = {
"devServer": {
"disableHostCheck": true
},
"transpileDependencies": [
"vuetify"
]
}
babel.config. js:
module.exports = {
presets: [
[
'@vue/cli-plugin-babel/preset',
{
targets: {
node: 'current',
},
},
]
],
}
Добавление (2020-03-20)
Удалось заставить его работать частично путем создания явного объявления типа:
import getParamNames = require('get-param-names');
declare function getParamNames(o: any): string[];
export = getParamNames;
А также изменил импорт с import * as getParamNames from 'get-param-names';
на import getParamNames from 'get-param-names';
; это работало нормально для внешнего интерфейса, который построен через vue-cli
, но не для внутреннего интерфейса, который построен через ts-node-dev
:
ts-node-dev --respawn -- src/index.ts
Это дает эту ошибку на внутреннем сервере:
[INFO] 08:15:21 Restarting: D:\Java\framework-ats\api\src\clients\base.ts has been modified
Using ts-node version 8.6.2, typescript version 3.8.2
TypeError: get_param_names_1.default is not a function
at Object.getOwnPropertyNames.filter.forEach.name (D:\Java\framework-ats\api\src\clients\base.ts:107:26)
at Array.forEach (<anonymous>)
at exports.Remotable (D:\Java\framework-ats\api\src\clients\base.ts:105:10)
at DecorateConstructor (D:\Java\framework-ats\api\node_modules\reflect-metadata\Reflect.js:541:33)
at Object.decorate (D:\Java\framework-ats\api\node_modules\reflect-metadata\Reflect.js:130:24)
at __decorate (D:\Java\framework-ats\api\src\clients\product.ts:4:92)
at Object.<anonymous> (D:\Java\framework-ats\api\src\clients\product.ts:7:36)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Module._compile (D:\Java\framework-ats\api\node_modules\source-map-support\source-map-support.js:541:25)
at Module.m._compile (C:\Users\HAROLD~1\AppData\Local\Temp\ts-node-dev-hook-8308269448535168.js:57:25)
[ERROR] 08:15:21 TypeError: get_param_names_1.default is not a function
Кстати, ошибка кажется очень похожей на эти проблемы: