-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
How to integrate react-refresh in esbuild to implement hmr ? #645
Comments
Sorry, esbuild isn't designed for HMR. It's possible to use esbuild's transform API as part of another HMR tool (e.g. what Vite does) but this isn't something that esbuild's build API supports. I'm not sure if it can be done with an esbuild plugin because esbuild's plugin API deliberately doesn't let you substitute your own linker. I haven't ever implemented HMR before but I believe HMR requires a different type of linker. For example the react-refresh documentation says that integrating it requires your bundler to support HMR. |
I know this, I think hmr should be an additional feature of esbuild. What's bad is that react-refresh currently only supports babel's transform. I haven't found any documentation or case of using esbuild's transform to implement react-refresh. https://github.com/facebook/react/blob/master/packages/react-refresh/src/ReactFreshBabelPlugin.js Look forward to esbuild's realization of HMR and lead us into the future. Cheers 🍻. |
+1 on this. HMR support or webpack-dev-server like support for esbuild would be a god send. I’m willing to dig into the source code and work on some PRs if someone’s willing to guide and review PRs. |
So I took a close look at how Dan outlined integrating HMR: facebook/react#16604 (comment). At first it didn’t make any sense but then I started to get it. What I understood is this: There’s a module you can require: If you look closely here: https://github.com/vitejs/vite/blob/main/packages/plugin-react-refresh/index.js, the way Vite implements HMR (which uses esbuild for part of its toolchain) is that it uses Babel to transform the JS. This is actually pretty easy to understand conceptually once you understand how React Refresh works. It basically just wraps your app / modules with some glue-code. You can see Evan You is literally wrapping code here: https://github.com/vitejs/vite/blob/main/packages/plugin-react-refresh/index.js#L123. return {
code: `${header}${result.code}${footer}`,
map: result.map
} In theory, someone should be able to make a proof-of-concept repo that demonstrates React Refresh without Babel -- just hardcode all of the boilerplate. See if that works. Then you have a pure implementation. From that, it’ll be easier to extrapolate how to apply these techniques to esbuild. From what I grok, esbuild may be able to handle all of this today. From what I can tell, webpack is used in order to talk to Babel in order to a) get |
I have written some custom hot module reloaders with webpack, and there interface is fairly clean The general idea is that you accept via |
Note that
You may not want to use esbuild then since esbuild is a bundler. You may want to consider using Snowpack or Vite instead. They are both no-bundler options that implement HMR and use esbuild internally for speed. |
I’ll probably have to investigate esbuild’s incremental build abilities. The whole idea of HMR and dev-server is that once you make a change in the editor, you immediately see it’s effect in the browser or similar runtime without having to rebuild the entire codebase or refresh the browser. Incremental devloop being in milliseconds is a big deal. I wonder how snowpack gets around this. May be esbuild exposes some version of an incremental module update api? |
So I figured out how to implement esbuild incremental recompilation + server-sent events to trigger reloads which feels like HMR but works today. I used this playground to validate my ideas: https://github.com/zaydek/esbuild-dev-server. I use a poll-based file watcher to look for changes to source files. The public dir is served and uses the /sse route to send sever-sent events. Those events allow me to trigger refreshes on the client browser tab. Build errors are passed along to the client browser tab and also logs them on the server. It’s not HMR but I basically can’t tell the difference. I also prefer this implementation since it doesn’t care about React anymore (React Refresh is tightly coupled to React) and there’s no quasi states to worry about anymore because state is never persisted between reloads (unless you write to localStorage). |
@zaydek i did not dig into this yet, but how is esbuild-dev-server this different than https://github.com/osdevisnot/sorvor? |
@osdevisnot It’s functionally similar. I actually reviewed your code which helped me a lot. I’ll put this in a
Main differences:
Anyway, I needed to understand all of the moving parts from first principles in order to keep working on my page-based router: https://github.com/zaydek/retro/tree/a54bf320c7787774c61412bec4237c9c9b7f2d22. I’m trying to solve for a rapid-development environment with page-based routes vs. a single-page application architecture. I went with poll-based watching (like you) because it’s the simplest and platform-independent by default. Rationale here: #21 (comment). |
There is https://github.com/progrium/hotweb , which is using esbuild and gorilla ws. You dont actually need to implement module.hot api to get stateful reloading. Most hmr servers send a “reload” event via ws. My experience with go is borderline none, but was looking through the esbuild docs/code and i think there is the foundation to do everything needed using the esbuild prelude and output meta.json . The wrappers esbuilds appends at the top of bundles, like __toModule(_import(“react”)) or whatever it is could be augmented a little bit to listen for reload events. The reload event could have a data object that has the changed module identifier(s) and the new code. The logic for re-executing the bundle could be wrapped around the original bundle using the prelude api. Is there a hook to intercept the final write ? Changing the bundles identifiers to use src paths would be super helpful there is a very simple implementation here using browserify (esbuild is setup also, not originally for esbuild hmr, but its a decent starting point to do some hacking ) const Test = () =>
useObserver(() => {
return (
<div>
<header>App Headers</header>
<h1 style={{ textAlign: "center" }}>Count: {model.counter}</h1>
<input
type="range"
min={0}
max={1000}
value={model.counter}
onInput={(evt) => (model.counter = Number((evt as any).target.value))}
style={{ width: "100%" }}
/>
</div>
);
}); Just as an example, you can update the text of App Headers and the range input slider will maintain state, because the value is held in a different module. This is without any hmr jsx transforms or anything, just the hot reload socket api. I think this is good enough for most apps. I think this level of reloading could be done via esbuild with very little code, i just have no idea how to do it. |
@zaydek
From what i understand, esbuild can indeed be used as a transformer like babel. Could you elaborate, on why it is not suitable for this situation? |
I can try to help based on my understanding of esbuild. It's important to think of esbuild as a build-time tool and not a runtime tool. HMR is basically a clever way to layer a system on top of the runtime. So that means in most cases, HMR needs to know something about your runtime, say for example React. React Refresh or whatever Facebook uses is a React tool. esbuild is a JavaScript tool. In order for esbuild to support HMR, esbuild would need to interact with React in a way that it's not designed to. esbuild does understand JSX but that's mostly a build-time transformation. So the point is, I think, more philosophical. esbuild is trying to be a compile-time tool for JavaScript, but it's not runtime or framework-aware in the way it would need to be to truly support HMR. HMR if I'm not mistaken is a deeply runtime / framework-specific tool, and esbuild is anything but that. I hope that clears up any of the misconceptions. I'm not saying HMR isn't possible with esbuild via plugins or something like that, I'm just trying to help explain why esbuild doesn't particularly have an opinion on HMR to begin with. |
Closing this issue because this is out of scope. I recommend using another tool instead. |
I use esbuild to build my react application, no webpack, no rollup, only esbuild.It works well, everything looks great.
But I don't know how to implement hmr in esbuild.
I checked the hmr implementation of vite. But the difference is that vite only injects the HMR code when the module is requested, not during the build.So this is easy to implement.
I used code splitting.
I need to inject hmr code during the first build process. Re-execute the build when the code is updated, and update the code dynamically.
This is not a simple matter.
I guess esbuild plugin should be able to implement it, but I need some guidance. How can i implement it ?
The text was updated successfully, but these errors were encountered: