remix
remix copied to clipboard
Purge cache on file changes on dev servers
Closes: -
- [ ] Docs
- [x] Tests
Testing Strategy:
remix-dev(remixtemplate): Addeddev-server-test.tsintegration test file to verify live reload worksexpresstemplate: Tested in my project which is based on remix express template
Background
For the smooth live reload on dev server, express template and remix dev commands both has a mechanism to purge cache of server-side bundle built by Remix compiler, named purgeRequireCache(). This is currently called for every HTTP request.
function purgeRequireCache() {
// purge require cache on requests for "server side HMR" this won't let
// you have in-memory objects between requests in development,
// alternatively you can set up nodemon/pm2-dev to restart the server on
// file changes, but then you'll have to reconnect to databases/etc on each
// change. We prefer the DX of this, so we've included it for you by default
for (const key in require.cache) {
if (key.startsWith(BUILD_DIR)) {
delete require.cache[key];
}
}
}
app.all(
"*",
process.env.NODE_ENV === "development"
? (req, res, next) => {
purgeRequireCache();
return createRequestHandler({
build: require(BUILD_DIR),
mode: process.env.NODE_ENV,
})(req, res, next);
}
: createRequestHandler({
build: require(BUILD_DIR),
mode: process.env.NODE_ENV,
})
);
(From templates/express/server.js)
Problem
However, this solution is not scalable in following ways and it's easy to get really slow compared to production builds:
- As the app grows, size of
build/index.jsgrows and it takes longer and longer to parse and execute. We don't want to repeat unnecessary re-computation.- In my project,
appdirectory is 600KB (9.5K lines), and the resulted bundle has 243KB (5K lines).
- In my project,
- As the nested routes get deeper, the purge happens multiple times per one navigation, because one navigation can make multiple requests to loaders for the ancestor routes
- If we have resource routes which are loaded by a page, then it can create huge amount of HTTP requests, each of which causes the purge as well.
In my project, each HTTP request takes like 400ms, which can easily lead to 1s for page navigation depending on routes.
Solution
Move purge call from HTTP request handler to...
remix-dev: after build is done, right before sending reload event to browserexpresstemplate: after build is done, watched bychokidar- Browser can be notified to reload and hit server before
chokidarnotifies to purge cache. 100ms delay is added before reload.
- Browser can be notified to reload and hit server before
I didn't find integration tests for dev server, so I added one to test live reload happens after file changes.
References
🦋 Changeset detected
Latest commit: d23af703dd32aeb38faab8bda95cd231ade19343
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 16 packages
| Name | Type |
|---|---|
| @remix-run/dev | Patch |
| create-remix | Patch |
| remix | Patch |
| @remix-run/architect | Patch |
| @remix-run/cloudflare | Patch |
| @remix-run/cloudflare-pages | Patch |
| @remix-run/cloudflare-workers | Patch |
| @remix-run/deno | Patch |
| @remix-run/eslint-config | Patch |
| @remix-run/express | Patch |
| @remix-run/netlify | Patch |
| @remix-run/node | Patch |
| @remix-run/react | Patch |
| @remix-run/serve | Patch |
| @remix-run/server-runtime | Patch |
| @remix-run/vercel | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
I think this will also help for support with in-memory caches / databases / stores / etc... on the server in dev. Currently, any in-memory data gets cleared out when a page transition occurs. Same thing with connections to databases, where we used to rely on global vars. Going to verify this tomorrow.
Any objects that need to survive the purge (like dB connection or cache) should be stored on the global object in development.
I have a similar solution for purges, but I created a special endpoint /build/__purge__ that is called by my custom <LiveReload>
fetch('/build/__purge__')
.finally(() => location.reload())
I created a related PR, https://github.com/remix-run/remix/pull/4307, meant to provide a stopgap against this same pain point, i.e. doing some work after builds complete.
In this reference PR, I also briefly touch on the idea that it would be great to have an API within the remix.config.js that allows users to hand a function to the Remix compiler that is run as part of the esbuild lifecycle callbacks.
I've added a proposal for a Node API for the compiler and dev server that I think will address the fundamental problem.
The proposal looks good. Looking forward to it being merged. Thanks a lot for digging into this!
Superceded by #5133