6

I have the following input:

import {onMediaChangeSubscribe} from '../functions/doOnMediaChange'

export class ActionButton extends ElementBase {
    mount(){
        onMediaChangeSubscribe(this.render)
    }
}

The output of typescript 4.3.x is:

doOnMediaChange_1.onMediaChangeSubscribe(this.render);

And typescript 4.4.x do the following:

(0, doOnMediaChange_1.onMediaChangeSubscribe)(this.render);

W̶h̶a̶t̶ ̶t̶h̶e̶ ̶h̶a̶c̶k̶?̶?̶?̶ My question is partially answered here: JavaScript syntax (0, fn)(args), but I would like to clarify.

Why there is such a change in TypeScript?


Minimal reproduction example:

a.ts

export function doTheThing(){}

b.ts

import {doTheThing} from './a'

doTheThing()

b.ts compiles to:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var a_1 = require("./a");
(0, a_1.doTheThing)();

Used tsconfig.json:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "sourceMap": true,
    "declaration": true,
    "strict": true,
    "noImplicitAny": false,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "downlevelIteration": true
  },
  "exclude": [
    "node_modules"
  ]
}
Anton Matiash
  • 172
  • 2
  • 6

1 Answers1

13

This change was implemented in microsoft/TypeScript#44624 as a fix to microsoft/TypeScript#35420, and was released with TypeScript 4.4. (Note that there was an earlier fix attempt at microsoft/TypeScript#35877 but this was reverted in microsoft/TypeScript#43993 before it was released.)

As the answer to the other question explains, the difference between foo.bar() and (0, foo.bar)() is that the this context of the former is foo, while in the latter it is undefined (in strict mode, or the global object in sloppy mode). foo.bar() treats bar as a method of foo, while (0, foo.bar)() treats bar as a function unrelated to foo.

Depending on TypeScript's module target, the emitted Javascript will put imported things into an object, so

import {onMediaChangeSubscribe} from '../functions/doOnMediaChange'

could be emitted as

const doOnMediaChange_1 = require("../functions/doOnMediaChange")

And then any reference to onMediaChangeSubscribe needs to be emitted as doOnMediaChange_1.onMediaChangeSubscribe.

When you call an imported function, you want it to act like a function and not a "method" of the module you're importing. Well, much of the time there's no difference, but if the body of the imported function happens to make reference to this, you really want that this to be undefined and not doOnMediaChange_1, or else it won't be equivalent to the behavior of native modules.

That was essentially the problem in microsoft/TypeScript#35420. The fix is to just universally wrap calls to imported functions with the comma operator, as you've seen.

jcalz
  • 264,269
  • 27
  • 359
  • 360