23

Consider:

enum Colors {
    Red,
    Green,
    Blue
}

It transpiles into this:

var Colors;
(function (Colors) {
    Colors[Colors["Red"] = 0] = "Red";
    Colors[Colors["Green"] = 1] = "Green";
    Colors[Colors["Blue"] = 2] = "Blue";
})(Colors || (Colors = {}));

Most questions about this result are answered in Enums in TypeScript: what is the JavaScript code doing?.

I quote the answers:

This is an 'immediately executing function'

And further down the topic:

think they could probably just go:

var Colors;
Colors || (Colors = {});
Colors[Colors["Cyan"] = 3] = "Cyan";
// ...

and skip the closure, but maybe I’m still missing something.

So the question remains: why wrapping this in an immediately executing function?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Florimond
  • 341
  • 3
  • 7
  • 3
    There is a very detailed explanation here: https://basarat.gitbooks.io/typescript/content/docs/enums.html – DSCH Nov 18 '17 at 08:20
  • @DSCH Your link does not address the question of why the closure is needed. – Derek 朕會功夫 Nov 18 '17 at 08:28
  • The most probable explanation is that it's just a matter of personal preference of whoever wrote that part of the transpiler. – JJJ Nov 18 '17 at 08:30
  • My guess is that it's due to the modularity of the transpiler. It probably uses IIFEs in general to hide temporary variables, but in this case there aren't any, so the IIFE is redundant. – Barmar Nov 18 '17 at 08:33
  • seems like multiple enums with the same name are allowed .. ha. I am more curious why the values are not inlined .. I guess to preserve the information for anyone reading/debugging the JavaScript result – Slai Nov 18 '17 at 14:13
  • @Slai The values *are* inlined if you declare it with `const enum Name`, rather than just `enum Name`. What's more, the declaration of the enum is completely wiped out so that only the inlined values remain in the code, unless a flag is passed to the compiler. – daemone Nov 19 '17 at 09:21

2 Answers2

13

I believe TypeScript uses IIFE (aka immediately-invoked function expression) because the variable name can be minified. If you have really long enum name, e.g. MySuperAmazingStatesNeededForWhateverIWantEnum, it can be minified inside an IIFE to just one letter

// amazing_enum.js
var MySuperAmazingStatesNeededForWhateverIWantEnum;
(function MySuperAmazingStatesNeededForWhateverIWantEnum() {
    MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum["Red"] = 0] = "Red";
    MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum["Green"] = 1] = "Green";
    MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum["Blue"] = 2] = "Blue";
})(MySuperAmazingStatesNeededForWhateverIWantEnum || (MySuperAmazingStatesNeededForWhateverIWantEnum = {}));

// amazing_enum.min.js
var MySuperAmazingStatesNeededForWhateverIWantEnum;!function e(){e[e.t=0]="Red",e[e.u=1]="Green",e[e.m=2]="Blue"}(MySuperAmazingStatesNeededForWhateverIWantEnum||(MySuperAmazingStatesNeededForWhateverIWantEnum={}));

Without IIFE minification wouldn't be as effective

// amazing_enum.js
var MySuperAmazingStatesNeededForWhateverIWantEnum;
MySuperAmazingStatesNeededForWhateverIWantEnum || (MySuperAmazingStatesNeededForWhateverIWantEnum = {}),
MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum.Red = 0] = "Red";
MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum.Green = 1] = "Green";
MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum.Blue = 2] = "Blue";

// amazing_enum.min.js
var MySuperAmazingStatesNeededForWhateverIWantEnum;MySuperAmazingStatesNeededForWhateverIWantEnum||(MySuperAmazingStatesNeededForWhateverIWantEnum={}),MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum.t=0]="Red",MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum.u=1]="Green",MySuperAmazingStatesNeededForWhateverIWantEnum[MySuperAmazingStatesNeededForWhateverIWantEnum.m=2]="Blue";

The difference in size is huge.

nwxdev
  • 4,194
  • 3
  • 16
  • 22
An0num0us
  • 961
  • 7
  • 15
  • A good minifier would rename all instances of `MySuperAmazingStatesNeededForWhateverIWantEnum`, not just the ones inside of the IIFE. – str Nov 18 '17 at 10:50
  • @str It would depend on whether the variable would need to be externally accessed – LegionMammal978 Nov 18 '17 at 15:41
  • @LegionMammal978 That does not matter as "all instances" would be renamed, even the external ones. – str Nov 18 '17 at 19:27
  • @str you cannot rename all instances - if it's a global variable, then its name might be generated dynamically, or it might be accessed from somewhere else (not this script). – zerkms Nov 18 '17 at 20:03
  • @zerkms There might be some occasions where this is true. But I doubt that TypeScript compiles enums to IIFEs because of these. If minification is the goal, then why doesn't TypeScript minify it already? – str Nov 18 '17 at 20:34
  • @str I don't know exactly, but minification is more efficient if you do everything at once (the mangled names are crafted specifically to save bytes - not every single-letter variable ends up being compressed as efficiently). – zerkms Nov 18 '17 at 20:41
1

That's because in TypeScript Enums are open ended.

This means that you can do something like below:

enum Colors {
 Red,
 Green,
 Blue
}

console.log(Colors); // { '0': 'Red', '1': 'Green', '2': 'Blue', Red: 0, Green: 1, Blue: 2 }

enum Colors {
 Black = 3, 
 White
}

console.log(Colors); // { '0': 'Red', '1': 'Green', '2': 'Blue', '3': 'Black', '4': 'White', Red: 0, Green: 1, Blue: 2, Black: 3, White: 4 }

That means that you can add new members to an already defined enum. Consider this as partial class in C# or interfaces in typescript.

The IIFE is used so that any existing definition (read object) of enum of the same name can be picked up.

Also TypeScript correctly shows reports the below error if we omit = 3 part from the second enum Colors definition.

In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.

(enum member) Colors.Black = 0

Read more here: https://basarat.gitbooks.io/typescript/docs/enums.html

EDIT: Also note that there are some benefit of using IIFE.

  1. Local scope optimization: It is always beneficial to declare your variable as close as possible to your first use of that variable. JavaScript can traverse from local scope to global scope in order to lookup a variable. Thus, using a local variable can provide performance benefit.
  2. Minification: this is already mentioned in the answer by @Piotr Kocia. It means that you can also use shorter variable names for the IIFE.

    var Colors;
    (function (C) {
        C[C["Red"] = 0] = "Red";
        C[C["Green"] = 1] = "Green";
        C[C["Blue"] = 2] = "Blue";
    })(Colors || (Colors = {}));
    

    This might not look a so lucrative proposition at this point but consider when you variable name is longer.

More on the topic "Why IIFE": http://gregfranko.com/blog/i-love-my-iife/

Hope this helps.

Sayan Pal
  • 4,768
  • 5
  • 43
  • 82