Skip to content

Improve Hot Module Replacement (HMR) #24755

@dgp1130

Description

@dgp1130

Command

build

Description

Angular CLI currently supports HMR via ng serve --hmr though it isn't the ideal experience. Under the hood, this mostly rerenders the Angular application from scratch, which is better than a full page reload, but could definitely be improved. @alan-agius4 has suggested a number of opportunities for improving HMR. Most importantly, our strategy here should be to optimize the turnaround time for any given change scaled with the frequency of that kind of change. For example, it is very common to make numerous tiny adjustments to CSS in quick succession while finding the precise styling needed for a page. This is a very hot path to optimize and even marginal improvements here can have a bit impact due to how frequently CSS-only changes are made during development.

Describe the solution you'd like

Some ideas to improve HMR include (but are not limited to):

  • Fast track CSS-only changes and apply them to any existing components on the page.
    • This should be relatively straightforward since we mostly need to correlate a component class with its <style /> tag on the page, then delete and recreate that tag with the new CSS content. This would be very fast and maintain page state.
    • Supporting inline styles is a bit more complicated but should be achievable?
  • Fast track Angular template-only changes and apply them to any existing components on the page.
    • In theory, we just need to delete the generated JavaScript render functions for a component and overwrite them with the new render functions from the modified template, then re-apply the DOM.
    • In practice, I think this is a challenge as Angular templates aren't completely stateless (ex. async pipe). But this is likely achievable in some capacity. Should be pretty quick and mostly maintain page state.
    • Supporting inline templates is a bit more complicated but should be achievable?
  • Re-render only the currently displayed Angular route, rather than the full application.
    • Statistically speaking, most code changes will be within the currently displayed Angular route and won't impact anything outside the route. In many cases, it would be sufficient to just rerender that one route.

Angular template and CSS changes in particular are two areas where users frequently make a large number of small changes in quick succession and in a manner which is easy for tooling to reason about. These are two spaces where we can have a huge impact on developer's quality of life.

If we can get this good enough, I'd love to get to a place where we could turn --hmr on by default in ng serve.

Describe alternatives you've considered

HMR is mostly one facet of the larger problem around "How do we make the time between editing a file and reloading its changes in the browser as quick as possible?" (edit/refresh time). HMR is one means of getting built changes into the running browser faster and maintain the application's current state. We should (and are) investigating ways to improve the overall edit/refresh experience independent of HMR.

More related to HMR, the focus on Angular template and CSS changes does highlight a lack of focus on TypeScript changes. This is somewhat intentional. Angular has strong separation of concerns between Angular templates (for HTML/DOM rendering), CSS (for styling), and TypeScript (for component functionality). While this isn't absolute and TypeScript can often containing rendering or styling functionality (ex. list filters or conditional CSS classes), the vast majority of DOM and CSS changes made by a developer will be done by editing a template or a stylesheet, not TypeScript.

TypeScript changes are also very tricky to apply consistently with HMR. As one example, renaming a class field in TypeScript can be HMR'd, however doing so often leads to inconsistencies and confusing behavior. HMR is intended to maintain the current user state, but it is effectively impossible for the build process to understand that deleting a foo field and creating a bar field as intended as a rename and state should be transferred from one property to the other during HMR. At runtime, it looks like this renamed field just "forgot" all its state, which can easily break the component and cause errors which require a full page reload to fix. That's one example, but the sheer variety of possible changes a user can make to TypeScript code means it can never be totally consistent or reliable.

We should definitely be open to improvements here where we can take advantage of them, and we should make sure not to let "perfect" be the enemy of "good enough". The key takeaway here is that HMR of TypeScript-only changes in Angular is simultaneously the hardest to do right, and also the least impactful. Again, scaling the impact of optimizing a particular change with the frequency that kind of change occurs is where we can find the most value for users.

/cc @alan-agius4 for other ideas around improving HMR, definitely lots of cool stuff we're hoping to do here.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions