-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
isolatedModules doesn't respect enabled preserveConstEnum option what the project might be build with #37774
Comments
It seems like arguably |
@RyanCavanaugh Yeah, at least a consumer who uses TypeScript without using isolatedModules couldn't get that "enums" inlined 🙂 |
Any updates for this issue? CRA hard enforces the |
i have same issue |
I'm hitting this issue as well trying to enable the |
@RyanCavanaugh I see this is tagged with |
We’ve discussed this some in the past and I feel like I must be forgetting some of the complexities that came up. I just jotted down some straw-man proposals, and the simplest one feels fairly straightforward:
|
There are three issues with exported ambient const enums, I think:
These are the solutions as I see them:
Note: Existing
|
Turns off inlining | Preserves imports | Transforms enums -> .js |
|
---|---|---|---|
preserveConstEnums |
❌ | ❌ | ✔️ |
isolatedModules |
✔️ | ✔️ | ✔️ |
I.e. isolatedModules
implies preserveConstEnums
, !preserveConstEnums
implies !isolatedModules
.
I've been giving significant thought to injecting a custom transformer into the pipeline that converts this: // Current const enum
export const enum AstKind {
None = 'None',
Script = 'Script',
AndIf = 'AndIf',
Command = 'Command',
CompoundWord = 'CompoundWord',
VariableExpansion = 'VariableExpansion',
Text = 'Text'
} Into this: // Provide values at runtime, use an object literal to allow minifiers to drop unused values and/or inline (if in the same scope, at least)
export const AstKind = {
None: 'None',
Script: 'Script',
AndIf: 'AndIf',
Command: 'Command',
CompoundWord: 'CompoundWord',
VariableExpansion: 'VariableExpansion',
Text: 'Text'
} as const;
// Allow the values to be used in a type position
export declare namespace AstKind {
export type None = typeof AstKind.None;
export type Script = typeof AstKind.Script;
export type AndIf = typeof AstKind.AndIf;
export type Command = typeof AstKind.Command;
export type CompoundWord = typeof AstKind.CompoundWord;
export type VariableExpansion = typeof AstKind.VariableExpansion;
export type Text = typeof AstKind.Text;
}
// Allow for the enum itself ot be used in a type position
export type AstKind = typeof AstKind[keyof typeof AstKind]; This allows the following usage: import { AstKind } from './AstKind';
const x: AstKind.AndIf = AstKind.AndIf;
const y: AstKind = AstKind.Command; Also further enables the following, which you can't do with a const enum: import { type AstKind } from './AstKind';
// Type checks properly
const z: AstKind.Text = 'Text';
const z: AstKind = 'Text'; but still prevents, for example: const x: AstKind.AndIf = AstKind.Command; // Error, not assignable
const y: AstKind = 'Unknown'; // Error, not assignable The one restriction of current It is possible to restore the assignability restriction while still allowing the raw string literal assignment via something like: export declare const __enum__OtherEnum: unique symbol;
export const OtherEnum = {
Bar: 'None',
Baz: 'Script',
Foo: 'Test'
} as {
Bar: 'None' & { __enum?: typeof __enum__OtherEnum };
Baz: 'Script' & { __enum?: typeof __enum__OtherEnum };
Foo: 'Test' & { __enum?: typeof __enum__OtherEnum };
};
export declare namespace OtherEnum {
export type Bar = typeof OtherEnum.Bar;
export type Baz = typeof OtherEnum.Baz;
export type Foo = typeof OtherEnum.Foo;
}
export type OtherEnum = typeof OtherEnum[keyof typeof OtherEnum]; Though unfortunately that probably has deleterious effects on type-checking performance. It is also a lot less readable than an |
Maybe the keyword should be
That is exactly the meaning of In fact It would also simplify adoption. The message is: "search+replace And then later we could introduce |
We run into this error at work because we define all our enums as The crux of the issue, from what I can tell, is that TS treats project references as an "opaque boundary" and ignores the tsconfig settings that may be set. This is one of those "principle of least surprise" things I suppose - because there's no guarantee that a referenced project is built using the referenced tsconfig settings so in order to be least surprising (and cause fewer runtime crashes) it's better to assume the However this does mean that in codebases that are built using the same config/built tool setup there's no way to convince TS that the pattern is in fact completely safe. So you're left with a typecheck run that's littered with TS2748 errors. At Canva we work around this by setting I wonder if there's a simple solution here to allow a user to convince TS that accessing |
Project references are not always considered as an opaque boundary. Module resolution in referenced |
I wanted something like this in #51530 so that we could use const enums within TS but still refer to them by value if needed; my issue is probably a dupe of the above request. |
Search Terms
isolatedModules, preserveConstEnum, const enum
Suggestion
The compiler should respect/understand that the package/module was compiled with enabled
preserveConstEnums
compiler option.Use Cases
Let's say we have a project written in TypeScript, which uses const enums.
The project uses them because the main
const enum
's advantage is inlining values instead of runtime accessing to the value.In other hand the maintainers understand that const enums compiles in nothing in JS code, and that means that consumers can't use a const enums in their JS code (in some cases in TS code either, see above). Thus, they decided to enable
preserveConstEnums
compiler option to leave all const enums in JS code and allow consumers use them as well.Up to this moment everything is good. But the next who appears on the stage is
isolatedModules
(say hello tocreate-react-app
, webpack's transpile-only mode,transpileModule
compiler's API and so on). This compiler option is used to detect that imported module could be transpiler without compilation into JS code and everything will work well. One of checks for this option is that you don't import const enums (errorTS2748: Cannot access ambient const enums when the '--isolatedModules' flag is provided
).To summarize, if the project/package is compiled with enabled
preserveConstEnums
, inisolatedModules
mode the compiler shouldn't fail with error about unavailability of using const enums, because that's not actually true.Examples
Quite possible we need to provide that information to the compiler somehow (by providing tsconfig.export.json or in package.json for instance), but I don't even know how it would be done.
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: