6

When using UglifyJS, function names are mangled, unless keep_fnames is set to true. For example, the following Typescript code:

class Test {}
console.log(Test.name);

compiled to JS as:

function Test() {}
console.log(Test.name);

will be uglified to:

function t() {}
console.log(t.name);

and output t instead of test to the console.

Is there a way (other than using keep_fnames option) to preserve the name property after uglification ? (I don't want to use keep_fnames:true because it increases the bundle size quite a lot.

Possible solutions I thought of:

  • Writing a Webpack plugin that hard codes the function name Test.name = 'Test', but this won't work as Function.prototype.name is a read only property;
  • Using Typescript decorators, metadata and the reflection API, but design:type metadata is not emitted for classes, it's only emitted for properties (which I believe is because Function.prototype.name exists, but I guess they missed this edge case ?).
user5365075
  • 2,094
  • 2
  • 25
  • 42
  • 1
    Can you write a Webpack plugin that replaces instances of `Test.name` with `"Test"`? Similar to what the `DefinePlugin` does. – CodingIntrigue Oct 05 '17 at 11:16
  • I guess yes, but in this case I simplified the example. In my real use case, the `.name` property is accessed from within an external library that I wrote as a private npm package. – user5365075 Oct 05 '17 at 11:28
  • Possible duplicate of https://stackoverflow.com/questions/46561116/angular4-component-name-doesnt-work-on-production – Estus Flask Oct 05 '17 at 13:24
  • @user5365075 What does this library need the `.name` for? – Bergi Oct 05 '17 at 14:05
  • This library uses class names to create a service registry, and also to inject those services in other services and classes. Providing a service ID does not solve anything because the lib also relies on `.name` to get the types to inject. Mangling replaces function names and thus corrupts typescript metadata, which does not store string type values but references to the class (which becomes a function when compiled). – user5365075 Oct 06 '17 at 12:41

2 Answers2

3

As it's explained here, Function.prototype.name cannot be relied on in client-side code, because the information on function original name will be destroyed with minification process. Preventing it from being renamed is quick-and-dirty fix.

name is read-only and non-configurable in some browsers, so doing something like

class Test {
  static get name() {
    return 'Test';
  }
}

or

function Test() {}
Object.defineProperty(Test, 'name', { configurable: true, value: 'Test' });

will fix it in most browsers but result in obscure compatibility problems in rest of them (for example, Android 4.x browser).

The proper way to do this is to never rely on name in client-side code for anything but debugging. As for Node.js and Electron, it depends on whether the code needs to be obfuscated.

If string identifier should exist for a class or a function, another static property name can be picked, e.g. id or unsupported yet conventional displayName.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Great answer, honestly. I like the idea of using `displayName`. Too bad it is an unavoidable workaround. – user5365075 Oct 06 '17 at 12:47
  • You're welcome. Unavoidable, yes. Notice that situation with `name` isn't same as with props for `design:type`. Props have fixed names (they can be mangled by minifier too, but this is hardly ever desirable). Functions don't. – Estus Flask Oct 06 '17 at 12:55
2

Is there a way (other than using keep_fnames option) to preserve the name property after uglification...

The only mechanism to keep the correct name involves that name being in the output file, so the short answer is no. If you want to use prototype.name you need to leave that name be.

The alternatives would involve either:

  1. Adding an additional property containing the name, which could introduce errors and would still take up space in your file
  2. Finding a tool that will pre-compile all uses of prototype.name with the string value... I'm not aware that one exists but you never know!
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • Option 2 I like very much :) I guess writing such a webpack plugin is doable, but I am worried that this won't work in this case because my code that uses the function name is contained in an external library. Hence, `.name` is access in an external library. – user5365075 Oct 05 '17 at 11:27
  • 1
    It would be terribly hard to resolve some uses, and of course potentially impossible if the usage would be different in multiple cases (which is presumably the case, otherwise you wouldn't be looking at the value). – Fenton Oct 05 '17 at 11:38