Skip to content

Commit

Permalink
manual mode updates (#7303)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcattori authored Aug 30, 2023
1 parent 0a33b2c commit 6c4364f
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 44 deletions.
42 changes: 18 additions & 24 deletions docs/guides/manual-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,12 @@ const path = require("node:path");
*/

const BUILD_PATH = path.resolve("./build/index.js");
const initialBuild = reimportServer();

/**
* Initial build
* @type {ServerBuild}
* @returns {ServerBuild}
*/
const build = require(BUILD_PATH);

/**
* @type {() => ServerBuild}
*/
const reimportServer = () => {
function reimportServer() {
// 1. manually remove the server build from the require cache
Object.keys(require.cache).forEach((key) => {
if (key.startsWith(BUILD_PATH)) {
Expand All @@ -109,7 +104,7 @@ const reimportServer = () => {

// 2. re-import the server build
return require(BUILD_PATH);
};
}
```

<docs-info>
Expand All @@ -126,28 +121,27 @@ To workaround this, you can use a timestamp query parameter to force ESM to trea
```js
import * as fs from "node:fs";
import * as path from "node:path";
import * as url from "node:url";

/**
* @typedef {import('@remix-run/node').ServerBuild} ServerBuild
*/

const BUILD_PATH = "./build/index.js";

/**
* Initial build
* @type {ServerBuild}
*/
const build = await import(BUILD_PATH);
const BUILD_PATH = path.resolve("./build/index.js");
const initialBuild = await reimportServer();

/**
* @type {() => Promise<ServerBuild>}
* @returns {Promise<ServerBuild>}
*/
const reimportServer = async () => {
async function reimportServer() {
const stat = fs.statSync(BUILD_PATH);

// convert build path to URL for Windows compatibility with dynamic `import`
const BUILD_URL = url.pathToFileURL(BUILD_PATH).href;

// use a timestamp query parameter to bust the import cache
return import(BUILD_PATH + "?t=" + stat.mtimeMs);
};
return import(BUILD_URL + "?t=" + stat.mtimeMs);
}
```

<docs-warning>
Expand Down Expand Up @@ -188,7 +182,7 @@ app.listen(port, async () => {
console.log(`Express server listening on port ${port}`);

if (process.env.NODE_ENV === "development") {
broadcastDevReady(build);
broadcastDevReady(initialBuild);
}
});
```
Expand Down Expand Up @@ -247,10 +241,10 @@ Now let's plug in our new manual transmission when running in development mode:
app.all(
"*",
process.env.NODE_ENV === "development"
? createDevRequestHandler(build)
? createDevRequestHandler(initialBuild)
: createRequestHandler({
build,
mode: build.mode,
build: initialBuild,
mode: initialBuild.mode,
})
);
```
Expand Down
58 changes: 38 additions & 20 deletions templates/express/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import sourceMapSupport from "source-map-support";
sourceMapSupport.install();
installGlobals();

const BUILD_PATH = url.pathToFileURL(
path.join(process.cwd(), "build", "index.js")
);
/**
* @type { import('@remix-run/node').ServerBuild | Promise<import('@remix-run/node').ServerBuild> }
* @typedef {import('@remix-run/node').ServerBuild} ServerBuild
*/
let build = await import(BUILD_PATH);

const BUILD_PATH = path.resolve("build/index.js");
const initialBuild = await reimportServer();

const app = express();

Expand All @@ -43,10 +42,10 @@ app.use(morgan("tiny"));
app.all(
"*",
process.env.NODE_ENV === "development"
? createDevRequestHandler()
? createDevRequestHandler(initialBuild)
: createRequestHandler({
build,
mode: build.mode,
build: initialBuild,
mode: initialBuild.mode,
})
);

Expand All @@ -55,26 +54,45 @@ app.listen(port, async () => {
console.log(`Express server listening on port ${port}`);

if (process.env.NODE_ENV === "development") {
broadcastDevReady(build);
broadcastDevReady(initialBuild);
}
});

function createDevRequestHandler() {
const watcher = chokidar.watch(BUILD_PATH, { ignoreInitial: true });
/**
* @returns {Promise<ServerBuild>}
*/
async function reimportServer() {
const stat = fs.statSync(BUILD_PATH);

// convert build path to URL for Windows compatibility with dynamic `import`
const BUILD_URL = url.pathToFileURL(BUILD_PATH).href;

watcher.on("all", async () => {
// 1. purge require cache && load updated server build
const stat = fs.statSync(BUILD_PATH);
build = import(BUILD_PATH + "?t=" + stat.mtimeMs);
// 2. tell dev server that this app server is now ready
broadcastDevReady(await build);
});
// use a timestamp query parameter to bust the import cache
return import(BUILD_URL + "?t=" + stat.mtimeMs);
}

/**
* @param {ServerBuild} initialBuild
* @returns {import('@remix-run/express').RequestHandler}
*/
function createDevRequestHandler(initialBuild) {
let build = initialBuild;
async function handleServerUpdate() {
// 1. re-import the server build
build = await reimportServer();
// 2. tell Remix that this app server is now up-to-date and ready
broadcastDevReady(build);
}
chokidar
.watch(BUILD_PATH, { ignoreInitial: true })
.on("add", handleServerUpdate)
.on("change", handleServerUpdate);

// wrap request handler to make sure its recreated with the latest build for every request
return async (req, res, next) => {
try {
//
return createRequestHandler({
build: await build,
build,
mode: "development",
})(req, res, next);
} catch (error) {
Expand Down

0 comments on commit 6c4364f

Please sign in to comment.