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-clies-node-dev 都开始一致地构建代码;此外,Jest 还需要在其单元测试中提供更多类型信息,但之后工作正常。

【讨论】:

以上是关于import 语句直接编译会出错,但是保存到 VS Code 后就可以了的主要内容,如果未能解决你的问题,请参考以下文章

思考|编译器会出错么?

使用JAVASSIST怎样向Class文件中加入import语句?

有人说没有用到的 import 语句会被忽略,真的这样吗?

python介绍

我把asp.net源码下载到电脑以后,用vs2008打开,全是错误,显示创建控件时出错。

Python基础-1