helmetjs / helmet

Help secure Express apps with various HTTP headers

Home Page:https://helmetjs.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

This expression is not callable. error on typescript helmet 5.0.1

noam-honig opened this issue · comments

The full error is:

src/server/index.ts:6:9 - error TS2349: This expression is not callable.
  Type 'typeof import("C:/try/t21/helet-issue/node_modules/helmet/dist/index")' has no call signatures.

6 app.use(helmet({ contentSecurityPolicy: false }));
          ~~~~~~

Previously in version 4.6.0 it works great, but in the latest helmet 5.0.1 version it does not work.
/src/server/index.ts

import * as express from 'express';
import * as helmet from 'helmet';


const app = express();
app.use(helmet({ contentSecurityPolicy: false })); // this line shows the error.
app.listen(3002, () => console.log("Server started"));

Here's my tsconfig:

{
    "compileOnSave": false,
    "compilerOptions": {
        "outDir": "./dist/server",
        "module": "commonjs",
        "baseUrl": "./",
        "moduleResolution": "node",
        "target": "es2017",
        "lib": [
            "es2020",
            "dom"
        ]
    },
}

You can see this in code sandbox:
https://codesandbox.io/s/s4fuq

When I change the import to:

import helmet from 'helmet';

I get a runtime error:
TypeError: (0 , helmet_1.default) is not a function

tl;dr: change your import.

-import * as helmet from 'helmet';
+import helmet from 'helmet';

 // ...

 app.use(helmet());

I believe this is working as expected. This kind of import should never work:

// This should not work:
import * as foo from 'my-example-import';
foo();

That's because import * imports the whole package as a namespace. TypeScript sometimes lets you get away with this, but it's not technically valid JavaScript as far as I understand.

You can fix it in one of two ways:

  1. Import Helmet's default export:

    import helmet from 'helmet';
    
    // ...
    
    app.use(helmet());
  2. Import Helmet as a namespace, then use its subpackages. Be careful not to forget any! (I don't recommend this option unless you know what you're doing.)

    import * as helmet from 'helmet';
    
    // ...
    
    app.use(helmet.contentSecurityPolicy());
    app.use(helmet.dnsPrefetchControl());
    // ...

I'm going to close this issue because I think things are working as intended, but let me know if that's wrong and I can reopen.

Hi Evan, see the suffix of my report - I've tried it and I get the following error when I run node:

TypeError: (0 , helmet_1.default) is not a function

See code sandbox:
https://codesandbox.io/s/s4fuq

Run npm run start:

import * as express from "express";
import helmet from "helmet";

const app = express();
app.use(helmet({ contentSecurityPolicy: false }));
app.listen(3002, () => console.log("Server started"));

Error:

TypeError: (0 , helmet_1.default) is not a function
    at Object.<anonymous> (/sandbox/src/server/index.ts:5:15)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Module._compile (/sandbox/node_modules/source-map-support/source-map-support.js:568:25)
    at Module.m._compile (/tmp/ts-node-dev-hook-03339809695552054.js:69:33)
    at Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at require.extensions.<computed> (/tmp/ts-node-dev-hook-03339809695552054.js:71:20)
    at Object.nodeDevHook [as .ts] (/sandbox/node_modules/ts-node-dev/lib/hook.js:63:13)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Module.require (internal/modules/cjs/loader.js:974:19)
[ERROR] 13:46:52 TypeError: (0 , helmet_1.default) is not a function

Hmm, not sure what's going on. I'll reopen.

Three ideas:

  1. Enable esModuleInterop in your TypeScript configuration
  2. Use TypeScript's CommonJS import (import helmet = require("helmet"))
  3. Probably a big change, but change your package to use ECMAScript Modules instead of CommonJS. This probably means updating your package.json and tsconfig.

Setting esModuleInterop to true on tsconfig.json worked for me:

Setting esModuleInterop also worked for me, it broke the import * as express, but I fixed that as well.

It is a breaking change between 4.6 and current - it's up to you.

Thanks for the help

That's good!

  1. Do you feel like your problem is solved?
  2. Do you think Helmet needs to change anything about how it's exported or documented?

Totally. I tried my best to avoid breaking changes like this, but looks like I goofed a bit.

I'm going to close this issue, but I'll think about how to improve this (now and in the next major version).

I'm having the same issue, enabling esModuleInterop messes my whole project and I would like to avoid that.

@GuyMev Does import helmet = require("helmet") work for you?

@EvanHahn I was also seeing this issue in my project and like @GuyMev I would like to avoid modifying my tsconfig.json just for this library.

But the good news, I might have a possible fix to solve everyone's problems here. I'm not the biggest ESM/CJS/TS expert, but will open a pull request for review and see what you think.

commented

Hello,

import helmet = require("helmet") doesn't work, but const helmet = require("helmet") does. (But TS sees it as "any", and I haven't found a way to import just HelmetOptions.)

(With NestJS and TS 4.5.4, and ESM interop false.)

Is there any drawback to ESMinterop?

@PAStheLoD I think esModuleInterop should be safe to use.

I still need to review #345 which may fix this problem, but I've been busy.

I just released helmet@5.0.2 which should fix this problem. Please try updating to see if that fixes things for you!