Skip to content

Instantly share code, notes, and snippets.

@jacob-ebey
Last active October 6, 2023 21:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacob-ebey/a137da89aaeef829a526fd74fea5c9da to your computer and use it in GitHub Desktop.
Save jacob-ebey/a137da89aaeef829a526fd74fea5c9da to your computer and use it in GitHub Desktop.
remix cjs server
const fs = require("node:fs");
const { createRequestHandler } = require("@remix-run/express");
const { broadcastDevReady, installGlobals } = require("@remix-run/node");
const compression = require("compression");
const express = require("express");
const morgan = require("morgan");
const sourceMapSupport = require("source-map-support");
sourceMapSupport.install();
installGlobals();
const BUILD_PATH = require.resolve("./build/index.js");
start();
async function start() {
/** @typedef {import('@remix-run/node').ServerBuild} ServerBuild */
const initialBuild = await reimportServer();
const remixHandler =
process.env.NODE_ENV === "development"
? await createDevRequestHandler(initialBuild)
: createRequestHandler({
build: initialBuild,
mode: initialBuild.mode,
});
const app = express();
app.use(compression());
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
app.disable("x-powered-by");
// Remix fingerprints its assets so we can cache forever.
app.use(
"/build",
express.static("public/build", { immutable: true, maxAge: "1y" })
);
// Everything else (like favicon.ico) is cached for an hour. You may want to be
// more aggressive with this caching.
app.use(express.static("public", { maxAge: "1h" }));
app.use(morgan("tiny"));
app.all("*", remixHandler);
const port = process.env.PORT || 3000;
app.listen(port, async () => {
console.log(`Express server listening on port ${port}`);
if (process.env.NODE_ENV === "development") {
broadcastDevReady(initialBuild);
}
});
}
var lastUpdated = null;
/**
* @returns {Promise<ServerBuild>}
*/
async function reimportServer() {
const stat = fs.statSync(BUILD_PATH);
if (lastUpdated !== stat.mtimeMs) {
// bust the require cache
delete require.cache[BUILD_PATH];
}
return require(BUILD_PATH);
}
/**
* @param {ServerBuild} initialBuild
* @returns {Promise<import('@remix-run/express').RequestHandler>}
*/
async 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);
}
const chokidar = await import("chokidar");
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,
mode: "development",
})(req, res, next);
} catch (error) {
next(error);
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment