Skip to content

Hot Module Replacement (HMR) Problems with Module "NodeNext" #60795

@DeusProx

Description

@DeusProx

Which @angular/* package(s) are the source of the bug?

Don't known / other

Is this a regression?

No

Description

Hot Module Replacement (HMR) Problems with Module "NodeNext"

Setting "module" to "NodeNext" in tsconfig.json leads to errors during HMR, when changing the template or styles of a component.
This repository is showcasing this behaviour by providing two npm scripts esnext and nodenext.

To understand the problem further you can checkout following explanation:

Example

TS:

@Component({
  selector: 'app-root',
  template: `<div class='title'>{{title}}</div>`,
  styles: '.title { font-size: xxx-large }',
})
export class AppComponent {
  title = 'hello world'
}

Running npm run esnext will start the dev server with module set to Esnext and moduleResolution set to bundler.
When now for example changing font-size to x-large will trigger an update, the build will be rerun,
a network request to http://localhost:4200/@ng/component?c=src%2Fcomponent.ts%40AppComponent&t=<timestamp will be made,
following code will be loaded and thus replaces the component with the update correctly.

export default function AppComponent_UpdateMetadata(AppComponent, ɵɵnamespaces) {
    const ɵhmr0 = ɵɵnamespaces[0];
    AppComponent.ɵfac = function AppComponent_Factory(__ngFactoryType__) {
        return new (__ngFactoryType__ || AppComponent)();
    }
    ;
    AppComponent.ɵcmp = /*@__PURE__*/
    ɵhmr0.ɵɵdefineComponent({
        type: AppComponent,
        selectors: [["app-root"]],
        decls: 2,
        vars: 1,
        consts: [[1, "title"]],
        template: function AppComponent_Template(rf, ctx) {
            if (rf & 1) {
                ɵhmr0.ɵɵelementStart(0, "div", 0);
                ɵhmr0.ɵɵtext(1);
                ɵhmr0.ɵɵelementEnd();
            }
            if (rf & 2) {
                ɵhmr0.ɵɵadvance();
                ɵhmr0.ɵɵtextInterpolate(ctx.title);
            }
        },
        styles: ["\n\n.title[_ngcontent-%COMP%] {\n  font-size: x-large;\n}\n/*# sourceMappingURL=component.css.map */"]
    });
    (() => {
        (typeof ngDevMode === "undefined" || ngDevMode) && ɵhmr0.ɵsetClassDebugInfo(AppComponent, {
            className: "AppComponent",
            filePath: "src/component.ts",
            lineNumber: 8
        });
    })();
}

Running npm run nodenext instead will start the dev server with module set to NodeNext.
When doing the same change the network request will instead deliver following code:

"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.default = AppComponent_UpdateMetadata;
function AppComponent_UpdateMetadata(AppComponent, ɵɵnamespaces) {
    const ɵhmr0 = ɵɵnamespaces[0];
    AppComponent.ɵfac = function AppComponent_Factory(__ngFactoryType__) {
        return new (__ngFactoryType__ || AppComponent)();
    };
    AppComponent.ɵcmp = /*@__PURE__*/
    ɵhmr0.ɵɵdefineComponent({
        type: AppComponent,
        selectors: [["app-root"]],
        decls: 2,
        vars: 1,
        consts: [[1, "title"]],
        template: function AppComponent_Template(rf, ctx) {
            if (rf & 1) {
                ɵhmr0.ɵɵelementStart(0, "div", 0);
                ɵhmr0.ɵɵtext(1);
                ɵhmr0.ɵɵelementEnd();
            }
            if (rf & 2) {
                ɵhmr0.ɵɵadvance();
                ɵhmr0.ɵɵtextInterpolate(ctx.title);
            }
        },
        styles: ["\n\n.title[_ngcontent-%COMP%] {\n  font-size: x-large;\n}\n/*# sourceMappingURL=component.css.map */"]
    });
    (() => {
        (typeof ngDevMode === "undefined" || ngDevMode) && ɵhmr0.ɵsetClassDebugInfo(AppComponent, {
            className: "AppComponent",
            filePath: "src/component.ts",
            lineNumber: 8
        });
    })();
}

This code will throw following error:

zone.js:2317 ReferenceError: exports is not defined
    at component?c=src%2Fco…=1744143776918:2:23

Please provide a link to a minimal reproduction of the bug

https://github.com/DeusProx/hmr-nodenext

Please provide the exception or error you saw

zone.js:2317 ReferenceError: exports is not defined
at component?c=src%2Fco…=1744143776918:2:23

Please provide the environment you discovered this bug in (run ng version)

% ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 19.2.6
Node: 20.15.1
Package Manager: npm 10.9.2
OS: linux x64

Angular: 19.2.5
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1902.6
@angular-devkit/build-angular   19.2.6
@angular-devkit/core            19.2.6
@angular-devkit/schematics      19.2.6
@angular/cli                    19.2.6
@schematics/angular             19.2.6
rxjs                            7.8.2
typescript                      5.8.3
zone.js                         0.15.0

Anything else?

Additional problems with Module "NodeNext"

Additionally to the HMR problem the setting also seems to break the angular LSP for templates.
The local LSP of your Editor will load the tsconfig.json so make sure to adjust it accordingly.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions