Search code examples
typescriptwebpacktsconfigecmascript-nextts-loader

this is changed to undefined with webpack + typescript + module = "esnext"


I have a setup with webpack + typescript (using ts-loader).

To enable code splitting with webpack, you must set module to esnext in tsconfig:

// tsconfig.json
{
  "compilerOptions": {
    "module": "esnext"
    // other configuration ...
  }
}

I'm trying to pass this as a parameter in one of my files. It works on node.js, which runs on individually compiled files using the native typescript compiler, but the problem is:

in webpack this is replaced with undefined

I've reduced it to this simple setup:

Typescript source code

export var this2 = this;

Output of tsc:

export var this2 = this;

Webpack output:

/*!**********************!*\
  !*** ./src/index.ts ***!
  \**********************/
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "this2": () => (/* binding */ this2)
/* harmony export */ });
var this2 = undefined;

/******/ })()
;

This even happens when there is a lot of other things exported in the file (so this is not just empty and therefor reduced to undefined)

I'm not 100% sure where the issue lies. But note that this does not happen with the same setup without typescript (so webpack + javascript source + module=esnext) nor does it happen with compiling typescript to javascript for nodejs with tsc.

I wonder where the problem lies and if this is expected behavior for any reason.

I want a file register all the exports of that file somewhere, without having to import it (the other file doesn't know of its existence). But this behavior seems to make it impossible to access the files' exports and pass them to a function

minimal reproduction setup

See this live stackblitz setup

Run webpack or tsc in the terminal to recreate the outputs in /webpack and /tsc


Solution

  • this at the top level of an ESM module has the value undefined. That's per specification (link). So Webpack is just doing what the specification says.

    If you want the value this would have at the top level of a non-module script, use the new(ish) globalThis (spec | MDN).

    In a comment (and in your question!) you've said:

    I would like to access all the files' exports like this usually does in commonjs

    You can do that in a surprising way: By getting the module namespace object of the module from...itself! Say you're doing this in mod.js. You can get the module namespace object for the module itself by doing:

    // *IN* `mod.js` itself
    import * as mod from "./mod.js";
    

    mod will refer to an object that has properties for all of the module's exports (the default export will have the property name default). Here's a complete example:

    export const a = 42;
    export const fn = () => { };
    
    import * as mod from "./mod.js";
    console.log(mod.a);        // 42
    console.log(typeof mod.fn) // "function"
    

    That said, if you want to pass things around as a unit like that, you might consider creating an object explicitly and exporting the object. It depends on the use case.