TypeScript
TypeScript copied to clipboard
Inconsistent typechecking with require() in JS and TS
🔎 Search Terms
require import js ts module esm esmodule cjs commonjs
🕗 Version & Regression Information
- This happens in the nightly version of TS
⏯ Playground Link
Multiple files not supported in playground, see bug workbench
💻 Code
// @types: ["node"]
// @allowJs
// @checkJs
// @filename: module-cjs-js.js
const Value = "module-cjs-js";
module.exports = { Value };
// @filename: module-cjs-ts.ts
const Value = "module-cjs-ts";
module.exports = { Value };
// @filename: module-esm-js.js
const Value = "module-esm-js";
export { Value };
// @filename: module-esm-ts.ts
const Value = "module-esm-ts";
export { Value };
// @filename: main-js.js
const ConstRequireCjsJs = require("./module-cjs-js");
const ConstRequireEsmJs = require("./module-esm-js");
const ConstRequireCjsTs = require("./module-cjs-ts");
const ConstRequireEsmTs = require("./module-esm-ts");
console.log(ConstRequireCjsJs.Value); // (alias) const Value: "module-cjs-js"
// ^?
console.log(ConstRequireEsmJs.Value); // (alias) const Value: "module-esm-js"
// ^?
console.log(ConstRequireCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
// ^?
console.log(ConstRequireEsmTs.Value); // (alias) const Value: "module-esm-ts"
// ^?
import * as ImportFromCjsJs from "./module-cjs-js";
import * as ImportFromEsmJs from "./module-esm-js";
import * as ImportFromCjsTs from "./module-cjs-ts";
import * as ImportFromEsmTs from "./module-esm-ts";
console.log(ImportFromCjsJs.Value); // (alias) const Value: "module-cjs-js"
// ^?
console.log(ImportFromEsmJs.Value); // (alias) const Value: "module-esm-js"
// ^?
console.log(ImportFromCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
// ^?
console.log(ImportFromEsmTs.Value); // (alias) const Value: "module-esm-ts"
// ^?
// @filename: main-ts.ts
const ConstRequireCjsJs = require("./module-cjs-js");
const ConstRequireEsmJs = require("./module-esm-js");
const ConstRequireCjsTs = require("./module-cjs-ts");
const ConstRequireEsmTs = require("./module-esm-ts");
console.log(ConstRequireCjsJs.Value); // any
// ^?
console.log(ConstRequireEsmJs.Value); // any
// ^?
console.log(ConstRequireCjsTs.Value); // any
// ^?
console.log(ConstRequireEsmTs.Value); // any
// ^?
import * as ImportFromCjsJs from "./module-cjs-js";
import * as ImportFromEsmJs from "./module-esm-js";
import * as ImportFromCjsTs from "./module-cjs-ts";
import * as ImportFromEsmTs from "./module-esm-ts";
console.log(ImportFromCjsJs.Value); // (alias) const Value: "module-cjs-js"
// ^?
console.log(ImportFromEsmJs.Value); // (alias) const Value: "module-esm-js"
// ^?
console.log(ImportFromCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
// ^?
console.log(ImportFromEsmTs.Value); // (alias) const Value: "module-esm-ts"
// ^?
import ImportRequireCjsJs = require("./module-cjs-js");
import ImportRequireEsmJs = require("./module-esm-js");
import ImportRequireCjsTs = require("./module-cjs-ts");
import ImportRequireEsmTs = require("./module-esm-ts");
console.log(ImportRequireCjsJs.Value); // (alias) const Value: "module-cjs-js"
// ^?
console.log(ImportRequireEsmJs.Value); // (alias) const Value: "module-esm-js"
// ^?
console.log(ImportRequireCjsTs.Value); // Error: Property 'Value' does not exist on type 'typeof import("./module-cjs-ts")'
// ^?
console.log(ImportRequireEsmTs.Value); // (alias) const Value: "module-esm-ts"
// ^?
🙁 Actual behavior
Type resolution is inconsistent when using require() from .js and .ts files:
- CommonJS modules with .ts extension have no properties regardless of import type (no error if ESModule.ts or CommonJS.js)
- Using
require()in a .ts file is always unchecked (checked in a .js file or in .ts file withimport X =syntax)
| MainExt | Type | Ext | RequireOrImport | Issue |
|---|---|---|---|---|
| JS | CJS | JS | const X = require("Y") | |
| JS | ESM | JS | const X = require("Y") | |
| JS | CJS | TS | const X = require("Y") | Error |
| JS | ESM | TS | const X = require("Y") | |
| JS | CJS | JS | import * as X from "Y" | |
| JS | ESM | JS | import * as X from "Y" | |
| JS | CJS | TS | import * as X from "Y" | Error |
| JS | ESM | TS | import * as X from "Y" | |
| JS | CJS | JS | const X = require("Y") | |
| JS | ESM | JS | const X = require("Y") | |
| JS | CJS | TS | const X = require("Y") | Error |
| JS | ESM | TS | const X = require("Y") | |
| TS | CJS | JS | const X = require("Y") | Any |
| TS | ESM | JS | const X = require("Y") | Any |
| TS | CJS | TS | const X = require("Y") | Any |
| TS | ESM | TS | const X = require("Y") | Any |
| TS | CJS | JS | import * as X from "Y" | |
| TS | ESM | JS | import * as X from "Y" | |
| TS | CJS | TS | import * as X from "Y" | Error |
| TS | ESM | TS | import * as X from "Y" | |
| TS | CJS | JS | import X = require("Y") | |
| TS | ESM | JS | import X = require("Y") | |
| TS | CJS | TS | import X = require("Y") | Error |
| TS | ESM | TS | import X = require("Y") |
🙂 Expected behavior
I expected require() to be typechecked in .ts files because it's typechecked in .js files.
I expected imports of CommonJS .ts files to work because CommonJS .js files work and are typechecked.
Additional information about the issue
This problem happens when porting an existing Node.JS codebase that uses CommonJS require() modules to TypeScript. It's not possible to port the code without also forcing it into ES Modules because:
- CommonJS .ts files don't work
- require() from .ts files is not typechecked
Have you tried changing your tsconfig.json settings and using esModuleInterop?"
I expected require() to be typechecked in .ts files because it's typechecked in .js files.
Only import i = require( is supported in TS
I expected imports of CommonJS .ts files to work because CommonJS .js files work and are typechecked.
What specifically is the repro for this?
Bug workbench doesn't seem to work with types: ["node"], but you can repro it like this:
// @types: ["node"]
// @filename: test-cjs.ts
module.exports = { Value: 1 };
// @filename: main.ts
import test = require("./test-cjs");
test.Value; // Error: Property 'Value' does not exist on type 'typeof import("./test-cjs")'
// ^? any
If the module has a JS extension, then the import works. But if the module has a TS extension, it no longer works.
Only
import i = require(is supported in TS
Is it possible to add support for const i = require( as well?
It seems strange because the functionality is there, the const require works and gets typechecked, but only with a JS extension, why not have it work if the file extension is TS as well? It would make the developer experience much better, and const require is much more flexible than import require:
// Can't do this with import X = require()
let X;
if (cond) {
X = require("x-impl-1");
}
else {
X = require("x-impl-2");
}
:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of the repro in the issue body running against the nightly TypeScript.
Issue body code block by @u130b8
:bangbang: Exception: Error - error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error. Visit https://aka.ms/ts6 for migration information.
Error: error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
Visit https://aka.ms/ts6 for migration information.
at Object.createVirtualTypeScriptEnvironment (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:8128:11)
at twoslasher (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:7618:17)
at /home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:439:44
at runTwoslashRequests (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:406:56)
at run (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:20096:75)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Historical Information
| Version | Reproduction Outputs | Time |
|---|---|---|
| 5.2.2, 5.3.2, 5.4.2, 5.5.2, 5.6.2 |
:x: Failed: -
|
⚠️ Way slower |
:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of this repro running against the nightly TypeScript.
Comment by @u130b8
:bangbang: Exception: Error - error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error. Visit https://aka.ms/ts6 for migration information.
Error: error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
Visit https://aka.ms/ts6 for migration information.
at Object.createVirtualTypeScriptEnvironment (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:8128:11)
at twoslasher (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:7618:17)
at /home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:439:44
at runTwoslashRequests (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:406:56)
at run (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:20096:75)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Historical Information
| Version | Reproduction Outputs | Time |
|---|---|---|
| 5.2.2, 5.3.2, 5.4.2, 5.5.2, 5.6.2 |
:x: Failed: -
|
⚠️ Way slower |