Fix JavaScript issues with function.name

Why function.name fails to work with minified code or when using class inheritance in IE11?

The ES2015/ES6 specification officially supports the function.name property that returns the name of the function.

// function declaration
function foo()
{}
console.log(foo.name); // console: foo
// TypeScript class declaration
class Bar{
}
console.log(new Bar().name); // console: Bar

Many JavaScript engines used to support it unofficially but in ES2015 it became part of the standard. It works fine yet introduces some issues with minification/uglification that should be considered.

I first learned about this feature while using TypedJSON library, a strongly-typed JSON parsing and serializing for TypeScript with decorators. I integrated the library, added some decorators, saw the magic happen and was pretty happy nailing this task in less than 1/2 time of my original estimation.

But! Once I created a deployment package to production I wasn’t happy anymore when the application failed to work. In this short article I will explore two problems I had and how I solved them.

Used technology stack

This story relates to the following technologies:

  • JavaScript ES2015
  • TypeScript version ≥2.x
  • webpack ≥ 2.x, UglifyJsPlugin
  • gulp, gulp-uglify plugin.

Minification breaks function.name

Most developers don’t minify their code during development, so they will encounter this issue way too late: when they deploy to production. As part of the minification process, and unless specified explicitly, the function names are being shortened to reduce bundle size. Some say it also improves runtime performance.

So once the code is minified things become weird as seen below:

View my jsFiddle here, select ‘result’ tab to see console log

To keep using function.name, many minifier plugins provide dedicated configuration.

An example of a gulp task configured to keep function names:

// gulp-uglify task configuration
gulp.task('compress', function (cb) {
pump([
gulp.src('dist/**/*.js'),
uglify({
mangle: {
keep_fnames: true // keep function names
}
}),
gulp.dest('dist/u')
],
cb
);
});

Another example of a webpack plugin loader configured to keep function names:

new UglifyJsPlugin({
...
mangle: {
keep_fnames: true // keep function names
},
...
})

Note that by doing so the bundle size will increase. As a reference, in our project the bundle size increased by 300k (minified but ungzipped)

|    Minfication Mode    | Size |
|------------------------|------|
| Kept function names | 2.4M |
| Dropped function named | 2.1M |

Alternatively, you can manage function/class names via string properties (or getter properties) since strings are not minified, but you will need to make sure they are in sync and this doesn’t comply with the DRY principle.


Runtime problem with TypeScript 2.x and IE11

TypeScript language, a typed superset of JavaScript that compiles to plain JavaScript, can be used as a transpiler to ES5. In ES5 there is no support for classes, so it uses a self-generated function named __extends. In TypeScript 2.x they modified that function implementation which breaks function.names in IE11 browser only when inheriting a class. It seems to work correctly in browsers that already support this feature.

Actually I’m not sure yet if this is a problem of TypeScript 2.x or of core-js library, a modular standard library for JavaScript that provides polyfills for older browsers like IE11. I will continue investigating it but for now if you upgraded to Typescript 2.x you will encounter this issue.

The following example writes to logs the function name of an inherited class. It is invoked once with the __extends function of TypeScript 2.x and another time with that function implementation used by the ts-helpers library (used in TypeScript 1.x).

View my jsFiddle here, select ‘result’ tab to see console log

Since it is not that easy finding IE11 nowadays, I added a snapshot of the result in IE11.

Console log from IE11 browser

To temporarily bypass this issue in our project that was already upgraded to TypeScript 2.x, we imported the old ts-helpers library used in TypeScript 1.x and configured TypeScript to assume that function already exists.

The following code was taken from the file tsconfig.json .

{
"compilerOptions": {
"noEmitHelpers": true, // will assume helpers already exists
"importHelpers": false, // will not use tslib library
..
.
},
...
}

If you find better ways to solve these problems please share them with me and I will be happy to use them as well.

Cheers,
Eran.