Understanding how to make Cloudflare Pages compatible with Vite, which became stable with Remix 2.7

chimame

chimame

Posted on March 1, 2024

Understanding how to make Cloudflare Pages compatible with Vite, which became stable with Remix 2.7

Remix 2.7 has been released. From 2.7 onwards, Vite compatibility, which has been unstable until now, has been adopted as the official version.
Prior to 2.7, the Node.js runtime provided something that worked, but not something that worked with Cloudflare Pages. However, at the same time as the release of 2.7, something that works with Cloudfalre Pages was released, so I will summarize the results of my research on what has changed and how it is being handled.

https://github.com/remix-run/remix/blob/main/CHANGELOG.md#v270

Several bugs were fixed after the release of 2.7.0, so we recommend using the bug-fixed version of the migration instead of 2.7.0.
Also, there is an official document for migrating to the Vite version, so I think reading it will help you understand better.

https://remix.run/docs/en/main/future/vite#migrating

Differences in initial package.json

First, let's compare the differences in the initial packages between the conventional version and the Vite version.

Differences in installation commands

Please note that the templates are separate, so the traditional version and the Vite version are different.

npx create-remix@latest --template remix-run/remix/templates/cloudflare-pages
Enter fullscreen mode Exit fullscreen mode
npx create-remix@latest --template remix-run/remix/templates/vite-cloudflare
Enter fullscreen mode Exit fullscreen mode

Difference between npm script and npm packages

The package.json actually generated from the above command differs as follows.

Difference between npm script and npm packages

Of course, Vite etc. are added to the installed packages. You can see that the packages are different, but you can also see that the npm scripts are also different.

  • Added npm scripts for deploy postinstall and typegen
  • Add dev and change start

There have been many changes, and I will explain each of the changes.

What are typegen and postinstall?

First, I will explain the additional npm scripts typegen and postinstall.
postinstall allows you to define commands that are automatically started after executing the npm install command. Therefore, in the Vite version, the script defined in postinstall will be executed by executing the command equivalent to npm install. The important content of postinstall is simply reading the typegen npm script. So the main body is a typegen npm script.

The true identity of typegen

I'm hitting a command called wrangler types which is what typegen is doing. You can understand what this is doing by reading the Wrangler documentation.

https://developers.cloudflare.com/workers/wrangler/commands/#types

In short, it generates worker-configuration.d.ts, the type file required for the application, from the wrangler.toml file used in Cloudflare's development environment and deployment.

Specifically, if you have a wrangler.toml file like the following

vars = { API_HOST = "example.com" }

kv_namespaces = [
  { id = "MY_KV", binding="MY_KV" }
]

d1_databases = [
  { binding = "DB", database_name = "TestDB", database_id = "test-db-id" }
]
Enter fullscreen mode Exit fullscreen mode
interface Env {
    MY_KV: KVNamespace;
    API_HOST: "example.com";
    DB: D1Database;
}
Enter fullscreen mode Exit fullscreen mode

This worker-configuration.d.ts type file is very important as it will be used later in Remix. This is because this file corresponds to the previous version, remix.env.d.ts. The Vite version does not use remix.env.d.ts, so be sure to generate it as written in the migrate documentation.

Notes on type generation

It's convenient because it automatically generates a type file, but there are some things to keep in mind.
What it is is the way wrangler.toml is written. In the previous example, there are two ways to write vars, and I wrote one of them, but the other is written as follows.

# Changed vars = { API_HOST = "example.com" }
[vars]
API_HOST = "example.com"

kv_namespaces = [
  { id = "MY_KV", binding="MY_KV" }
]

d1_databases = [
  { binding = "DB", database_name = "TestDB", database_id = "test-db-id" }
]
Enter fullscreen mode Exit fullscreen mode

This is also a correct description, but if you run typegen's npm script in this state, worker-configuration.d.ts like the following will be created.

interface Env {
    API_HOST: "example.com";
    kv_namespaces: [{"id":"MY_KV","binding":"MY_KV"}];
    d1_databases: [{"binding":"DB","database_name":"TestDB","database_id":"test-db-id"}];
}
Enter fullscreen mode Exit fullscreen mode

This is not the type file format that Remix is aiming for, so be careful when writing it.

wrangler.toml trap in Cloudflare Pages

A very big trap lurks. The README of Cloudflare Pages, which is compatible with the Vite version of Remix, has the following description.

> [!WARNING]  
> Cloudflare does _not_ use `wrangler.toml` to configure deployment bindings.
> You **MUST** [configure deployment bindings manually in the Cloudflare dashboard][bindings].
Enter fullscreen mode Exit fullscreen mode

Since it generates the form, wouldn't it also be reflected during deployment? For some reason, wrangler pages deploy does not reflect the contents of wrangler.toml. So, I'm telling you to first configure it on the Cloudflare console.

Two ways to launch a development environment

In the Vite version of 2.7, there are two methods. That's the modified dev and start npm script. The previous version of dev's npm script is written as follows.

  "scripts": {
    ...
    "dev": "remix dev --manual -c \"npm run start\"",
    ...
    "start": "wrangler pages dev --compatibility-date=2023-06-21 ./public",
    ...
  },
Enter fullscreen mode Exit fullscreen mode

So, if you start dev, it will start up to start. (The conventional version is also built using remix dev.) So, what's going on with the Vite version?

  "scripts": {
    ...
    "dev": "remix vite:dev",
    ...
    "start": "wrangler pages dev ./build/client",
    ...
  },
Enter fullscreen mode Exit fullscreen mode

dev and start are not linked. Works as separate commands. Regarding dev, some commands that work with Vite are defined. As for start, the command to start with wrangler is defined in the built file as before.
So what has changed here is that start starts the development environment using wrangler, which is almost the same as the previous version, and dev is the command to start the development environment with the newly added Vite.

The reality of remix vite:dev

Let's find out what the newly added remix vite:dev is. The explanation will be long if you follow from the cli command, so I will only write the main points.
Since it is the Vite version, when you look at the Vite configuration vite.config.ts, you will see the following description for Cloudflare called cloudflareDevProxyVitePlugin.

import {
  vitePlugin as remix,
  cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [
    remixCloudflareDevProxy(),
    remix(),
    tsconfigPaths()
  ],
});
Enter fullscreen mode Exit fullscreen mode

In this guy's code, we get to the reality of Vite Server, a so-called development environment.

https://github.com/remix-run/remix/blob/df0a668d416014f19313419dc7701ddcbe4ee312/packages/remix-dev/vite/cloudflare-proxy-plugin.ts#L59-L85

In this configureServer, we call the wranlger API called getPlatformProxy. It is no exaggeration to say that this is the reality of remix vite:dev.

https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy

Simply put, wrangler acts as a proxy, and Vite Server actually runs. So, why does wrangler need to act as a proxy? What is returned from this getPlatformProxy is that running Cloudflare's own services (such as D1, R2, KV, etc.) on the Vite Server side returns the API. That is a major feature. I wrote a program to access D1 in the Remix code in the development environment, and it works on Vite Server because Remix on Vite Server runs the Cloudflare environment API that was proxied with getPlatformProxy.

Notes on remix vite:dev

If you have read this far and have the following questions, you are in good hands.

I want to check the operation in a workerd environment.

that's right. It's still the same as wrangler providing Cloudflare's API and running on Vite Server, that is, Node server. Therefore, since it is not a wranglerd environment, code that depends on Node's API will run without doing anything.
I think that's one of the reasons why the start npm script remains in order to finally check the operation. On the start side, we will use wrangler to raise the development environment, so we will check the operation in the workerd environment.

“Then why don’t we just say start?”

I certainly think so. However, there are other problems with start, and you need to be careful if you use it regularly.

https://github.com/remix-run/remix/issues/7466

Since the Vite version has just been released, there are some issues, but efforts to improve this have already begun, so I think it would be best to wait patiently, or since it is OSS, it would be better to cooperate.

Build process up to deployment in Vite version

The method of starting the development environment has certainly changed with Vite, but the main focus is building. Since it is a Vite version, of course the build will be done with Vite. By the way, the conventional version is built using esbuild.

The reality of remix vite:build

Previously, Remix generated various options for building with esbuild and built it, but I will write about how it is done in the Vite version. This can also be seen by reading from vite.config.ts in the same way as remix vite:dev.

import {
  vitePlugin as remix,
  cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  plugins: [
    remixCloudflareDevProxy(),
    remix(),
    tsconfigPaths()
  ],
});
Enter fullscreen mode Exit fullscreen mode

Here we set something called vitePlugin as a Vite plugin. This is the main body that contains the settings for building.

https://github.com/remix-run/remix/blob/df0a668d416014f19313419dc7701ddcbe4ee312/packages/remix-dev/vite/plugin.ts#L530

This will return multiple Vite plugin configurations for building Remixes. Among them, the following four are the ones you should read if you want to know about build-related processes.

  1. name: "remix": which returns the Vite build config settings
  2. name: "remix-virtual-modules": Regenerates server-side code to build only Remix server-side code.
  3. name: "remix-dot-server": Validates Remix's .server file from being mixed into client code.
  4. name: "remix-dot-client": Validates Remix's .client file in server code.

So, if you want to customize the options for building with Vite, you can also change the return value of name: "remix" that is returned at the beginning of the array.
If you have been confused about not being able to change the build settings of esbuild until now, please try changing to the Vite version, as you can change the build as you like.

This will build it with Vite and generate client code and server code in build/client/ and build/server/.

By the way, in order to build for the server, I am doing something quite clumsy to build the files in app/routes as one file.

https://github.com/remix-run/remix/blob/df0a668d416014f19313419dc7701ddcbe4ee312/packages/remix-dev/vite/plugin.ts#L704-L750

As you can see, the build method has changed in the Vite version, not only for Cloudflare.

Build for Cloudflare Pages

Just having the results built with Vite will not work with Cloudflare Pages. If you want to perform server-side processing, you will need a function called Cloudflare Pages Functions that links Cloudflare Pages and Cloudflare Workers.

In the previous version, functions/[[path]].js, which is Cloudflare Pages Functions, was generated by building with Remix. The Vite version hasn't gotten this far yet.

However, the Vite version provides a file called functions/[[path]].ts from the beginning.

import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
// eslint-disable-next-line import/no-unresolved
import * as build from "../build/server";

export const onRequest = createPagesFunctionHandler({ build });
Enter fullscreen mode Exit fullscreen mode

In the previous version, it was used as a place to place the JavaScript result of the build, but in the Vite version, TypeScript has been provided from the beginning, and build/server/index.js is loaded in it. wrangler (pages) recognizes a directory called functions/ as Cloudflare Pages Functions, but it supports not only JavaScript but also TypeScript.

https://developers.cloudflare.com/pages/functions/typescript/

So, by doing this, we changed the method to generate Cloudflare Pages Functions that include the built JavaScript of Remix's server-side processing when running wrangler pages dev and wrangler pages deploy.

Note that after all, you need to remix vite:build before start(wranlger pages dev) will work.

As you can see, Cloudflare Pages compatible with the Vite version of Remix was made possible not only by Remix, but also by Wrangler and Vite.
Although there are some issues, the development experience with Remix has been further improved by moving to Vite, so please give it a try.

💖 💪 🙅 🚩
chimame
chimame

Posted on March 1, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related