ts-node icon indicating copy to clipboard operation
ts-node copied to clipboard

Resolution prefers .json over .ts when extension omitted from import specifier

Open sjoerd222888 opened this issue 2 years ago • 2 comments

Search Terms

".default is not a constructor"

Expected Behavior

Can instantiate class instance even if there is a json file with the same name as the class

Actual Behavior

Cannot instantiate class instance if there is a json file with the same name as the class

    this.config = new Config();
                  ^
TypeError: Config_1.default is not a constructor

just because a file config.json is also present in the folder where the Config.ts class resides.

Steps to reproduce the problem

Create a typescript class and export module. Add a json file with the same name as the class. For example create a Config.ts class and add a Config.json file in the same folder.

But code runs fine is build with tsc and then run as JavaScript in node. Problem seems only to occur in ts-node.

Minimal reproduction

Source folder content:

Config.ts:

export default class Config {
  constructor() {}
  test: string = "Test";
}

ConfigurationManager.ts:

import Config from "./Config";

export default class ConfigurationManager {
  private config: Config;
  constructor() {
    this.config = new Config();
  }
  public getConfiguration(): Config {
    return this.config;
  }
}

app.ts:

import ConfigurationManager from "./ConfigurationManager";
const configuration = new ConfigurationManager();
console.log(configuration.getConfiguration().test);

config.json:

{}

Specifications

  • ts-node version: 10.9.1 (also tested 9.1.1)
  • node version: 16.14.0
  • TypeScript version: 4.7.4 (also tested 4.5.4)
  • tsconfig.json, if you're using one:
{
  "compilerOptions": {
      "forceConsistentCasingInFileNames": true,
      "module": "commonjs",
      "esModuleInterop": true,
      "outDir": "./build",
      "rootDir": "./source",
      "target": "es6",
      "skipLibCheck": true,
      "strict": true
  }
}
  • package.json:
{
	"name": "ts-node-issue",
	"version": "1.0.0",
	"description": "minimal-example",
	"scripts": {
		"start": "ts-node source/app.ts",
		"build": "tsc"
	},
	"keywords": [],
	"author": "sjoerd222888",
	"dependencies": {
		"ts-node": "^10.9.1",
		"typescript": "^4.7.4"
	},
	"devDependencies": {
		"@babel/preset-typescript": "^7.16.7"
	}
}
  • Operating system and version: macOS Monterey 12.5 & Windows 10
  • If Windows, are you using WSL or WSL2?: No

sjoerd222888 avatar Aug 22 '22 09:08 sjoerd222888

I suspect this is an issue with module resolution, and the part about classes is a red herring. "Module resolution" refers to the process where node sees import from "./Config"; and converts the string "./Config" into an absolute path on the filesystem.

Here is my guess:

When there are multiple files on the filesystem with the same name and different extensions:

  • foo.js
  • foo.json
  • foo.ts ...and when you omit the file extension from an import, node will load them in this order: .js first, then .json, then .ts.

If my hypothesis is correct, then you will not see the problem in ts-node with foo.js and foo.json, but you will see the problem with foo.ts and foo.json.

Are you able to simplify your reproduction to really narrow down and focus on this hypothesis to prove if it's correct or not? That will help figure out what to do next.

cspotcode avatar Aug 31 '22 19:08 cspotcode

This requires "allowJs": true in tsconfig.json and then this works, yes. I assume your hypothesis makes sense.

sjoerd222888 avatar Sep 05 '22 07:09 sjoerd222888