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 type
s 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
.