Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HMR for esbuild #464

Closed
fellz opened this issue Oct 16, 2020 · 11 comments
Closed

HMR for esbuild #464

fellz opened this issue Oct 16, 2020 · 11 comments

Comments

@fellz
Copy link

fellz commented Oct 16, 2020

Hi
Esbuild is amazing, i almost don't notice build process in contrast to webpack where i just waiting waiting and waiting...
Is it possible to add Hot module replacement ( or Fast Refresh now) for react in bundler ? Here is the process to include it in bundler facebook/react#16604.

@nettybun
Copy link

HMR is often regarded as very difficult to get right. The over-engineering and complexity might be worth it in systems like webpack when a build takes a prohibitively long time each save, but in esbuild it should be fast enough that you can cut out that complexity and save yourself future headaches.

There's some articles talking about this, like Dan's "Ode to Complexity" one. I can't speak for Evan but I personally don't know if I'd want to see this in esbuild...

@evanw
Copy link
Owner

evanw commented Oct 16, 2020

Yes that's how I feel as well. Closing as a duplicate of #97.

FWIW I think facebook/react#16604 should be possible with the plugin API once that is released (you can follow #111 for updates).

@evanw evanw closed this as completed Oct 16, 2020
@ryanflorence
Copy link

ryanflorence commented Mar 9, 2021

Quick note, HMR isn't just about dealing with build times, but also page load times and page state.

Page load time

Most of the utility in HMR is in styling a page. If you're adding/removing classes from element generated by frameworks like React/Vue/etc. then the build may be super fast but to see your style updates you have to reload, which might be doing a number of async things like talking to oauth servers, loading data from local and remote APIs, etc. Would be nice to not have to wait on all of that just to see if your 2px of extra padding feels good.

UI State

Sometimes when you're styling your page is in a certain state, like the middle of a checkout flow where you don't want the state in the URL. HMR lets you get into the state, and then style your page.

Anyway, excited to see a plugin, but I also think it'd be worth considering first-class support since HMR is about a lot more than build times.

@nettybun
Copy link

Yes but like I said the complexity to manage and reconcile your application state/styling/etc is highly framework specific - even app specific - and it doesn't really make sense for a bundler to make those opinionated decisions about HMR. Even other HMR implementations like webpack provide only a high level "accept"/"decline" API because you have to figure out the specifics or stay inline with an abstraction by a framework.

I know it sounds easy but if you dive into it you'll see it gets messy fast. For instance, in the two cases you mentioned, styling isn't always a stylesheet replacement - popular frameworks like tailwind, emotion, and styled-components need compile-time and/or runtime updates that recompute style based on props and state of the entire render tree - assuming you have a render tree. Similarly, state isn't always something you can "get into" - I imagine you're thinking of a Redux-like system paired with a declarative React-like UI which will be happy to reconcile your DOM, but that's only one corner of web dev; if you're writing an imperative JS app whose state is just variables on the heap then how are you coordinating updates?

Side effects and other "async things" are very hard to manage. Sometimes frameworks push ideas similar to functional/declarative/immutable programming which can help/offset this complexity - even to the point where HMR can be reasonable to implement.

As a general purpose bundler, esbuild can't force users into any particular programming model. Even if esbuild could replace modules at runtime it wouldn't be as featureful compared to other framework-specific HMR options.

@nettybun
Copy link

Would be nice to not have to wait on all of that just to see if your 2px of extra padding feels good.

I know what you mean and I want that too. In my framework the CSS-in-JS will recompute the hash for the classname and I'd need to rerender the DOM either way. I wish these things were easy haha.

A quick low-tech solution is to use your dev tools to select the element and modify styles directly, then copy them out.

@desmap
Copy link

desmap commented Mar 14, 2021

Just stumbled over this thread and agree with both @heyheyhello and @ryanflorence. Looking at my build system which features webpack + webpack-dev-server is fragile. Now, it works flawlessly and having HMR in any FE-monorepo is great but I am also always afraid to change anything because it took me literally months to get there. It's easy to break HMR or anything else and hence, I have quite a long README about what to do and what not, the first time in a repo haha.

For others driving by, I am wondering who omitted HMR and doesn't look back because of faster build times, etc. Before I jump into the next rabbit hole I'd like to do my due diligence and wonder how users deal with this restriction, maybe it's even better without.

@ryanflorence
Copy link

I know it sounds easy

Oh I don't think it sounds easy at all, having messed around with HMR for a few years already 🥴

I was replying to the reasoning to not make HMR a first-class concept in esbuild:

The over-engineering and complexity might be worth it in systems like webpack when a build takes a prohibitively long time each save, but in esbuild it should be fast enough that you can cut out that complexity and save yourself future headaches.

Yes that's how I feel as well.

If the reasoning for "no hmr" is that "fast builds make it unnecessary" I think that's missing the whole story. Page reloads in real apps pretty much always have async deps before the page renders. It's not about the bundler, it's about the page.

If the reasoning is simply "it's just way too complicated", I can respect that.

@Jarred-Sumner
Copy link
Contributor

Jarred-Sumner commented Mar 26, 2021

Maybe an unopinionated HMR API would look something like:

  1. Optional runtime wrapper around exports that receives a semi-persistent module ID (a hash of the filepath?), the original export name, and an ephemeral "content ID" (to determine whether or not the content changed). By default, this runtime wrapper would be a no-op but if the developer used inject to export a specially-named object, it would register itself for Hot Module Reloading
  2. esbuild.rebuild() would accept an optional id which rebuilds the module ID and updates the metadata
  3. Its up to the HMR plugin author to trigger rebuilds, send code updates to the client, do error handling, handle rollbacks, etc

From there, maybe a React Refresh plugin author would do something like:

  • Setup an EventSource/WebSocket server with access to the esbuild object
  • Hook into React.createElement via a unique jsx function per module ID (part of the runtime wrapper by the plugin author). This would track the component name scoped to the module ID. This would be the closest equivalent to the babel plugin that registers each function call. I don't know if that would work for hooks though.
  • The server responds to esbuild changes by pushing a message to the client saying "these module IDs changed". The client says "ok i'm using components/button.js can you gimme a new one"
  • The server calls await esbuild.rebuild(moduleIDs), pushes the code to the client
  • Oversimplifying, but the client runs try { import(moduleId) } catch(exception} { rollback(); }
  • The rest of the implementation probably looks a lot like this https://github.com/codesandbox/codesandbox-client/blob/b3633367c18b0ac664fd2c4419e8e1c76333b890/packages/app/src/sandbox/eval/transpilers/react/refresh-transpiler.ts

I don't really know if this would work or be a good experience. Really hard to predict without trying

Its unfortunate that its not easy to serialize/deserialize react trees while preserving state/props. If that were possible, refresh would probably be a lot simpler

@evanw
Copy link
Owner

evanw commented Jan 14, 2023

For those who find this thread: it's now straightforward to add hot reloading of CSS on top of esbuild's new live reloading capability. This was just released in version 0.17.0.

@jacob-ebey
Copy link

Dropping this here for anyone who runs across this and is looking to implement their own HMR runtime for esbuild: https://github.com/jacob-ebey/esbuild-hmr

@hanayashiki
Copy link

https://github.com/jacob-ebey/esbuild-hmr

If esbuild could work with React Refresh, I'd abandon Vite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants