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

[Feature Request] Add Support for const enum in TS #8741

Closed
calebeby opened this issue Sep 20, 2018 · 45 comments · Fixed by #13324
Closed

[Feature Request] Add Support for const enum in TS #8741

calebeby opened this issue Sep 20, 2018 · 45 comments · Fixed by #13324
Labels
area: typescript i: enhancement outdated A closed issue/PR that is archived due to age. Recommended to make a new issue

Comments

@calebeby
Copy link
Contributor

calebeby commented Sep 20, 2018

Feature Request

In the docs, it says that:

Does not support const enums because those require type information to compile.

I don't believe that this is true. Babel should be able to transpile const enums into objects, which will achieve the same functionality as typescript, through different transpiled output.

Given this code:

const enum Direction { Left, Right, Down, Up }
console.log(Direction.Down)

tsc would compile this to:

console.log(2)

I propose that Babel compiles the enum to:

const Direction = {
  Left: 0,
  Right: 1,
  Down: 2,
  Up: 3
}
console.log(Direction.Down)

Which functions the same as the tsc output.

A minifier like terser/uglify is able to transpile that output ☝️ to the same as the tsc output.

@babel-bot
Copy link
Collaborator

Hey @calebeby! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community that typically always has someone willing to help. You can sign-up here
for an invite.

@TrySound
Copy link
Contributor

@calebeby This is true for cross module enums. There is not way to transpile imported enum into something other than identifier or type.

@calebeby
Copy link
Contributor Author

@TrySound If const enum's were transpiled into objects, they could be imported across modules, right? Then if you were using a bundler with scope hoisting, terser could still inline the values

@hzoo
Copy link
Member

hzoo commented Sep 20, 2018

This is similar to #6476 I believe? Also happy to take a PR for this. If we don't want to simply drop the const we could do an option.

@calebeby
Copy link
Contributor Author

calebeby commented Sep 20, 2018

@hzoo preserveConstEnums would treat const enums like normal enums, which is different from this proposal. I want const enums to be transpiled to plain objects, i.e.

export const E = {
  A: 1
};

instead of

export let E;

(function (E) {
  E[E["A"] = 1] = "A";
})(E || (E = {}));

because then the output is much less bloated (but it doesn't have the merging ability).

I would be happy to look into submitting a PR
I think that transpiling const enums into plain objects should be the default behavior

@Lodin
Copy link
Contributor

Lodin commented Sep 21, 2018

@calebeby I like to place const enum into the d.ts files because they leave nothing but a value itself. What would be the output for this case?

@calebeby
Copy link
Contributor Author

@Lodin it would output a plain object, which if you are using scope hoisting in your bundler, terser/uglify could inline the values

@0xdeafcafe
Copy link

Has any progress been made on this?

@calebeby
Copy link
Contributor Author

@0xdeafcafe I have not gotten time to work on this yet

@reverofevil
Copy link

The whole point of const enum is to not have any code generated at all. Correct solution is to replace Direction.Down by 2 whenever it's used as a value. Consider the following code:

import {Direction} from './somewhere';
if (a === Direction.Down) { ... }

You need another file access to somewhere to know what Direction is, how it was defined, and what value to assign to Direction.Down. There is no way to do it otherwise. Babel compiles on a file basis, and doesn't read somewhere while it compiles other files. Thus support for const enum was dropped.

While compiling somewhere it's still possible to generate an intermediate JSON file with a mapping like

{"somewhere#Direction.Down": 2, ...}

and then read it during compilation of dependent modules. There are still cyclic dependencies to be considered in such design (as when A imports a value from B, while B imports type from A), but it should be already pretty obvious why proposed solution is wrong, and that it's actually quite tedious and ugly task.

@calebeby
Copy link
Contributor Author

calebeby commented Feb 9, 2019

@polkovnikov-ph I will try to explain this again:

Input:

// a.ts

import {Direction} from './b'

if (a === Direction.Down) {}
// b.ts

export const enum Direction {
  Up,
  Down,
  Left,
  Right
}

Proposed babel output:

// a.ts

import {Direction} from './b'

if (a === Direction.Down) {}
// b.ts

export const Direction = {
  Up: 0,
  Down: 1,
  Left: 2,
  Right: 3
}

When compiling a.ts, Babel doesn't have to know about the other file, and it doesn't even have to know that Direction is an enum
Although this output is less ideal than the tsc output, it functions the same, and can become the same as the tsc output using the other tools.

Then, after a bundler like Rollup combines them to the same scope:

const Direction = {
  Up: 0,
  Down: 1,
  Left: 2,
  Right: 3
}

if (a === Direction.Down) {}

Then, once a minifier like Terser minifies it:

if (a === 1) {}

This way, the final output is the same as the tsc output.

The reason this doesn't work for normal enums is because normal enums are created with the ability to merge with other enums with the same name, which makes the code too complex to be hoisted/inlined.

    var Direction;
    (function (Direction) {
        Direction[Direction["Up"] = 0] = "Up";
        Direction[Direction["Down"] = 1] = "Down";
        Direction[Direction["Left"] = 2] = "Left";
        Direction[Direction["Right"] = 3] = "Right";
    })(Direction = exports.Direction || (exports.Direction = {}));

@reverofevil
Copy link

@calebeby I'll try to explain again. The whole point of const enum is to not generate any code. They are a feature of type system. Not with rollup. Not with uglify. Not with closure compiler in any of its modes. It doesn't matter the generated code is smaller. It should generate no code. It's an issue of those tools that they cannot figure out hoisting/inlining for regular enums, and should be fixed there.

@nicolo-ribaudo
Copy link
Member

If a const enum is used in the same file the transform can be smart enough to inline the enum values. We would only need to create the object for exported enums: it is definetly better than the current "support".

@nicolo-ribaudo nicolo-ribaudo added PR: New Feature 🚀 A type of pull request used for our changelog categories area: typescript i: enhancement and removed PR: New Feature 🚀 A type of pull request used for our changelog categories labels Feb 9, 2019
@calebeby
Copy link
Contributor Author

calebeby commented Feb 9, 2019

@polkovnikov-ph I agree with you that if babel could output const enums without any code, that would be ideal. But until babel supports multi-file compilation, I think this is the best we can do.

If I am a user that has a large TS codebase, with many const enums in it, and I am switching from tsc to babel, currently I have 1 option: Go through my codebase and change all the const enums to normal enums

In #6476, another option is proposed, preserveConstEnums, which would output const enums as if they were normal enums.

In both cases, the large non-minifyable enum output:

    var Direction;
    (function (Direction) {
        Direction[Direction["Up"] = 0] = "Up";
        Direction[Direction["Down"] = 1] = "Down";
        Direction[Direction["Left"] = 2] = "Left";
        Direction[Direction["Right"] = 3] = "Right";
    })(Direction = exports.Direction || (exports.Direction = {}));

What I am proposing is better than either of those options, because it outputs a minifyable object instead of a non-minifyable function. Although it isn't quite as good as tsc output, the output becomes the same once we use tools like rollup and terser.

Like @nicolo-ribaudo said, we can implement the inlining behavior in babel if the enum is not exported.

If enough people think that transpiling cross-file const enums to objects is undesirable/unexpected behavior, we could add an option that enables this behavior, and if the option is turned off it would give an error when export const enum is used.

@nicolo-ribaudo
Copy link
Member

How does TypeScript with the --isolatedModules option handle exported/imported const enums? Our goal is to mach the behavior of tsc with that option enabled.

@calebeby
Copy link
Contributor Author

calebeby commented Feb 9, 2019

@nicolo-ribaudo tsc outputs the large output when --isolatedModules is enabled.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction = exports.Direction || (exports.Direction = {}));

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Feb 9, 2019

Only for exported const enums?

@calebeby
Copy link
Contributor Author

calebeby commented Feb 9, 2019

Hmm, I tried the example from the docs and there is an error:

screenshot

@calebeby
Copy link
Contributor Author

calebeby commented Feb 9, 2019

It works fine with isolatedModules when I add export

@nicolo-ribaudo
Copy link
Member

@babel/typescript is it the expected behavior?

@jaredpalmer
Copy link

@calebeby I dig this proposal.

@Jessidhia
Copy link
Member

Jessidhia commented Mar 7, 2019

tsc will emit the full "fat enum" format, as const enum are still mergeable.

The importing file will just have its imports elided, just like type-only imports -- tsc will just leave dangling references to the "concrete" enum. This seems to be the case regardless of the --preserveConstEnums option.

Inputs:

/// a.ts
export const enum A {
  B
}
export const enum A {
  C = 2
}

/// b.ts
import { A } from './a'

console.log(A.B, A.C)

Outputs (--isolatedModules, --module esnext):

/// a.js
export var A;
(function (A) {
    A[A["B"] = 0] = "B";
})(A || (A = {}));
(function (A) {
    A[A["C"] = 2] = "C";
})(A || (A = {}));
//# sourceMappingURL=a.js.map

/// b.js
console.log(A.B, A.C);
//# sourceMappingURL=b.js.map

The tl;dr is that none of this makes any sense. Either typescript needs to find some way for const enum to be valid across module boundaries (e.g. the proposed statically analyzable object above, which would require forbidding declaration merging for const enums), or to ban exporting const enums at all.

The output for b.js is especially bizarre, considering typescript adopts the "unambiguous module" specification for itself -- its output is now in sloppy mode!

mnzaki added a commit to jolocom/smartwallet-app that referenced this issue Jun 18, 2019
- rm
  - android/app/src/main/res/mipmap-{m,h,x,xx}dpi/ic_launcher.png
  - ios/*tvOS*

- checkout from rn-0.59
  - android/gradle/wrapper/gradle-wrapper.properties
  - android/gradle/wrapper/gradle-wrapper.jar
  - android/app/BUCK
  - android/gradlew
  - android/gradlew.bat
  - android/app/proguard-rules.pro

- merge
  - android/app/build.gradle
    - versionCode, versionName
    - vectorDrawables.useSupportLibrary = true
    - signingConfigs
  - android/build.gradle
    - plugins { id 'base' }
  - android/settings.gradle
    - rootProject.name was left as JolocomWallet (instead of older
      'jolocomwallet')
  - android/app/src/main/AndroidManifest.xml
    - android:allowBackup="true"
    - android:launchMode="singleTop"
    - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
      <uses-permission android:name="android.permission.VIBRATE"/>
    - 'roundIcon' was unchanged, but we don't have round icons yet
    - update MainActivity
      - add 'android:exported="true"'
      - add intent-filter for consent, payment, authenticate screens
    - add SpashActivity
  - android/app/src/main/java/com/jolocomwallet/MainActivity.java
    - add SplashScreen code
  - android/app/src/main/java/com/jolocomwallet/MainApplication.java
    - where's SQLitePluginPackage() ??????
  - android/app/src/main/res/values/strings.xml
    - app_name changes to "Jolocom SmartWallet"
  - android/app/src/main/res/values/styles.xml
    - add SplashTheme
  - app.json
    - displayName and orientation
  - tsconfig.json
    - experimentalDecorators, emitDecoratorMetadata, sourceMap, noUnusedLocals,
      baseUrl, paths
  - .babelrc
    - removed, and moved some config to babel.config.js
    - add "proposal-decorators" and proposal-class-properties plugins
  - index.ts
    - reflect-metadata
  - metro.config.js
    - extraNodeModules settings for node polyfills
  - package.json
    - adjust startup scripts to use metro
    - remove all babel dev deps, add new babel deps
    - remove haul deps
    - remove 'rnpm' section
    - add/update deps from new project
      - react: 16.8.3,
      - react-native: 0.59.8
      - @babel/core: ^7.4.5
      - @babel/runtime: ^7.4.5
      - @types/jest: ^24.0.13
      - @types/react: ^16.8.19
      - @types/react-native: ^0.57.60
      - @types/react-test-renderer: ^16.8.1
      - babel-jest: ^24.8.0
      - jest: ^24.8.0
      - metro-react-native-babel-preset: ^0.54.1
      - react-test-renderer: 16.8.3
      - typescript: ^3.5.1
    - upgrade some of our old deps:
      - yarn
      - ts-jest
      - react-native-svg
      - react-native-sqlite-storage
      - react-native-splash-screen
      - react-native-camera
      - react-native-vector-icons
      - react-native-snap-carousel
    - remove some unneeded deps:
      - react-native-svg-uri
    - add some new deps
      - @babel/plugin-proposal-decorators
      - babel-plugin-transform-typescript-metadata
      - crypto-browserify (polyfill)
      - assert (polyfill)
      - stream-browserify (polyfill)
      - events (polyfill)
      - vm-browserify (polyfill)

- Issues
  - babel type error
    - weird "TypeError: t(...).isPlaceholder() is not a function"
    - worked after removing and reinstalling node_modules
  - dynamic require in src/locales/i18n.ts
    - can't use webpack style "dynamic" requires with variables
    - changed to manual require map
  - node libs polyfills (crypto, stream, assert, events)
    - using method from https://github.com/philikon/ReactNativify
      but just through babel-plugin-rewrite-require and aliases crypto to crypto-browserify
    - node_modules/web3-eth-accounts/node_modules/eth-lib/lib/bytes.js
      - if (typeof require !== "undefined") rnd = require("c" + "rypto") <-- WTF
      - it's probably trying to make sure require is being used from node or
        something, but in any case "babel-plugin-rewrite-require" does not like
        this
    - found some builtin support in metro for mapping modules in a way
      https://facebook.github.io/metro/docs/en/configuration#extranodemodules
    - add 'process.version = ...' in index.ts
  - const enums are not supported in @babel/typescript
    - it's complicated babel/babel#8741
    - removed 'const' from enums in:
      - src/lib/errors.ts
  - circular import in storage/entities/index.ts
    - fixed
  - decorators were not working correctly due to some babel7+typescript mess
    - typeorm decorators were breaking on "@context", because of a bug in
      @babel/plugin-proposal-decorators
lucaswerkmeister added a commit to wmde/WikibaseDataModelTypes that referenced this issue Jun 5, 2020
We don’t want to use const enums, for two reasons:

1. It seems to be generally discouraged to share const enums across
   module boundaries; they are apparently mostly intended as an internal
   mechanism. (I have yet to find any documentation that tells you this,
   but [1] seems to imply we’re supposed to understand this naturally.)

2. Babel’s TypeScript transform plugin does not support them [2]. There
   is a plugin [3] to transform const enums into non-const ones, but it
   seems to be intended for your own code, not for dependencies. This
   means that Data Bridge can’t use this module as long as it uses const
   enums, because they will not be inlined at compile time, but rather
   be sought for at run time, when they don’t exist.

With type aliases for union types, we lose the possibility for
declaration merging, but it’s unclear how significant this actually was.
If we (the Wikidata team) need more data (value) types, it’ll make more
sense to add them upstream (here) anyways, rather than augmenting the
declarations in our downstream projects.

(Side note: the augmentation syntax in the README.md was incorrect
anyways – a namespace name must be an identifier, not a string literal.
An ambient module would have been closer to the real syntax.)

[1]: microsoft/TypeScript#37179 (comment)
[2]: babel/babel#8741
[3]: https://github.com/dosentmatter/babel-plugin-const-enum
lucaswerkmeister added a commit to wmde/WikibaseDataModelTypes that referenced this issue Jun 5, 2020
We don’t want to use const enums, for two reasons:

1. It seems to be generally discouraged to share const enums across
   module boundaries; they are apparently mostly intended as an internal
   mechanism. (I have yet to find any documentation that tells you this,
   but [1] seems to imply we’re supposed to understand this naturally.)

2. Babel’s TypeScript transform plugin does not support them [2]. There
   is a plugin [3] to transform const enums into non-const ones, but it
   seems to be intended for your own code, not for dependencies. This
   means that Data Bridge can’t use this module as long as it uses const
   enums, because they will not be inlined at compile time, but rather
   be sought for at run time, when they don’t exist.

With type aliases for union types, we lose the possibility for
declaration merging, but it’s unclear how significant this actually was.
If we (the Wikidata team) need more data (value) types, it’ll make more
sense to add them upstream (here) anyways, rather than augmenting the
declarations in our downstream projects.

(Side note: the augmentation syntax in the README.md was incorrect
anyways – a namespace name must be an identifier, not a string literal.
An ambient module would have been closer to the real syntax.)

[1]: microsoft/TypeScript#37179 (comment)
[2]: babel/babel#8741
[3]: https://github.com/dosentmatter/babel-plugin-const-enum
lucaswerkmeister added a commit to wmde/WikibaseDataModelTypes that referenced this issue Jun 8, 2020
We don’t want to use const enums, for two reasons:

1. It seems to be generally discouraged to share const enums across
   module boundaries; they are apparently mostly intended as an internal
   mechanism. (I have yet to find any documentation that tells you this,
   but [1] seems to imply we’re supposed to understand this naturally.)

2. Babel’s TypeScript transform plugin does not support them [2]. There
   is a plugin [3] to transform const enums into non-const ones, but it
   seems to be intended for your own code, not for dependencies. This
   means that Data Bridge can’t use this module as long as it uses const
   enums, because they will not be inlined at compile time, but rather
   be sought for at run time, when they don’t exist.

With type aliases for union types, we lose the possibility for
declaration merging, but it’s unclear how significant this actually was.
If we (the Wikidata team) need more data (value) types, it’ll make more
sense to add them upstream (here) anyways, rather than augmenting the
declarations in our downstream projects.

(Side note: the augmentation syntax in the README.md was incorrect
anyways – a namespace name must be an identifier, not a string literal.
An ambient module would have been closer to the real syntax.)

[1]: microsoft/TypeScript#37179 (comment)
[2]: babel/babel#8741
[3]: https://github.com/dosentmatter/babel-plugin-const-enum
@sushruth
Copy link

One good case for this would be action type names as const enums within a react project which also uses redux - ability to have a HUGE action names enum with essentially zero cost to output bundle is a great benefit.

@snathanail
Copy link

Hi guys. I need some help to understand the current situation here please.
This is a very simplified view of my project:

/******* Module A: Typings *******/

// index.ts -> Typedefs
export const enum MyEnum {
  Foo,
  Bar,
  Baz,
}

// index.js -> Transpiled
...
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   MyEnum() => (/* binding */ MyEnum),
/* harmony export */ });
const MyEnum = {
  Foo: 0,
  Bar: 1,
  Baz: 2,
};
...

Packaged, published. I've kept index.ts in the package, to use for types instead of a .d.ts file, since I understand Babel doesn't play well with those. I've also set "main" and "types" in the package.json file, to point to the relevant files (index.js and index.ts)

/******* Module B: The code where I want to use the above enum *******/

// test-import.ts
import { MyEnum } from 'my-published-package'

const TestDefinition = {
  name: 'John Doe',
  type: MyEnum.Foo
}

// test-import.js (with TSC) -- I kept this just for comparison purposes
const TestDefinition = {
  name: 'John Doe',
  type: 0 // Good!
} 

// index.js (with Babel)
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _my_published_package__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! my-published-package */ "my-published-package");
const TestDefinition = {
  name: 'John Doe',
  type: _my_published_package__WEBPACK_IMPORTED_MODULE_0__.MyEnum.Foo,
}

The above fails on runtime with TypeError: Cannot read property 'Foo' of undefined.

Can someone please explain to me why? I've spent two days on the above with no real progress, gone over dozens of SO questions, made over 10 iterations of my library using various TSC compiler options and Babel plugins (such as babel-plugin-const-enum), but this Will.Not.Work. for any reason. It keeps getting transpiled into _my_published_package__WEBPACK_IMPORTED_MODULE_0__.MyEnum.Foo instead of 0, and subsequently fails in runtime.

What am I doing wrong here? I just need to have a common library enum to use across various modules in my project, am I going about it the wrong way?

Thank you very much in advance.

@reverofevil
Copy link

@snathanail const enum is not supported by babel. This means that you cannot use const enum with babel. Yes, you're going about it the wrong way, because you're literally asking what you can do to make it work in the issue that tells it doesn't.

The underlying issue is that const enums require compilation process to span over a set of files, while babel's architecture supports compilation of one file at a time only. Rewrite of Babel to support this feature is nowhere on a roadmap. It's (in theory) possible to implement the feature through a combination of babel transform and webpack plugin. I've never seen one in the wild though.

The bitter part in all of this is that there are thousands of programmers who spend their "two days" to switch options on and off, while it would take less time to implement the missing feature. "Tragedy of the commons" unfolds slowly, so be sure to have a nice observation point by the time it will.

@snathanail
Copy link

Understood, thank you very much! I had some hope with babel-plugin-const-enum but alas.

It's a pity because I would expect that it's a pretty common scenario.

For what it's worth, I ended up dropping babel-loader in my webpack config and going with ts-loader -- since my project is server-side, I don't think I needed Babel to begin with. 😅

PS. I just want to take a moment to address the last part: I hear you, and agree completely, and, believe it or not, 20-year-old me would be jumping for the opportunity to add to the source code.
I'm only going to say that ...I'm not 20 any more, haven't been for a long time, and I really wish I had some free time to work on exciting side projects like this. Don't want to sound apologetic or anything like that, don't feel I need to anyway; just wanted to set the record straight.
🙂

Thanks again for your time and help!

@nicolo-ribaudo
Copy link
Member

I opened #13324 to try adding const enum support. Opinions are welcome!

@dosentmatter
Copy link
Contributor

dosentmatter commented May 18, 2021

@snathanail I am the author of babel-plugin-const-enum and babel-preset-const-enum. I assume you are using one of these plugins in your example code? I expect your use case to work, though I haven't done extensive testing with importing enums between packages.

  • What happens if you try importing from a raw node js .js file?
  • What happens if you change the const enum to a manually typed out constObject?
  • Does removeConst plugin option work?
  • Does the import work within the same package?
  • I'm wondering why I see no export const enum in your transpiled package output. If you look further down, is it eventually exported?

A possible cause of your error could be if the constObject isn't actually exported. TSC import would still work because it only grabs information from the types file .d.ts (.ts in your case), so it doesn't matter if the constObject is broken or non-existent for TSC. The reason TSC only grabs information from types is that const enum are just types and produces no transpiled JS output (non-existent constObject).

babel on the other hand doesn't support const enum so it requires my const-enum plugin/preset to convert it to a JS constObject so it can be imported in JS (and eventually inlined by webpack terser). NodeJS doesn't support const enum either, so you should be able to test your package from there. Your constObject looks correct. Make sure it is exported and available to be imported though.

@dosentmatter
Copy link
Contributor

dosentmatter commented May 18, 2021

@polkovnikov-ph

It's (in theory) possible to implement the feature through a combination of babel transform and webpack plugin. I've never seen one in the wild though.

babel-plugin-const-enum and babel-preset-const-enum are meant to be used with webpack terser plugin for inlining. Is this the type of babel transform you are referring to, or are you looking for something else? I actually made a post about it earlier in this thread, but it got buried by comments. It was also added to the babel -preset-typescript docs:

  1. This plugin does not support const enums because those require type information to compile.
    Workarounds:
    • Use the plugin babel-plugin-const-enum.
    • Remove the const, which makes it available at runtime.

@snathanail
Copy link

snathanail commented May 18, 2021

@snathanail I am the author of babel-plugin-const-enum and babel-preset-const-enum. I assume you are using one of these plugins in your example code? I expect your use case to work, though I haven't done extensive testing with importing enums between packages.

I am. I tried both actually (not together). Thank you for devoting time to create this, by the way. 🙂

  • What happens if you try importing from a raw node js .js file?

import { MyEnum } from 'my-published-package/index.js'

I've removed the .ts file and the "types" attribute in package.json. Same behavior, except now I'm getting the warning that there is no declaration file (as expected).

  • What happens if you change the const enum to a manually typed out constObject?

I lose the ability to use the enum as a type for another variable, which is what I wanted in the first place. Furthermore, the transpiler output for the library (using the constObject option) is identical (i.e. a const object) . As expected, the behavior of the main app is the same.

  • Does removeConst plugin option work?

Using the removeConst options I get this output from transpiling the library:


(function (MyEnum) {
  MyEnum[MyEnum["Foo"] = 0] = "Foo";
  MyEnum[MyEnum["Bar"] = 1] = "Bar";
  MyEnum[MyEnum["Baz"] = 2] = "Baz";
})(MyEnum || (MyEnum = {}));

Again, as expected. Transpiling the main app gives the same output/behavior as before. Note, I've tried both removeConst and constObject in the main app.

  • Does the import work within the same package?

It does! Interestingly, it transpiles into the very same code (other than the different path for the __webpack_require()). Even with constObject.

  • I'm wondering why I see no export const enum in your transpiled package output. If you look further down, is it eventually exported?

It is, I think. This is the full webpack output:

/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	// The require scope
/******/ 	var __webpack_require__ = {};
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/make namespace object */
/******/ 	(() => {
/******/ 		// define __esModule on exports
/******/ 		__webpack_require__.r = (exports) => {
/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 			}
/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
/******/ 		};
/******/ 	})();
/******/ 	
/************************************************************************/

var __webpack_exports__ = {};

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "MyEnum": () => (/* binding */ MyEnum)
/* harmony export */ });

const MyEnum = {
  Foo: 0,
  Bar: 1,
  Baz: 2
};

Note, we're getting into webpack internals now, so I'm not 100% sure how this translates to exporting the enum, but from a bit of reverse engineering I guess Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); does the trick -- so we should have an object exports with a property MyEnum and a value of the const object.

From all of the above I think I agree with you that there is an issue with importing the const object via webpack. I don't think this has strictly to do with enums.

@dosentmatter
Copy link
Contributor

dosentmatter commented May 18, 2021

@snathanail Thanks for the detailed comment. We are straying away from OP's topic and I don't want to trigger notifications to subscribers of this issue any further.

  • If you think this is an issue with one of my plugins, please create a minimally reproducible example (published package + simple repo importing) and file an issue in the babel-plugin-const-enum GitHub repo.
  • If you think it is just a support issue, you can create a post on Stack Overflow.

You can edit your post to link the new post and I can take a look if it's related to my plugin.


Your transpiled output doesn't look like the full output. I tested it out locally by appending }() at the end. I'm not sure if it has any other output that was cropped off. Anyway I followed the webpack code, and it looks like your transpilation output only Object.defineProperty on __webpack_exports__ and not module.exports. I get an empty object {} when require()ing from a nodejs repl. Maybe you should check the environments in your build target configuration. I don't know how __webpack_exports__ is supposed to work. Some people have issues with it:

https://stackoverflow.com/q/48921170/7381355
https://stackoverflow.com/q/64883537/7381355

A few more things to try:

  • See if require() from raw node js ie. no webpack works.
  • See if default export works.

@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Nov 3, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: typescript i: enhancement outdated A closed issue/PR that is archived due to age. Recommended to make a new issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.