Skip to content

Commit

Permalink
chore(changeset): update dev server changeset (#6219)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcattori authored Apr 27, 2023
1 parent f962863 commit b4f1591
Showing 1 changed file with 71 additions and 93 deletions.
164 changes: 71 additions & 93 deletions .changeset/dev-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,46 @@
"@remix-run/server-runtime": minor
---

Dev server improvements
The Remix dev server spins up your app server as a managed subprocess.
This keeps your development environment as close to production as possible.
It also means that the Remix dev server is compatible with _any_ app server.

- Push-based app server syncing that doesn't rely on polling
- App server as a managed subprocess
- Gracefully handle new files and routes without crashing
- Statically serve static assets to avoid fetch errors during app server reboots

# Guide

Enable `unstable_dev` in `remix.config.js`:

```js
{
future: {
"unstable_dev": true
}
}
```

## Remix App Server

Update `package.json` scripts

```json
{
"scripts": {
"dev": "remix dev"
}
}
```

That's it!
By default, the dev server will use the Remix App Server, but you opt to use your own app server by specifying the command to run it via the `-c`/`--command` flag:

```sh
npm run dev
remix dev # uses `remix-serve <serve build path>` as the app server
remix dev -c "node ./server.js" # uses your custom app server at `./server.js`
```

## Other app servers
The dev server will:

Update `package.json` scripts, specifying the command to run you app server with the `-c`/`--command` flag:
- force `NODE_ENV=development` and warn you if it was previously set to something else
- rebuild your app whenever your Remix app code changes
- restart your app server whenever rebuilds succeed
- handle live reload and HMR + Hot Data Revalidation

```json
{
"scripts": {
"dev": "remix dev -c 'node ./server.js'"
}
}
```
### App server coordination

In order to manage your app server, the dev server needs to be told what server build is currently being used by your app server.
This works by having the app server send a "I'm ready!" message with the Remix server build hash as the payload.

Then, call `broadcastDevReady` in your server when its up and running.
This is handled automatically in Remix App Server and is set up for you via calls to `broadcastDevReady` or `logDevReady` in the official Remix templates.

For example, an Express server would call `broadcastDevReady` at the end of `listen`:
If you are not using Remix App Server and your server doesn't call `broadcastDevReady`, you'll need to call it in your app server _after_ it is up and running.
For example, in an Express server:

```js
// server.js
// <other imports>
import { broadcastDevReady } from "@remix-run/node";

// Path to Remix's server build directory ('build/' by default)
let BUILD_DIR = path.join(process.cwd(), "build");
const BUILD_DIR = path.join(process.cwd(), "build");

// <code setting up your express server>

app.listen(3000, () => {
let build = require(BUILD_DIR);
const build = require(BUILD_DIR);
console.log("Ready: http://localhost:" + port);

// in development, call `broadcastDevReady` _after_ your server is up and running
Expand All @@ -76,69 +52,71 @@ app.listen(3000, () => {
});
```

That's it!
### Options

```sh
npm run dev
```
Options priority order is: 1. flags, 2. config, 3. defaults.

# Configuration
| Option | flag | config | default |
| -------------- | ------------------ | ---------------- | --------------------------------- |
| Command | `-c` / `--command` | `command` | `remix-serve <server build path>` |
| HTTP(S) scheme | `--http-scheme` | `httpScheme` | `http` |
| HTTP(S) host | `--http-host` | `httpHost` | `localhost` |
| HTTP(S) port | `--http-port` | `httpPort` | Dynamically chosen open port |
| Websocket port | `--websocket-port` | `websocketPort` | Dynamically chosen open port |
| No restart | `--no-restart` | `restart: false` | `restart: true` |

Most users won't need to configure the dev server, but you might need to if:
🚨 The `--http-*` flags are only used for internal dev server <-> app server communication.
Your app will run on your app server's normal URL.

- You are setting up custom origins for SSL support or for Docker networking
- You want to handle server updates yourself (e.g. via require cache purging)
To set `unstable_dev` configuration, replace `unstable_dev: true` with `unstable_dev: { <options> }`.
For example, to set the HTTP(S) port statically:

```js
{
// remix.config.js
module.exports = {
future: {
unstable_dev: {
// Command to run your app server
command: "wrangler", // default: `remix-serve ./build`
// HTTP(S) scheme used when sending `broadcastDevReady` messages to the dev server
httpScheme: "https", // default: `"http"`
// HTTP(S) host used when sending `broadcastDevReady` messages to the dev server
httpHost: "mycustomhost", // default: `"localhost"`
// HTTP(S) port internally used by the dev server to statically serve built assets and to receive app server `broadcastDevReady` messages
httpPort: 8001, // default: Remix chooses an open port in the range 3001-3099
// Websocket port internally used by the dev server for sending updates to the browser (Live reload, HMR, HDR)
websocketPort: 8002, // default: Remix chooses an open port in the range 3001-3099
// Whether the app server should be restarted when app is rebuilt
// See `Advanced > restart` for more
restart: false, // default: `true`
}
}
}
```

You can also configure via flags. For example:

```sh
remix dev -c 'nodemon ./server.mjs' --http-port=3001 --websocket-port=3002 --no-restart
httpPort: 8001,
},
},
};
```

See `remix dev --help` for more details.

### restart
#### SSL and custom hosts

If you want to manage app server updates yourself, you can use the `--no-restart` flag so that the Remix dev server doesn't restart the app server subprocess when a rebuild succeeds.
You should only need to use the `--http-*` flags and `--websocket-port` flag if you need fine-grain control of what scheme/host/port for the dev server.
If you are setting up SSL or Docker networking, these are the flags you'll want to use.

For example, if you rely on require cache purging to keep your app server running while server changes are pulled in, then you'll want to use `--no-restart`.
🚨 Remix **will not** set up SSL and custom host for you.
The `--http-scheme` and `--http-host` flag are for you to tell Remix how you've set things up.
It is your task to set up SSL certificates and host files if you want those features.

🚨 It is then your responsibility to call `broadcastDevReady` whenever server changes are incorporated in your app server. 🚨
#### `--no-restart` and `require` cache purging

So for require cache purging, you'd want to:
If you want to manage server changes yourself, you can use the `--no-restart` flag to tell the dev server to refrain from restarting your app server when builds succeed:

1. Purge the require cache
2. `require` your server build
3. Call `broadcastDevReady` within a `if (process.env.NODE_ENV === 'development')`

([Looking at you, Kent](https://github.com/kentcdodds/kentcdodds.com/blob/main/server/index.ts#L298) 😆)
```sh
remix dev -c "node ./server.js" --no-restart
```

---
For example, you could purge the `require` cache of your app server to keep it running while picking up server changes.
If you do so, you should watch the server build path (`build/` by default) for changes and only purge the `require` cache when changes are detected.

The ultimate solution for `--no-restart` would be for you to implement _server-side_ HMR for your app server.
Note: server-side HMR is not to be confused with the client-side HMR provided by Remix.
Then your app server could continuously update itself with new build with 0 downtime and without losing in-memory data that wasn't affected by the server changes.
🚨 If you use `--no-restart`, it is your responsibility to call `broadcastDevReady` when your app server has picked up server changes.
For example, with `chokidar`:

This is left as an exercise to the reader.
```js
// server.dev.js
const BUILD_PATH = path.resolve(__dirname, "build");

const watcher = chokidar.watch(BUILD_PATH);

watcher.on("change", () => {
// 1. purge require cache
purgeRequireCache();
// 2. load updated server build
const build = require(BUILD_PATH);
// 3. tell dev server that this app server is now ready
broadcastDevReady(build);
});
```

0 comments on commit b4f1591

Please sign in to comment.