Оператор импорта работает неправильно, если скомпилирован напрямую, но работает нормально после сохранения в VS Code - PullRequest
0 голосов
/ 27 февраля 2020

У меня проблема с тем, что определенный импорт не работает при прямой компиляции, но работает нормально после сохранения исходного кода в 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

Кстати, ошибка кажется очень похожей на эти проблемы:

1 Ответ

0 голосов
/ 02 марта 2020

удалось все исправить, установив "esModuleInterop": true на tsconfig.json и применив требуемый импорт как getParamNames from 'get-param-names'.

После этого и vue-cli, и es-node-dev начали последовательно строить код; кроме того, Jest также требовал немного больше информации о типах в своих модульных тестах, но потом работал нормально.

...