remix
remix copied to clipboard
[vite] Files shared between Remix and Express are loaded twice
Reproduction
https://stackblitz.com/edit/remix-run-remix-muutqq?file=server.js,app%2Froutes%2F_index.tsx
Check server.js and routes/_index.tsx (there are comments indicating where).
System Info
System:
OS: macOS 13.5.2
CPU: (8) arm64 Apple M1
Memory: 109.50 MB / 8.00 GB
Shell: 3.6.1 - /opt/homebrew/bin/fish
Binaries:
Node: 18.19.1 - ~/.local/share/mise/installs/node/18.19/bin/node
npm: 10.2.4 - ~/.local/share/mise/installs/node/18.19/bin/npm
pnpm: 8.7.5 - /opt/homebrew/bin/pnpm
Browsers:
Chrome: 123.0.6312.106
Safari: 16.6
Used Package Manager
npm
Expected Behavior
In the StackBlitz, I'd expect that all imports of foo-service would resolve to the same memory reference.
Actual Behavior
When importing the same module in express code and in Remix code, the module gets loaded "twice" (once the actual module, and the other is its copy bundled in build/server/index.js)
I tried using ssr.external but that doesn't seem to work with relative files, only node_modules?
With the classic compiler, I was able to work by using the server option in remix.config.js, but I can't find a way to that with Vite (I'm not well versed with Vite's options though). If there's any workaround to this problem I'd appreciate. Otherwise, I won't be able to use the new Vite compiler at all in my projects
Interesting. Yeah, I thought that importing directly from app/services from my server file would work, but you're right. The generated build/server/index.js file contains a copy of the imported code.
Anyway, I played around with a bunch of Vite and esbuild flags, but never could get them to combine since they're not built together.
So I got the crazy idea of simply having Remix import my Express server file directly. My server file actually exports the express app (for use in my Vite plugin)
// app/entry.server.tsx
import app from "#server/index"; // import the created express app
export { app }
// rest of entry.server.tsx
Had Remix build the production file and sure enough, the generated file included both my Express code and the Remix stuff.
Ran NODE_ENV=production node ./build/server/remix.js
Voila!
Thanks @kiliman, that's actually not a bad idea
I was able to make it work by (ab)using entry.server.tsx:
- create an express app in
entry.server.tsxthat has the Remix handler and other related express code- you can create the Remix request handler by importing
virtual:remix/server-builddirectly
- you can create the Remix request handler by importing
- in my
server.tsfile I create another express app (this is the one that will actually handle requests)- call the
ssrLoadModule()to access the build file - then you can access the "inner" express app with
build.entry.module.app(appis how I exported the express app inentry.server.tsx)
- call the
It's cool that I get HMR in the server for my entire app (both express and Remix parts)!
You can see the resulting code here: https://stackblitz.com/edit/remix-run-remix-t9pwxg?file=server.js,app%2Fentry.server.tsx
I'm not sure there's anything in Remix side that could be done to make this better, at least make sure the build file won't change too much in the future (but if it does change, that would be easy to catch in dev because it will fail fast). Either way, I do think this is a reasonable workflow and should be supported by Remix somehow.
PS: One thing I noticed is that the server build file exports some of my components and hooks. I could not understand why that happens, but got worried I could get a name conflict with the app in entry.server.tsx
Any news ? Building with rollupOptions.external work. But ssr.external doesn't work.
So build : OK but HMR : KO