Fraction.js icon indicating copy to clipboard operation
Fraction.js copied to clipboard

Constructor error (TS2351) in TypeScript when using ESM since v5

Open abouvier opened this issue 1 year ago • 7 comments

package.json

{
  "type": "module",
  "dependencies": {
    "fraction.js": "^5.1.0"
  },
  "devDependencies": {
    "typescript": "^5.6.3"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "module": "node16"
  }
}

test.ts

import Fraction from "fraction.js";

let x = new Fraction(1.88);

Compilation error:

$ npx tsc
test.ts:3:13 - error TS2351: This expression is not constructable.
  Type 'typeof import(".../node_modules/fraction.js/fraction")' has no construct signatures.

3 let x = new Fraction(1.88);
              ~~~~~~~~


Found 1 error in test.ts:3

It worked fine in v4, and it works again if "type": "module" is added back to node_modules/fraction.js/package.json.

abouvier avatar Nov 01 '24 06:11 abouvier

What is your current nodejs version? For me it worked providing just the TS definition file. From v4 to v5 the environment setup was changed to work on all major platforms. The "type": "module" is something that makes a module a 100% ESM module, which breaks all older installations. Does it work with a named export?

import {Fraction} from "fraction.js";

infusion avatar Nov 01 '24 07:11 infusion

What is your current nodejs version?

I tested with nodejs version 22.9.0 and 23.1.0.

Does it work with a named export?

Nope:

$ npx tsc
test.ts:1:9 - error TS2614: Module '"fraction.js"' has no exported member 'Fraction'. Did you mean to use 'import Fraction from "fraction.js"' instead?

1 import {Fraction} from "fraction.js";
          ~~~~~~~~


Found 1 error in test.ts:1

By removing default on the line export default class Fraction { in node_modules/fraction.js/fraction.d.ts then it works with import {Fraction} from "fraction.js";

abouvier avatar Nov 01 '24 07:11 abouvier

That's an interesting insight, thanks! I tested it again and again with nodejs from v16 to v23 and never had a problem. However, since your hint is very valuable, I updated the .d.ts file to support named and default export, same as the ESM export. Hope that also fixes your issue!

infusion avatar Nov 01 '24 17:11 infusion

That's an interesting insight, thanks! I tested it again and again with nodejs from v16 to v23 and never had a problem. However, since your hint is very valuable, I updated the .d.ts file to support named and default export, same as the ESM export. Hope that also fixes your issue!

Yes now import {Fraction} from "fraction.js"; is working, but the default still not.

It works though if fraction.d.ts is renamed to fraction.d.mts (and the name updated in package.json). As explained here, each file extension should reflect the type of module used in the file, because without "type": "module" in package.json then fraction.d.ts is considered a CommonJS file.

abouvier avatar Nov 02 '24 09:11 abouvier

Are the types wrong?

Explanation of the problem: https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md

abouvier avatar Apr 07 '25 06:04 abouvier

Thanks for the test tool. I couldn't grasp the problem of the issue yet, since I'm not a TS developer, but still want to maintain the highest compatibility. Is it generally possible to give TS the correct exports for CJS and MJS? "type": "module" is not possible, since it would break the CommonJS import. The solution in the JS world is to maintain the exports field in the package.json. After a lot of trouble this now works perfectly fine, the question is how the TS compiler can get the right definition for each regime.

infusion avatar Apr 07 '25 07:04 infusion

You probably need to maintain one type file for each engine, like in this library for example:

https://github.com/MikeMcl/bignumber.js/commit/a543ab5444c7dd6e7ea1759949a4f5ca74fe8f98 https://github.com/MikeMcl/bignumber.js/pull/371

abouvier avatar Apr 07 '25 14:04 abouvier

I did a larger refactoring and fixed this issue. I hope everything is noe working fine with v5.3

infusion avatar Aug 16 '25 12:08 infusion

Yes the import is working now, but shouldn't bigint be part of FractionInput in fraction.d.mts?

abouvier avatar Aug 16 '25 18:08 abouvier

Good point! I reworked the TS definition a bit and hope everything is covered now with 5.3.1.

infusion avatar Aug 17 '25 09:08 infusion