I've been trying to upgrade our project Starchart from the Classic Remix Compiler to Remix Vite. We recently upgraded from Remix v1 to v2 and that went pretty smoothly, but this jump is proving pretty tricky. I've run into a number of challenges.
Switching from remix watch
to remix dev
When we upgraded to Remix v2 we overlooked switching from remix watch
to remix dev
. I had to work on that first before I could begin upgrading to Remix Vite.
Upgrade `remix watch` to `remix dev`
#842
This is an intermediate step in upgrading to Remix Vite.
- Upgrade
remix watch
toremix dev
- We were supposed to do this when upgrading to v2
- Use
tsx
to runserver.ts
and watch for changes instead of building it first withesbuild
- Remix examples use a .js file for the server so I went to the Remix Discord server and they recommended this method. However, this is slower than building the server.
- We no longer need
predev
,dev:build
,delay
,dev:remix
,dev:server:delay
,dev:server
scripts
- Remove
purgeRequireCache
- According to the video walkthrough linked below this is no longer required.
- Remove
--require ./node_modules/dotenv/config
- According to the video walkthrough linked below, this is no longer required because Remix automatically loads
.env
files.
- According to the video walkthrough linked below, this is no longer required because Remix automatically loads
- Replace
ts-node
withtsx
inprisma.seed
- We already had
ts-node
to run TypeScript files, but Remix recommendstsx
and there's no reason to keep both
- We already had
Upgrading to v2: remix dev
: Custom app server
@remix-run/dev CLI: remix dev
: With custom app server
Migrating your project to v2_dev π
Custom server built with TypeScript
We're using a custom server written in TypeScript, but all of the examples in the documentation for custom servers are written in JavaScript. There's a footnote that mentions using tsx
or tsm
which I only saw after I'd already switched over to tsx
after looking through Remix's Discord server.
I had to add ViteDevServer to our custom server, but the type definitions for ViteDevServer
are incompatible with Remix. ViteDevServer.ssrLoadmodule
returns Record<string, any>
while createRequestHandler({build})
expects an argument of type ServerBuild
, so I've had to do some weird assertion stuff.
app.all(
'*',
createRequestHandler({
build: viteDevServer
? async () => {
const mod = await viteDevServer.ssrLoadModule('virtual:remix/server-build');
return mod as unknown as ServerBuild;
}
: async () => {
const mod = await import('./build/server/index.js'); // This import's been giving me some trouble too
return mod as unknown as ServerBuild;
},
getLoadContext,
})
);
Dynamic import problem
await import('./build/server/index.js');
The dynamic import above is where the server imports all of the server-side code after it's been built. Because of this, the linter and type check are failing in CI, since the module being imported hasn't been built when they're run. I haven't had time to think about the fix - maybe just add an ignore comment - but I feel like there has to be a better solution.
CommonJS
We're also compiling our custom server to CommonJS which there aren't any examples for in the Remix docs. All of the examples for Remix Vite use top-level await, which isn't allowed when compiling to CommonJS so I've had to wrap the whole thing in an IIFE. Maybe switching to ES modules would be a better approach?
There was also this package @emotion/cache
that wouldn't import properly until I used a Vite plugin for CommonJS/ES modules interoperability.
// vite.config.ts
import { vitePlugin as remix } from '@remix-run/dev';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { cjsInterop } from 'vite-plugin-cjs-interop'; // This plugin
export default defineConfig({
plugins: [
!process.env.VITEST &&
remix({
ignoredRouteFiles: ['**/.*', '**/*.css', '**/*.test.{js,jsx,ts,tsx}'],
serverModuleFormat: 'cjs',
}),
tsconfigPaths(),
// https://remix.run/docs/en/main/guides/vite#esm--cjs
cjsInterop({
dependencies: ['@emotion/cache'],
}),
],
optimizeDeps: {
include: ['@emotion/cache'],
},
});
Vitest
Also our project is currently on Vitest 0.34 which at one point was causing type errors with Vite 6. I upgraded Vitest to v3 on my branch and fixed the breaking changes and as far as I can tell it's working fine. I just had to switch from watchExclude
to server.watch.ignored
:
// vitest.config.ts
// Before
/// <reference types="vitest" />
/// <reference types="vite/client" />
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
setupFiles: ['./test/unit/setup-test-env.ts'],
include: ['./test/unit/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
watchExclude: ['.*\\/node_modules\\/.*', '.*\\/build\\/.*', '.*\\/docker\\/volumes\\/.*'],
coverage: {
provider: 'istanbul',
},
},
});
// After
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
setupFiles: ['./test/unit/setup-test-env.ts'],
include: ['./test/unit/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
coverage: {
provider: 'istanbul',
},
},
server: {
watch: {
ignored: ['.*\\/node_modules\\/.*', '.*\\/build\\/.*', '.*\\/docker\\/volumes\\/.*'],
},
},
});
Why bother?
We're considering migrating to React Router v7 and this is an intermediate step. From what I can tell the jump from Remix Vite to React Router v7 should be simpler than the jump from Classic Remix Compiler to React Vite.
I'm also a fan of how much cleaner our npm scripts are (or will be, if I can get this to work).
// Before
"build": "run-s build:*",
"build:remix": "remix build",
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build --bundle",
"predev": "npm run build:server",
"dev": "cross-env NODE_ENV=development SECRETS_OVERRIDE=1 run-p dev:remix dev:server:delay",
"dev:build": "npm run build:server -- --watch",
"delay": "node -e \"setTimeout(() => process.exit(0), 3000)\"",
"dev:remix": "remix watch",
"dev:server:delay": "run-s delay dev:server",
"dev:server": "node --inspect --require ./node_modules/dotenv/config ./build/server.js",
// After
"dev": "cross-env SECRETS_OVERRIDE=1 tsx --inspect ./server.ts",
"build": "remix vite:build",
"start": "cross-env SECRETS_OVERRIDE=1 NODE_ENV=production node ./build/server/index.js",
As of now, the dev
script and hot module reloading work, the build
script works, and the app starts, so all hope is not lost.
Other stuff this week
Other stuff I worked on this week included:
- Improving our contributing documentation
Rework CONTRIBUTING.md
#836
- Add unit testing instructions
- Add end-to-end testing instructions
- Add contributing workflow section
- Add resources section
- Add separate section with link to wiki to make it more prominent and easier to find
- Split setup commands into separate codeblocks for easy copy pasting
- Add more headings and reword some sections for clarity
- Move testing section directly underneath setup section
- Remove npm prerequisite listing since it comes with Node
- Remove table of contents since GitHub has one baked-in
- Cleaning up our ignore files (
.prettierignore
,.dockerignore
,.gitignore
)
Clean up ignore files
#835
- Improving type handling for DNS Record types
Improve DNS record type handling
#837
Follow-up on #830.
In #830, there was discussion on using a function to map type
to RRType
instead of using type as RRType
: https://github.com/DevelopingSpace/starchart/pull/830#pullrequestreview-2613283520
Earlier in the code, we're already asserting type as DnsRecordType
, which is a subset of RRType
. I think it makes sense to be consistent and use type as DnsRecordType
here too.
Also in services/reconciler/route53-client.server.ts > getDnsRecordSetPage(), we can set the type of the type
parameter to RRType so we don't need to map it later.
Unrelated changes:
- Simplified the logic for handling multi-value records to blacklist types that disallow them (there are only two) instead of whitelisting types that allow them, of which new ones have been added to Route53.
- Cleaned up imports in services/reconciler/route53-client.server.ts
- Adding ESLint 9 back after we removed it a while ago, and configuring it to work together with oxlint
Add ESLint 9 to support oxlint
#834
Fixes #803
-
Added ESLint 9, which is to be run after oxlint as a fallback for linting rules that aren't covered by oxlint. Rules that oxlint does cover won't be linted again by ESLint. This is the approach recommended by oxc:
We recommend running oxlint before ESLint in your lint-staged or CI setup for a quicker feedback loop, considering it only takes a few seconds to run on large codebases.
- I created the config file by grabbing the ESLint 8 flat config template recommended by Remix and using ESLint's config migrator on it. I then tweaked it from there.
-
Added two oxc plugins not enabled by default: jsx-a11y and import
- These plugins don't implement all of the linting rules of their ESLint equivalents, so we need to keep the ESLint plugins too. See https://github.com/cockpit-project/cockpit/issues/20279
-
Removed some settings from .oxlintrc.json that didn't do anything or were just re-defining the defaults.
-
Fixed lint errors detected after ESLint was added.
Top comments (0)