Search code examples
javascriptfabricjscreate-react-appuglifyjs

UglifyJs - minified mangled variable recovers at runtime its original name


In my codebase I have this custom code that interacts with fabric.js:

export function cropToCircle(object) {
    let minDim = Math.min(object.width, object.height);
    window.lastMinDim = minDim;
    return object.set('clipTo', ctx => {
        if (typeof minDim === 'undefined') minDim = window.lastMinDim || 1000;
        ctx.arc(0, 0, minDim / 2, 0, Math.PI * 2, true);
    });
}

It gets minified by uglify.js into this string, which I present here prettified:

function s(e) {
    var t = Math.min(e.width, e.height);
    return window.lastMinDim = t, e.set('clipTo', function (e) {
        'undefined' === typeof t && (t = window.lastMinDim || 1e3), e.arc(0, 0, t / 2, 0, 2 * Math.PI, !0);
    });
}

At runtime this minified code works well in most invocations. But there is one, triggered by Fabric's canvas.renderAll() in which I get an error "Reference Error: e is not defined". The error comes during the execution of the callback of e.set('clipTo').

The amazing thing is that if I check in the debugger, I see that the callback is being run as a snippet where the input param is called ctx instead of e. Here is the screen shot:

ctx is back

Obviously if the input param is renamed to ctx by the interpreter, then the e inside the body has no definition; but it is like the browser "knows" that e was called originally ctx, which does not make any sense to me, unless Fabric (which uses ctx all over the place) has somehow influence on the execution of this snippet.

This is verified in both Chrome 65 and Firefox 59.

I have tried in every way I could imagine to change something in the source code in order to avoid the appearance of ctx:

  • rename ctx to context in the source code
  • extract the callback into a separated named function

But still no joy.

Can anyone explain why the browser is renaming the input variable of this callback?


Solution

  • This is a very remarcable accident. Never seen anything like this and, apparently, no one else seems to have anything to say about it.

    The solution I have found is to protect the param name ctx from mangling during the minification.

    The relevant UglifyJS config is:

      mangle: {
        except: ['ctx'],
      },
    

    which creates the following minified/prettified code:

    function s(e) {
        var t = Math.min(e.width, e.height);
        return window.lastMinDim = t, e.set('clipTo', function (ctx) {
            'undefined' === typeof t && (t = window.lastMinDim || 1e3), ctx.arc(0, 0, t / 2, 0, 2 * Math.PI, !0);
        });
    }
    

    This way the browser still overrides the input param name with ctx, but in this case the function body makes still sense during its execution.