Ts-node: Use of enums is transpiled in a different way than with tsc

Created on 17 Jan 2018  路  13Comments  路  Source: TypeStrong/ts-node

For some reason TypeScript enum values are transpiled differently with ts-node than with tsc. This can lead to runtime errors if the used enum doesn't exist at runtime.

Here's an example: the current typings for big.js define an enum that doesn't actually exist in the JS library:

// node_modules/@types/big.js/index.d.ts
export const enum Comparison {
    GT = 1,
    EQ = 0,
    LT = -1,
}

This enum doesn't exist at runtime:

console.info(require('big.js').Comparison)
// undefined

Now, let's say we have this TS code:

import {Comparison} from 'big.js'

Comparison.LT.toString()

tsc transpiles it to this (using a sane tsconfig.json with ES2017 target):

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-1 /* LT */.toString();

Note that there's no require calls anywhere and the enum value -1 is inlined with a comment included. Running the generated JS file with node doesn't throw any exceptions.

Now, running the same TS file with ts-node gives me this:

/home/joonas/enum-bug/main.ts:3
Comparison.LT.toString()
           ^
TypeError: Cannot read property 'LT' of undefined
    at Object.<anonymous> (/home/joonas/enum-bug/main.ts:3:12)
    at Module._compile (module.js:643:30)
    at Module.m._compile (/home/joonas/enum-bug/node_modules/ts-node/src/index.ts:423:6)
    at Module._extensions..js (module.js:654:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/home/joonas/enum-bug/node_modules/ts-node/src/index.ts:426:4)
    at Module.load (module.js:556:32)
    at tryModuleLoad (module.js:499:12)
    at Function.Module._load (module.js:491:3)
    at Function.Module.runMain (module.js:684:10)
    at Object.<anonymous> (/home/joonas/enum-bug/node_modules/ts-node/src/_bin.ts:177:12)

After adding some logging to ts-node inside my node_modules, I can see that this is the transpiled output:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const big_js_1 = require("big.js");
big_js_1.Comparison.LT.toString();

Note the require call and how the enum value is not inlined but accessed at runtime.

Any ideas? This might of course be a misconfiguration in my part, a TS compiler bug, or a problem with the compiler API.

$ node --version
v8.9.4

Library versions:

  "dependencies": {
    "@types/big.js": "4.0.2",
    "big.js": "5.0.3",
    "ts-node": "4.1.0",
    "typescript": "2.6.2"
  }
invalid

Most helpful comment

Hello, please re-open this issue and remove the invalid label as ts-node is behaving differently than tsc and node for completely valid TypeScript code. Here is a minimal example to reproduce:

MyEnum.ts:

const enum MyEnum {
    A = "A",
    B = "B",
}
export default MyEnum;

index.ts:

import MyEnum from "./MyEnum";
console.log(MyEnum.A)

Expected behavior as evidenced by tsc/node:

$ node_modules/.bin/tsc index.ts MyEnum.ts
$ node index.js
A

Actual behavior of ts-node:

$ node_modules/.bin/ts-node index.ts

index.ts:2
console.log(MyEnum.A)
                   ^
TypeError: Cannot read property 'A' of undefined
    at Object.<anonymous> (index.ts:3:20)
    at Module._compile (module.js:660:30)
    at Module.m._compile (node_modules/ts-node/src/index.ts:400:23)
    at Module._extensions..js (module.js:671:10)
    at Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:403:12)
    at Module.load (module.js:573:32)
    at tryModuleLoad (module.js:513:12)
    at Function.Module._load (module.js:505:3)
    at Function.Module.runMain (module.js:701:10)
    at Object.<anonymous> (node_modules/ts-node/src/bin.ts:141:12)

Yes, it works correctly with the TS_NODE_TYPE_CHECK environment variable set to Y but this issue should still be fixed.

All 13 comments

I am also experiencing this problem. It occurs in both 3.3.0 and 4.1.0 when using TypeScript 2.5.3.

Running ts-node in type check mode should fix this problem:

```sh
$ TS_NODE_TYPE_CHECK=Y ts-node
````

Please note this has nothing to do with ts-node vs TypeScript compilations being different. This is just because runtime is failing (e.g. compilation works on both, but the code is wrong). That's because the .d.ts file is incorrect, .d.ts files don't interact with runtime so whatever is in there needs to match expectations - in this case, it does not, because that package doesn't actually have an enum in the JavaScript. Please file type errors with DefinitelyTyped.

I apologise actually, @localvoid is correct. It's likely because it needs the type information for inlining. I didn't realise TypeScript had changed this behaviour, I recall it was an issue long ago with the compiler.

Hello, please re-open this issue and remove the invalid label as ts-node is behaving differently than tsc and node for completely valid TypeScript code. Here is a minimal example to reproduce:

MyEnum.ts:

const enum MyEnum {
    A = "A",
    B = "B",
}
export default MyEnum;

index.ts:

import MyEnum from "./MyEnum";
console.log(MyEnum.A)

Expected behavior as evidenced by tsc/node:

$ node_modules/.bin/tsc index.ts MyEnum.ts
$ node index.js
A

Actual behavior of ts-node:

$ node_modules/.bin/ts-node index.ts

index.ts:2
console.log(MyEnum.A)
                   ^
TypeError: Cannot read property 'A' of undefined
    at Object.<anonymous> (index.ts:3:20)
    at Module._compile (module.js:660:30)
    at Module.m._compile (node_modules/ts-node/src/index.ts:400:23)
    at Module._extensions..js (module.js:671:10)
    at Object.require.extensions.(anonymous function) [as .ts] (node_modules/ts-node/src/index.ts:403:12)
    at Module.load (module.js:573:32)
    at tryModuleLoad (module.js:513:12)
    at Function.Module._load (module.js:505:3)
    at Function.Module.runMain (module.js:701:10)
    at Object.<anonymous> (node_modules/ts-node/src/bin.ts:141:12)

Yes, it works correctly with the TS_NODE_TYPE_CHECK environment variable set to Y but this issue should still be fixed.

How do you expect this issue to be fixed if the output is correct from typescript?

Encountered this as well. The behavior should be that if loading a .js file that is accompanied with a .d.ts file then it should type check. (in my case the declaration file is exported adjacent to the javascript.) Just setting the environment variable isn't enough because it means that if someone clones my project they can't get up and running without setting that.

Oddly... I ran my tests against pure typescript as well (no JS) and const enums still did not propagate correctly. I had to set "preserveConstEnums" to true.

This is a legitimate issue. The const enum itself being generated "correctly" doesn't mean anything-- the whole point is that the usage sites need to be found and substituted with the literal value of the enum member being referenced. The reliable way to do that is probably to just run a type checking step, which is too bad.

My particular case involved referencing a declare const enum from another library which set preserveConstEnums: false in its tsconfig, but I wouldn't be surprised if a const enum within the same project would fail at runtime if preserveConstEnums: false is set in tsconfig.json.

@RikkiGibson How is it a legitimate issue if it can not be solved and is not an issue with ts-node nor TypeScript? Just because it's an issue for you doesn't make it legitimate and the solution is simple - use type checking because enums require type checking to properly pull in the correct constants. You can't work around this.

I even made type checking mode the default again with the latest major release, and in previously releases this information was added to the README.

@electricessence How does someone run your project if it's expected to use ts-node already? There must be a way of communicating expectations of your project to others. You should consider putting the start command somewhere it can be reused such as NPM scripts and include --type-check (this is not needed for the latest release which is using --type-check mode by default again).

It sounds like you've basically done the right thing by moving to a safer default and documenting that a performance-optimizing flag doesn't work with a relatively obscure feature of the language :)

@RikkiGibson That's a good point, it should document if that option is disabled again (I removed the documentation when I changed the default). There are issues with the type checking mode by default though - for instance, the TypeScript compiler will load an entire project for something simple such as a Gulp task. This actually had run on issues such as breaking the official TypeScript build when the flag switched back.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

grissius picture grissius  路  3Comments

KiaraGrouwstra picture KiaraGrouwstra  路  3Comments

cibergarri picture cibergarri  路  3Comments

conordickinson picture conordickinson  路  4Comments

max-block picture max-block  路  4Comments