DEV Community

Uday Rana
Uday Rana

Posted on

Trying to Upgrade from Classic Remix Compiler to Remix Vite

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.

Changes

  • Upgrade remix watch to remix dev
    • We were supposed to do this when upgrading to v2
  • Use tsx to run server.ts and watch for changes instead of building it first with esbuild
    • 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.
  • Replace ts-node with tsx in prisma.seed
    • We already had ts-node to run TypeScript files, but Remix recommends tsx and there's no reason to keep both

References

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,
    })
  );
Enter fullscreen mode Exit fullscreen mode

Dynamic import problem

await import('./build/server/index.js');
Enter fullscreen mode Exit fullscreen mode

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'],
  },
});
Enter fullscreen mode Exit fullscreen mode

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\\/.*'],
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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",
Enter fullscreen mode Exit fullscreen mode

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

Changes

  • 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

Changes

  • Sort alphabetically
  • Remove entries that don't exist (like /e2e)
  • Add new entries
  • 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

Changes

  • 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.

  • Added two oxc plugins not enabled by default: jsx-a11y and import

  • 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)

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay