import 语句直接编译会出错,但是保存到 VS Code 后就可以了
Posted
技术标签:
【中文标题】import 语句直接编译会出错,但是保存到 VS Code 后就可以了【英文标题】:Import statement works incorrectly if directly compiled, but works okay after saving it on VS Code 【发布时间】:2020-06-11 22:07:12 【问题描述】:我遇到了一个问题,直接编译时某个导入无法工作,但在将源代码保存到 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)
如果您在不更改任何内容的情况下继续编译应用程序,则会重复该错误,但是,如果您在 VS Code 上编辑 base.ts
,将导入更改为错误,然后将其改回,它会编译,即使您关闭重新启动服务器,错误不再重复。
如果你再次删除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
顺便说一句,该错误似乎与这些问题非常相似:
https://github.com/webpack/webpack/issues/4742 https://github.com/palantir/blueprint/issues/959【问题讨论】:
您在导入get-param-names
时似乎遇到了问题。添加此库的导出代码部分。当您将所有内容导入getParamNames
然后将其用作函数时,这对我来说看起来很奇怪。生成的 getParamNames 是一个对象。
应该是一个函数(npmjs.com/package/get-param-names);而且,事实上,它通常按照记录的方式工作;问题是,有时编译器会以其他方式处理它。我试过import * as foo from 'bar'
、import foo from 'bar
和 'import foo from 'bar';每个都给出不同的错误;除了所描述的情况外,只有第一个选项有些一致。
【参考方案1】:
通过在tsconfig.json
上设置"esModuleInterop": true
并以getParamNames from 'get-param-names'
实现所需的导入,设法解决了所有问题。
之后,vue-cli
和 es-node-dev
都开始一致地构建代码;此外,Jest 还需要在其单元测试中提供更多类型信息,但之后工作正常。
【讨论】:
以上是关于import 语句直接编译会出错,但是保存到 VS Code 后就可以了的主要内容,如果未能解决你的问题,请参考以下文章
使用JAVASSIST怎样向Class文件中加入import语句?