Repository navigation
Allow ES Module Type-Only Imports from CJS Modules #49721
Description
Suggestion
🔍 Search Terms
es esm module cjs commonjs type import moduleResolution node16 nodenext
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
Allow CommonJS modules to import types from ES Modules without an async import() if import types is specified.
With moduleResolution: "node16" enabled, we now get this very helpful error message when importing an ES Module from a CommonJS module (thank you!):
// Error: Module 'my-es-module' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use dynamic import instead.
import MyModule from 'my-es-module';However, it is often the case that module will export a types API as well as a runtime API. Sometimes, we only want to import the types from a package. Typescript has the very helpful import type directive to acommodate this use case. However, if we want to import the types from an ES Module, we still get the following error:
// Error: Module 'my-es-module' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use dynamic import instead.
import type { MyTypeDefinition } from 'my-es-module';Because import types are stripped from runtime code, there is no need to throw this error.
📃 Motivating Example
Because this error is thrown, it forces us to add an unnecessary async import somewhere in our file, causing non-ergonomic development at best, and unnecessary runtime complexity at worst.
Consider:
// ES Module "my-es-module"
export type MyTemplateString = `${string}-custom-string`;function myFunction() {
const tmp = await import('my-ex-module');
const custom: tmp.MyTemplateString = 'contrived-custom-string';
return custom;
}It also (very unfortunately) makes it simply impossible to create and re-export derivative types from a module like this:
import type { MyTemplateString } from 'my-es-module';
export interface MyInterfaceDefinition {
type: MyTemplateString | null;
value: string;
}💻 Use Cases
See above motivating example. I'm currently using // @ts-expect-error to get past this, but would be great not to! This error should obviously still throw if importsNotUsedAsValues is set to preserve.
Activity
RyanCavanaugh commentedon Jun 30, 2022
Previously discussed at #47338 (comment) - this not working is intentional since it's a forward-compat hazard.
The proposed solution at the time was to add import mode assertions, but people didn't like this (see comments #48644) and we couldn't agree on a different syntax that was acceptable, so moved import mode assertions to be nightly-only. There's issue #49055 tracking next steps on this issue.
brillout commentedon Nov 13, 2022
Workaround: https://www.npmjs.com/package/@brillout/import.
slorber commentedon Nov 18, 2022
Not sure to understand why it doesn't, but for me just adding
// @ts-expect-erroris a good enough workaround to make my program typesafe without complicating things too much.slorber commentedon Apr 21, 2023
In my case (building the Docusaurus framework packages), using
// @ts-expect-erroris not a good solution when those types are re-exported.The emitted
.d.tsfiles contain the imports but// @ts-expect-erroris not in the.d.tsoutput of the lib.This makes the framework users have compilation errors with
skipLibCheck: false, such as:As far as I understand, Import attributes moved to stage 3 again with a new syntax (#53656)
See also #53426 (comment)
Do you think the following will fix the problem?
Can we expect it to be available soon Ryan Cavanaugh (@RyanCavanaugh) ?
Looks like you'd rather not rush things according to this comment: #53656 (comment)
Is there any alternative workaround to the above solution in the meantime?
I tried weird workarounds like this one
And of course it didn't work 😅 TS error + the type is generic
voxpelli commentedon Jun 3, 2023
Duplicate of #52529 which has a fix in #53426 (I know this one was created first, but the other one has more activity)
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
slorber commentedon Oct 4, 2023
Isn't TS 5.3 beta fixing this issue Andrew Branch (@andrewbranch) ?
andrewbranch commentedon Oct 4, 2023
Yep!
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings
fix: Use local file for oidc-provider typings