Node.js + TypeScript: Unclear syntax with type script compiled code
Asked Answered
E

5

28

I'm trying to work with TypeScript in my node project, but I have some issues with that.

This is my index.ts file:

import express from 'express';

const app = express();

I'm running:

tsc --module commonsjs -d index.ts

My output is index.js:

var express_1 = require('express');
var app = express_1["default"]();

Where did this ["default"] came from? It is making my code not to run properly:

var app = express_1["default"]();
                              ^

TypeError: express_1.default is not a function

As far as I understand, I should have got the code without the "default" brackets and it would have worked fine - I tried removing the brackets and it worked.

What am I missing here?

Expose answered 29/12, 2015 at 22:45 Comment(3)
Module should be commonjs and not commonsjs. That may be causing you a problem.Installment
Consider using esModuleInterop: true compiler option (set it on your tsconfig). This allows you to do import express from 'express' as you would expectSimoniac
instead of import express from 'express' you could also try import * as express from 'express' - which one will work usually depends on esModuleInterop setting in tsconfig.jsonHoward
I
38

The safest solution would be:

import express = require('express');

This transpiles to:

var express = require('express');

The official documentation for import require declarations can be found here.

I believe TypeScript expects an export named "default" to function as your code above, judging from the final paragraph here.


Side note: It looks like TypeScript's newest version ([email protected] at the time of writing) will throw a warning on a compile attempt which would attempt to use a missing default:

index.ts(1,8): error TS1192: Module '"express"' has no default export.

Side note 2: An example from Microsoft using the import * as express from 'express'; syntax can be found here. When targeting a module of commonjs (as they are in this example), this will also transpile to var express = require('express');.


If you have at least TypeScript 2.7 and are targeting CommonJS, you can use esModuleInterop, as well.

From the link:

To give users the same runtime behavior as Babel or Webpack, TypeScript provides a new --esModuleInterop flag when emitting to legacy module formats.

Under the new --esModuleInterop flag, these callable CommonJS modules must be imported as default imports like so:

import express from "express";

let app = express();

We strongly suggest that Node.js users leverage this flag with a module target of CommonJS for libraries like Express.js, which export a callable/constructable module.

Incline answered 29/12, 2015 at 23:44 Comment(2)
import * is the wrong way to import legacy modules. See https://mcmap.net/q/130582/-new-es6-syntax-for-importing-commonjs-amd-modules-i-e-import-foo-require-39-foo-39.Hybris
Consider using esModuleInterop: true compiler option (set it on your tsconfig). This allows you to do import express from 'express' as you would expectSimoniac
F
39

I solved this by adding the following to tsconfig.json:

{
  "compilerOptions": {
    ... 
    "module": "commonjs",
    "esModuleInterop": true,
    ...
  }
}

The esModuleInterop flag is described as: "Emit __importStar and __importDefault helpers for runtime babel ecosystem compatibility and enable --allowSyntheticDefaultImports for typesystem compatibility."

https://www.typescriptlang.org/docs/handbook/compiler-options.html

Formulism answered 25/6, 2019 at 18:38 Comment(3)
This solved my problem! Could've known ts-node-dev is using TypeScript's configuration, so I added a tsconfig.json file. Thanks!Filbert
This should be the accepted answer, as it allows to simply use import express from 'express'Sivan
I agree this is the correct strategy with newer versions of typescript. This option wasn't made available until typescript 2.7, which came out in 2018.Incline
I
38

The safest solution would be:

import express = require('express');

This transpiles to:

var express = require('express');

The official documentation for import require declarations can be found here.

I believe TypeScript expects an export named "default" to function as your code above, judging from the final paragraph here.


Side note: It looks like TypeScript's newest version ([email protected] at the time of writing) will throw a warning on a compile attempt which would attempt to use a missing default:

index.ts(1,8): error TS1192: Module '"express"' has no default export.

Side note 2: An example from Microsoft using the import * as express from 'express'; syntax can be found here. When targeting a module of commonjs (as they are in this example), this will also transpile to var express = require('express');.


If you have at least TypeScript 2.7 and are targeting CommonJS, you can use esModuleInterop, as well.

From the link:

To give users the same runtime behavior as Babel or Webpack, TypeScript provides a new --esModuleInterop flag when emitting to legacy module formats.

Under the new --esModuleInterop flag, these callable CommonJS modules must be imported as default imports like so:

import express from "express";

let app = express();

We strongly suggest that Node.js users leverage this flag with a module target of CommonJS for libraries like Express.js, which export a callable/constructable module.

Incline answered 29/12, 2015 at 23:44 Comment(2)
import * is the wrong way to import legacy modules. See https://mcmap.net/q/130582/-new-es6-syntax-for-importing-commonjs-amd-modules-i-e-import-foo-require-39-foo-39.Hybris
Consider using esModuleInterop: true compiler option (set it on your tsconfig). This allows you to do import express from 'express' as you would expectSimoniac
H
7

If you are trying to use the default export of a non-ES6 module like Express.js, you need to use the legacy import syntax import express = require('express').

In ES6 modules, there is no default value export like the module.exports of Node.js modules or the return of AMD modules; the default export of an ES6 module is just the default key. This is why, when you use an ES6 default import as you are attempting to do, TypeScript generates JavaScript with an access to the default property.

More information about this is available at New es6 syntax for importing commonjs / amd modules i.e. `import foo = require('foo')`.

Hybris answered 30/12, 2015 at 4:17 Comment(7)
Thanks! I had some research on the subject thanks to you.Expose
I don’t understand why you accepted the other answer as the correct answer. It’s the wrong answer.Hybris
If it is truly the wrong answer for the current version of Typescript, then I suggest you have Microsoft edit their examples.Incline
@Incline That Microsoft example is not the same as what the OP is doing! The example uses only the static property from express, it does not try to use the default export (because it couldn’t, because it’s wrong and won’t work). In such a case, import * is fine, because it makes the imported express identifier a blank List, then adds all exported properties of the express module onto that List, per the ES specification. It not correct for using the default export.Hybris
As far as I can tell, the const app = express(); in the original question is exactly what is being done in the Microsoft example (minus the const, of course). I agree that require will work in more scenarios, however, so I will edit accordingly.Incline
Sorry, I missed that line. That really should not be working, since the ES spec is explicit that the value of a * as import is a Namespace Object, not any callable item, and implementation of ES6 modules syntax was supposed to disallow it. So I’m trying to track down when and how the semantics of * as were changed to not fail in this case.Hybris
Perhaps it allows it because my test environment and their example were both aiming for commonjs (for better or worse or unintentionally)? Targeting ES6 with no module defined resulted in error TS1202 (import assignment cannot be used), but does attempt to transpile. ES6/ES5 with commonjs as the module both transpiled to var express = require('express');. Targeting ES5 with no module defined doesn't even attempt to transpile, and suggests setting a module.Incline
B
5

If you still want to use the import keyword then use it like:

import express from "express"; 
// If the above is not supported by your project environment then follow as below
import * as express from "express";

In file tsconfig.json

{
  "compilerOptions": {
    ...   
    "module": "commonjs"
    ...
  }
}

Thanks to Josh Dando.

Bloodandthunder answered 12/10, 2018 at 4:52 Comment(1)
Make sure to have "module": "commonJS" in your tsconfig.jsonDisturbing
J
-1

Another solution that could work for you is do it like this:

import * as express from express;
const app = express();

It should work in the same way.

Jones answered 17/4, 2020 at 21:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.