forked from remix-run/remix
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor(dev): builds return the updated assets manifest * refactor(dev): inject live reload port during build * feat(dev): new dev server via future flag * test(dev): update tests for new dev server * Create mean-clocks-bow.md
- Loading branch information
1 parent
fc4f474
commit 322a7a8
Showing
22 changed files
with
277 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
"remix": minor | ||
"@remix-run/dev": minor | ||
"@remix-run/react": minor | ||
"@remix-run/serve": minor | ||
"@remix-run/server-runtime": minor | ||
--- | ||
|
||
# The new dev server | ||
|
||
The new dev flow is to spin up the dev server _alongside_ your normal Remix app server: | ||
|
||
```sh | ||
# spin up the new dev server | ||
remix dev | ||
|
||
# spin up your app server in a separate tab or via `concurrently` | ||
nodemon ./server.js | ||
``` | ||
|
||
The dev server will build your app in dev mode and then rebuild whenever any app files change. | ||
It will also wait for your app server to be "ready" (more on this later) before triggering a live reload in your browser. | ||
|
||
## Benefits | ||
|
||
- Navigations no longer wipe in-memory references (e.g. database connections, in-memory caches, etc...). That means no need to use `global` trick anymore. | ||
- Supports _any_ app server, not just the Remix App Server. | ||
- Automatically wires up the live reload port for you (no need for you to mess with env vars for that anymore) | ||
|
||
## App server picks up changes | ||
|
||
Use `nodemon` (or similar) so that your app server restarts and picks up changes after a rebuild finishes. | ||
|
||
For example, you can use `wrangler --watch` for Cloudflare. | ||
|
||
Alternatively, you can roll your own with `chokidar` (or similar) if you want to still use the `global` trick to persist in-memory stuff across rebuilds. | ||
|
||
## Configure | ||
|
||
- Dev server port | ||
- flag: `--port` | ||
- future config: `unstable_dev.port` | ||
- default: finds an empty port to use | ||
- App server port | ||
- flag: `--app-server-port` | ||
- future config: `unstable_dev.appServerPort` | ||
- default: `3000` | ||
- Remix request handler path | ||
- Most Remix apps shouldn't need this, but if you wire up the Remix request handler at a specific URL path set this to that path so that the dev server can reliably check your app server for "readiness" | ||
- future flag: `unstable_dev.remixRequestHandlerPath` | ||
- default: `''` | ||
- Rebuild poll interval (milliseconds) | ||
- future config: `unstable_dev.rebuildPollIntervalMs` | ||
- default: 50ms |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import getPort, { makeRange } from "get-port"; | ||
import os from "os"; | ||
import path from "node:path"; | ||
import prettyMs from "pretty-ms"; | ||
import fetch from "node-fetch"; | ||
|
||
import { type AssetsManifest } from "./assets-manifest"; | ||
import * as Compiler from "./compiler"; | ||
import { type RemixConfig } from "./config"; | ||
import { loadEnv } from "./env"; | ||
import * as LiveReload from "./liveReload"; | ||
|
||
let info = (message: string) => console.info(`💿 ${message}`); | ||
|
||
let relativePath = (file: string) => path.relative(process.cwd(), file); | ||
|
||
let sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); | ||
|
||
let getHost = () => | ||
process.env.HOST ?? | ||
Object.values(os.networkInterfaces()) | ||
.flat() | ||
.find((ip) => String(ip?.family).includes("4") && !ip?.internal)?.address; | ||
|
||
let findPort = async (portPreference?: number) => | ||
getPort({ | ||
port: | ||
// prettier-ignore | ||
portPreference ? Number(portPreference) : | ||
process.env.PORT ? Number(process.env.PORT) : | ||
makeRange(3001, 3100), | ||
}); | ||
|
||
let fetchAssetsManifest = async ( | ||
origin: string, | ||
remixRequestHandlerPath: string | ||
): Promise<AssetsManifest | undefined> => { | ||
try { | ||
let url = origin + remixRequestHandlerPath + "/__REMIX_ASSETS_MANIFEST"; | ||
let res = await fetch(url); | ||
let assetsManifest = (await res.json()) as AssetsManifest; | ||
return assetsManifest; | ||
} catch (error) { | ||
return undefined; | ||
} | ||
}; | ||
|
||
export let serve = async ( | ||
config: RemixConfig, | ||
flags: { port?: number; appServerPort?: number } = {} | ||
) => { | ||
await loadEnv(config.rootDirectory); | ||
|
||
let { unstable_dev } = config.future; | ||
if (unstable_dev === false) | ||
throw Error("The new dev server requires 'unstable_dev' to be set"); | ||
let { remixRequestHandlerPath, rebuildPollIntervalMs } = unstable_dev; | ||
let appServerPort = flags.appServerPort ?? unstable_dev.appServerPort ?? 3000; | ||
|
||
let host = getHost(); | ||
let appServerOrigin = `http://${host ?? "localhost"}:${appServerPort}`; | ||
|
||
let waitForAppServer = async (buildHash: string) => { | ||
while (true) { | ||
// TODO AbortController signal to cancel responses? | ||
let assetsManifest = await fetchAssetsManifest( | ||
appServerOrigin, | ||
remixRequestHandlerPath ?? "" | ||
); | ||
if (assetsManifest?.version === buildHash) return; | ||
|
||
await sleep(rebuildPollIntervalMs ?? 50); | ||
} | ||
}; | ||
|
||
// watch and live reload on rebuilds | ||
let port = await findPort(flags.port ?? unstable_dev.port); | ||
let socket = LiveReload.serve({ port }); | ||
let dispose = await Compiler.watch(config, { | ||
mode: "development", | ||
liveReloadPort: port, | ||
onInitialBuild: (durationMs) => info(`Built in ${prettyMs(durationMs)}`), | ||
onRebuildStart: () => socket.log("Rebuilding..."), | ||
onRebuildFinish: async (durationMs, assetsManifest) => { | ||
if (!assetsManifest) return; | ||
socket.log(`Rebuilt in ${prettyMs(durationMs)}`); | ||
|
||
info(`Waiting for ${appServerOrigin}...`); | ||
let start = Date.now(); | ||
await waitForAppServer(assetsManifest.version); | ||
info(`${appServerOrigin} ready in ${prettyMs(Date.now() - start)}`); | ||
|
||
socket.reload(); | ||
}, | ||
onFileCreated: (file) => socket.log(`File created: ${relativePath(file)}`), | ||
onFileChanged: (file) => socket.log(`File changed: ${relativePath(file)}`), | ||
onFileDeleted: (file) => socket.log(`File deleted: ${relativePath(file)}`), | ||
}); | ||
|
||
// TODO exit hook: clean up assetsBuildDirectory and serverBuildPath? | ||
|
||
return async () => { | ||
await dispose(); | ||
socket.close(); | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import WebSocket from "ws"; | ||
|
||
type Message = { type: "RELOAD" } | { type: "LOG"; message: string }; | ||
|
||
type Broadcast = (message: Message) => void; | ||
|
||
export let serve = (options: { port: number }) => { | ||
let wss = new WebSocket.Server({ port: options.port }); | ||
|
||
let broadcast: Broadcast = (message) => { | ||
wss.clients.forEach((client) => { | ||
if (client.readyState === WebSocket.OPEN) { | ||
client.send(JSON.stringify(message)); | ||
} | ||
}); | ||
}; | ||
|
||
let reload = () => broadcast({ type: "RELOAD" }); | ||
|
||
let log = (messageText: string) => { | ||
let _message = `💿 ${messageText}`; | ||
console.log(_message); | ||
broadcast({ type: "LOG", message: _message }); | ||
}; | ||
|
||
return { reload, log, close: wss.close }; | ||
}; |
Oops, something went wrong.