-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
Copy pathcli.ts
152 lines (131 loc) · 3.95 KB
/
cli.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import "@remix-run/node/install";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import url from "node:url";
import {
type ServerBuild,
broadcastDevReady,
installGlobals,
} from "@remix-run/node";
import { type RequestHandler, createRequestHandler } from "@remix-run/express";
import chokidar from "chokidar";
import compression from "compression";
import express from "express";
import morgan from "morgan";
import sourceMapSupport from "source-map-support";
import getPort from "get-port";
process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
sourceMapSupport.install({
retrieveSourceMap: function (source) {
// get source file with the `file://` prefix
let match = source.match(/^file:\/\/(.*)$/);
if (match) {
let filePath = url.fileURLToPath(source);
return {
url: source,
map: fs.readFileSync(`${filePath}.map`, "utf8"),
};
}
return null;
},
});
installGlobals();
run();
function parseNumber(raw?: string) {
if (raw === undefined) return undefined;
let maybe = Number(raw);
if (Number.isNaN(maybe)) return undefined;
return maybe;
}
async function run() {
let port = parseNumber(process.env.PORT) ?? (await getPort({ port: 3000 }));
let buildPathArg = process.argv[2];
if (!buildPathArg) {
console.error(`
Usage: remix-serve <server-build-path> - e.g. remix-serve build/index.js`);
process.exit(1);
}
let buildPath = path.resolve(buildPathArg);
let versionPath = path.resolve(buildPath, "..", "version.txt");
async function reimportServer() {
Object.keys(require.cache).forEach((key) => {
if (key.startsWith(buildPath)) {
delete require.cache[key];
}
});
let stat = fs.statSync(buildPath);
// use a timestamp query parameter to bust the import cache
return import(url.pathToFileURL(buildPath).href + "?t=" + stat.mtimeMs);
}
function createDevRequestHandler(initialBuild: ServerBuild): RequestHandler {
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(versionPath, { 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);
}
};
}
let build: ServerBuild = await reimportServer();
let onListen = () => {
let address =
process.env.HOST ||
Object.values(os.networkInterfaces())
.flat()
.find((ip) => String(ip?.family).includes("4") && !ip?.internal)
?.address;
if (!address) {
console.log(`[remix-serve] http://localhost:${port}`);
} else {
console.log(
`[remix-serve] http://localhost:${port} (http://${address}:${port})`
);
}
if (process.env.NODE_ENV === "development") {
void broadcastDevReady(build);
}
};
let app = express();
app.disable("x-powered-by");
app.use(compression());
app.use(
build.publicPath,
express.static(build.assetsBuildDirectory, {
immutable: true,
maxAge: "1y",
})
);
app.use(express.static("public", { maxAge: "1h" }));
app.use(morgan("tiny"));
app.all(
"*",
process.env.NODE_ENV === "development"
? createDevRequestHandler(build)
: createRequestHandler({
build,
mode: process.env.NODE_ENV,
})
);
let server = process.env.HOST
? app.listen(port, process.env.HOST, onListen)
: app.listen(port, onListen);
["SIGTERM", "SIGINT"].forEach((signal) => {
process.once(signal, () => server?.close(console.error));
});
}