Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ES Module Type-Only Imports from CJS Modules #49721

Closed
5 tasks done
amiller-gh opened this issue Jun 29, 2022 · 7 comments
Closed
5 tasks done

Allow ES Module Type-Only Imports from CJS Modules #49721

amiller-gh opened this issue Jun 29, 2022 · 7 comments
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@amiller-gh
Copy link

amiller-gh commented Jun 29, 2022

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.

@RyanCavanaugh
Copy link
Member

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.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jun 30, 2022
@brillout
Copy link

@slorber
Copy link

slorber commented Nov 18, 2022

Not sure to understand why it doesn't, but for me just adding // @ts-expect-error is a good enough workaround to make my program typesafe without complicating things too much.

// @ts-expect-error
import type { ContainerDirective } from "mdast-util-directive";

@slorber
Copy link

slorber commented Apr 21, 2023

In my case (building the Docusaurus framework packages), using // @ts-expect-error is not a good solution when those types are re-exported.

The emitted .d.ts files contain the imports but // @ts-expect-error is not in the .d.ts output of the lib.

This makes the framework users have compilation errors with skipLibCheck: false, such as:

../packages/docusaurus-mdx-loader/lib/loader.d.ts:9:32 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("unified")' call instead.

9 import type { Pluggable } from 'unified';

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?

import type { Plugin } from 'unified' with { "resolution-mode": "import" };

Can we expect it to be available soon @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

// @ts-expect-error: :s
async function getUnifiedPluginHack() {
  const {unified} = await import('unified');
  type Attachers = ReturnType<typeof unified>['attachers'];
  type Attacher = Attachers[number];
  type PluginP = Attacher[0];
  return null as any as PluginP;
}
type Plugin = Awaited<ReturnType<typeof getUnifiedPluginHack>>;

And of course it didn't work πŸ˜… TS error + the type is generic

src/index.ts:15:16 - error TS2841: The type of this expression cannot be named without a 'resolution-mode' assertion, which is an unstable feature. Use nightly TypeScript to silence this error. Try updating with 'npm install -D typescript@next'.

15 async function getUnifiedPluginHack() {

@voxpelli
Copy link

voxpelli commented 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)

joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Jun 16, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Jun 16, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Jun 16, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
@slorber
Copy link

slorber commented Oct 4, 2023

Isn't TS 5.3 beta fixing this issue @andrewbranch ?

CleanShot 2023-10-04 at 14 58 42@2x

@andrewbranch
Copy link
Member

Yep!

joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Oct 5, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Oct 5, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Oct 5, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Oct 5, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Oct 5, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Oct 6, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
joachimvh added a commit to CommunitySolidServer/CommunitySolidServer that referenced this issue Oct 6, 2023
Due to v8 of oidc-provider being ESM,
we can't use the typings directly because of a TS bug:
microsoft/TypeScript#49721.
This works around that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants