Ability to declare and define enum on the global scope
Closed this issue ยท 10 comments
TypeScript Version: 2.2.2
The main problem this suggestion solves, is it to be able to create an enum (in a .ts
file)
which can be referred by a .d.ts
file.
In projects which use a module loader (require.js for instance) it's common to have 0 import statements for local files (non external modules), this conflicts with enum definition and declaration.
Currently, when defining an enum, one must import it on every .ts
file that uses this enum,
furthermore, at the .d.ts
level one can't refer this enum without re-declaring it and all of its members, which are never validated as being the same as in the enum definition.
Code
MyEnum.ts
global enum MyEnum {
MyEnumMember1,
MyEnumMember2,
MyEnumMember3
}
Expected behavior:
Compiler will assume MyEnum is available at runtime from the global scope in
addition to compiling the enum to the MyEnum.js file.
Actual behavior:
On .ts
files:
Unless imported explicitly, usages of MyEnum leads to a compilation error
On .d.ts
files:
Declaration files can't declare types which refers to this enum.
Unless imported explicitly, usages of MyEnum leads to a compilation error
If you are in a module, use
declare global {
enum MyEnum {
MyEnumMember1,
MyEnumMember2,
MyEnumMember3
}
}
Declaration files can't declare types which refers to this enum.
Oh but they can. I assume you received the error MyEnum has members with initializers that are not literals
That means that in order to be encoded in a .d.ts
file, something which must stand in the absence of an implementation to specify the values, you need to state these values explicitely as they define the enum members.
enum MyEnum {
MyEnumMember1 = 1,
MyEnumMember2 = 2,
MyEnumMember3 = 3
}
If you are in a module, use
declare global {
enum MyEnum {
MyEnumMember1,
MyEnumMember2,
MyEnumMember3
}
}
This only creates the declaration but not the definition, therefore I have to create another file which creates the enum and assigns it on the global scope, the problem here is that the compiler doesn't offer any feature that validates the actual enum definition with its declaration, meaning I can declare and define different enum member or order and still successfully compile my code.
I assume you received the error MyEnum has members with initializers that are not literals
No, this is not what I get, I get Cannot fine name 'MyEnum'
Here is an example:
MyEnum.ts:
declare global {
enum MyEnum {
MyEnumMember1,
MyEnumMember2,
MyEnumMember3
}
}
MyDeclarationFIle.d.ts
declare interface IMyFactory {
MyObject create(MyEnum enum);
}
This doesn't compile since MyEnum isn't declared globally, and again If I will declare it globally, it duplicates with the definition and there's no way I can enforce the compiler to match the definition and declaration.
You are right, I misunderstood what you were having difficulty with.
What you can do is create a .ts
file, instead of a .d.ts
file.
This file must not be a module.
globals.ts
enum MyEnum {
MyEnumMember1,
MyEnumMember2,
MyEnumMember3
}
this will both declare and define the enum
. TypeScript will recognize this as a global because there are no top level import
or export
clauses in the file.
If you have trouble with your loader or bundler, for example if it wraps the enum
and thereby prevents it from being attached to the global object, you can add this line
(window as { MyEnum? }).MyEnum = MyEnum;
or something similar to explicitly attach it.
This did the job, thank you very much, I appreciate your help.
My final code version is:
MyEnum.ts
enum MyEnum {
MyEnumMember1,
MyEnumMember2,
MyEnumMember3
}
(window as { MyEnum? }).MyEnum = MyEnum;
My loader framework
require('./MyEnum.js');
RandomFile.ts
const enumMember = MyEnum.MyEnumMember1;
This compiled and ran successfully.
I'd still suggest a more elegant way to do this, since I can see how this can be commonly used by many developers, but since it solves my problem I'll close the issue.
I believe this should be a built-in feature rather than a hackish solution.
@jasonslyvia you mean an elegant way to get at the global scope and mutate it?
There are so many runtime issues for determining what the global scope actually is. For those running in a browser window
is fine, but getting true isomorphic code is far more complex (web workers and NodeJS are two major challenges). There is a TC39 proposal for global
but that is still in the pipeline of being approved. Because of those run-time challenges of actually dealing with that, the ability to model what is in the global namespace is what TypeScript concerns itself with, which is sufficient as it stands.
Also, it is generally considered anti-pattern to pollute the global namespace. Putting random enums out there is really bad practice. I don't think having TypeScript make it easier to pollute the global namespace will do anybody any favours. If you are reflecting the bad behaviours of other libraries, TypeScript already has that capability.
What would be the right way to define a global type that has a property of an enum, then access that enum across the rest of your project? I can't seem to grok why my type declarations' declare enum
blocks are unreachable from inside my project.
If I declare the enum in a module, then I can't import it without breaking the type definitions' no-module-rule. Feels like a catch-22 to me.
It seems like an extremely common case that you would want to use your enums in your d.ts file and also elsewhere in your code.
If you really want a global, you can always make one.
@mmmeff if you are in a module, you can use declare global
to both define the availability of the enum
at the type level and also make the assignment that provides the value typecheck.
// define-my-enum.ts
export {}
// this is a module.
enum myEnum {
A,
B,
C
}
window.myEnum = myEnum;
declare global {
interface Window {
myEnum: typeof myEnum;
}
}
Then any consuming code simply needs to be in a compilation context that references the module. For example:
// main.ts
import './define-my-enum';
export default 42;
// app.ts
Object.entries(window.myEnum).forEach(console.log); // not an error