diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts index 54a274f2ded..0280942ad37 100644 --- a/packages/remix-dev/cli/commands.ts +++ b/packages/remix-dev/cli/commands.ts @@ -174,8 +174,8 @@ export async function build( sourcemap, }; if (mode === "development" && config.future.v2_dev) { - let origin = await resolveDevOrigin(config); - options.devOrigin = origin; + let resolved = await resolveDev(config); + options.REMIX_DEV_ORIGIN = resolved.REMIX_DEV_ORIGIN; } let fileWatchCache = createFileWatchCache(); @@ -238,7 +238,8 @@ export async function dev( return await new Promise(() => {}); } - await devServer_unstable.serve(config, await resolveDevServe(config, flags)); + let resolved = await resolveDevServe(config, flags); + await devServer_unstable.serve(config, resolved); } export async function codemod( @@ -467,58 +468,72 @@ let parseMode = ( let findPort = async () => getPort({ port: makeRange(3001, 3100) }); -type DevOrigin = { - scheme: string; - host: string; - port: number; -}; -let resolveDevOrigin = async ( +let resolveDev = async ( config: RemixConfig, - flags: Partial & { + flags: { + port?: number; tlsKey?: string; tlsCert?: string; + /** @deprecated */ + scheme?: string; // TODO: remove in v2 + /** @deprecated */ + host?: string; // TODO: remove in v2 } = {} -): Promise => { +) => { let dev = config.future.v2_dev; if (dev === false) throw Error("This should never happen"); - // prettier-ignore - let scheme = - flags.scheme ?? - (dev === true ? undefined : dev.scheme) ?? - (flags.tlsKey && flags.tlsCert) ? "https": "http"; - // prettier-ignore - let host = - flags.host ?? - (dev === true ? undefined : dev.host) ?? - "localhost"; // prettier-ignore let port = flags.port ?? (dev === true ? undefined : dev.port) ?? (await findPort()); + let tlsKey = flags.tlsKey ?? (dev === true ? undefined : dev.tlsKey); + if (tlsKey) tlsKey = path.resolve(tlsKey); + let tlsCert = flags.tlsCert ?? (dev === true ? undefined : dev.tlsCert); + if (tlsCert) tlsCert = path.resolve(tlsCert); + let isTLS = tlsKey && tlsCert; + + let REMIX_DEV_ORIGIN = process.env.REMIX_DEV_ORIGIN; + if (REMIX_DEV_ORIGIN === undefined) { + // prettier-ignore + let scheme = + flags.scheme ?? // TODO: remove in v2 + (dev === true ? undefined : dev.scheme) ?? // TODO: remove in v2 + isTLS ? "https" : "http"; + // prettier-ignore + let hostname = + flags.host ?? // TODO: remove in v2 + (dev === true ? undefined : dev.host) ?? // TODO: remove in v2 + "localhost"; + REMIX_DEV_ORIGIN = `${scheme}://${hostname}:${port}`; + } + return { - scheme, - host, port, + tlsKey, + tlsCert, + REMIX_DEV_ORIGIN: new URL(REMIX_DEV_ORIGIN), }; }; -type DevServeFlags = DevOrigin & { - command?: string; - restart: boolean; - tlsKey?: string; - tlsCert?: string; -}; let resolveDevServe = async ( config: RemixConfig, - flags: Partial = {} -): Promise => { + flags: { + port?: number; + tlsKey?: string; + tlsCert?: string; + scheme?: string; // TODO: remove in v2 + host?: string; // TODO: remove in v2 + command?: string; + restart?: boolean; + } = {} +) => { let dev = config.future.v2_dev; if (dev === false) throw Error("Cannot resolve dev options"); - let origin = await resolveDevOrigin(config, flags); + let resolved = await resolveDev(config, flags); // prettier-ignore let command = @@ -528,16 +543,9 @@ let resolveDevServe = async ( let restart = flags.restart ?? (dev === true ? undefined : dev.restart) ?? true; - let tlsKey = flags.tlsKey ?? (dev === true ? undefined : dev.tlsKey); - if (tlsKey) tlsKey = path.resolve(tlsKey); - let tlsCert = flags.tlsCert ?? (dev === true ? undefined : dev.tlsCert); - if (tlsCert) tlsCert = path.resolve(tlsCert); - return { + ...resolved, command, - ...origin, restart, - tlsKey, - tlsCert, }; }; diff --git a/packages/remix-dev/cli/run.ts b/packages/remix-dev/cli/run.ts index df1cdcaa4ef..c5fa46eec15 100644 --- a/packages/remix-dev/cli/run.ts +++ b/packages/remix-dev/cli/run.ts @@ -44,8 +44,6 @@ ${colors.logoBlue("R")} ${colors.logoGreen("E")} ${colors.logoYellow( [v2_dev] --command, -c Command used to run your app server - --scheme Scheme for the dev server. Default: http - --host Host for the dev server. Default: localhost --port Port for the dev server. Default: any open port --no-restart Do not restart the app server when rebuilds occur. --tls-key Path to TLS key (key.pem) @@ -183,13 +181,15 @@ export async function run(argv: string[] = process.argv.slice(2)) { // dev server "--command": String, "-c": "--command", - "--scheme": String, - "--host": String, "--port": Number, "-p": "--port", "--no-restart": Boolean, "--tls-key": String, "--tls-cert": String, + + // deprecated, remove in v2 + "--scheme": String, + "--host": String, }, { argv, diff --git a/packages/remix-dev/compiler/js/compiler.ts b/packages/remix-dev/compiler/js/compiler.ts index 7679e636873..42b5cf3f4cb 100644 --- a/packages/remix-dev/compiler/js/compiler.ts +++ b/packages/remix-dev/compiler/js/compiler.ts @@ -136,6 +136,10 @@ const createEsbuildConfig = ( publicPath: ctx.config.publicPath, define: { "process.env.NODE_ENV": JSON.stringify(ctx.options.mode), + "process.env.REMIX_DEV_ORIGIN": JSON.stringify( + ctx.options.REMIX_DEV_ORIGIN ?? "" + ), + // TODO: remove in v2 "process.env.REMIX_DEV_SERVER_WS_PORT": JSON.stringify( ctx.config.devServerPort ), diff --git a/packages/remix-dev/compiler/options.ts b/packages/remix-dev/compiler/options.ts index 072f16ce1e6..2f85f6160e9 100644 --- a/packages/remix-dev/compiler/options.ts +++ b/packages/remix-dev/compiler/options.ts @@ -4,10 +4,5 @@ export type Options = { mode: Mode; sourcemap: boolean; - // TODO: required in v2 - devOrigin?: { - scheme: string; - host: string; - port: number; - }; + REMIX_DEV_ORIGIN?: URL; // TODO: required in v2 }; diff --git a/packages/remix-dev/compiler/server/compiler.ts b/packages/remix-dev/compiler/server/compiler.ts index f1fb0c0d491..cd37e98f1ee 100644 --- a/packages/remix-dev/compiler/server/compiler.ts +++ b/packages/remix-dev/compiler/server/compiler.ts @@ -104,12 +104,16 @@ const createEsbuildConfig = ( publicPath: ctx.config.publicPath, define: { "process.env.NODE_ENV": JSON.stringify(ctx.options.mode), - // TODO: remove REMIX_DEV_SERVER_WS_PORT in v2 + // TODO: remove in v2 "process.env.REMIX_DEV_SERVER_WS_PORT": JSON.stringify( ctx.config.devServerPort ), + "process.env.REMIX_DEV_ORIGIN": JSON.stringify( + ctx.options.REMIX_DEV_ORIGIN ?? "" + ), + // TODO: remove in v2 "process.env.REMIX_DEV_HTTP_ORIGIN": JSON.stringify( - ctx.options.devOrigin ?? "" // TODO: remove nullish check in v2 + ctx.options.REMIX_DEV_ORIGIN ?? "" ), }, jsx: "automatic", diff --git a/packages/remix-dev/compiler/server/plugins/entry.ts b/packages/remix-dev/compiler/server/plugins/entry.ts index 4966dd3ad93..edb4fb60b08 100644 --- a/packages/remix-dev/compiler/server/plugins/entry.ts +++ b/packages/remix-dev/compiler/server/plugins/entry.ts @@ -50,13 +50,6 @@ ${Object.keys(config.routes) export const future = ${JSON.stringify(config.future)}; export const publicPath = ${JSON.stringify(config.publicPath)}; export const entry = { module: entryServer }; - ${ - options.devOrigin - ? `export const dev = ${JSON.stringify({ - port: options.devOrigin.port, - })}` - : "" - } export const routes = { ${Object.keys(config.routes) .map((key, index) => { diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts index 73abaaa16f2..f6084a07501 100644 --- a/packages/remix-dev/config.ts +++ b/packages/remix-dev/config.ts @@ -38,12 +38,15 @@ export type ServerPlatform = "node" | "neutral"; type Dev = { command?: string; - scheme?: string; - host?: string; port?: number; restart?: boolean; tlsKey?: string; tlsCert?: string; + + /** @deprecated remove in v2 */ + scheme?: string; + /** @deprecated remove in v2 */ + host?: string; }; interface FutureConfig { diff --git a/packages/remix-dev/devServer_unstable/index.ts b/packages/remix-dev/devServer_unstable/index.ts index 25cebb061d9..1abeb364142 100644 --- a/packages/remix-dev/devServer_unstable/index.ts +++ b/packages/remix-dev/devServer_unstable/index.ts @@ -24,14 +24,6 @@ import invariant from "../invariant"; import { logger } from "../tux"; import { kill, killtree } from "./proc"; -type Origin = { - scheme: string; - host: string; - port: number; -}; - -let stringifyOrigin = (o: Origin) => `${o.scheme}://${o.host}:${o.port}`; - let detectBin = async (): Promise => { let pkgManager = detectPackageManager() ?? "npm"; if (pkgManager === "npm") { @@ -47,12 +39,11 @@ export let serve = async ( initialConfig: RemixConfig, options: { command?: string; - scheme: string; - host: string; port: number; - restart: boolean; tlsKey?: string; tlsCert?: string; + REMIX_DEV_ORIGIN: URL; + restart: boolean; } ) => { await loadEnv(initialConfig.rootDirectory); @@ -92,12 +83,6 @@ export let serve = async ( : http.createServer(app); let websocket = Socket.serve(server); - let origin: Origin = { - scheme: options.scheme, - host: options.host, - port: options.port, - }; - let bin = await detectBin(); let startAppServer = (command?: string) => { let cmd = @@ -113,7 +98,8 @@ export let serve = async ( NODE_ENV: "development", PATH: bin + (process.platform === "win32" ? ";" : ":") + process.env.PATH, - REMIX_DEV_HTTP_ORIGIN: stringifyOrigin(origin), + REMIX_DEV_ORIGIN: options.REMIX_DEV_ORIGIN.href, + REMIX_DEV_HTTP_ORIGIN: options.REMIX_DEV_ORIGIN.href, // TODO: remove in v2 FORCE_COLOR: process.env.NO_COLOR === undefined ? "1" : "0", }, // https://github.com/sindresorhus/execa/issues/433 @@ -178,7 +164,7 @@ export let serve = async ( options: { mode: "development", sourcemap: true, - devOrigin: origin, + REMIX_DEV_ORIGIN: options.REMIX_DEV_ORIGIN, }, fileWatchCache, logger, @@ -276,7 +262,7 @@ export let serve = async ( } ); - server.listen(origin.port); + server.listen(options.port); return new Promise(() => {}).finally(async () => { state.appServer?.pid && (await kill(state.appServer.pid)); diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx index c77492a548d..ba367468e3c 100644 --- a/packages/remix-react/components.tsx +++ b/packages/remix-react/components.tsx @@ -1782,7 +1782,6 @@ export const LiveReload = process.env.NODE_ENV !== "development" ? () => null : function LiveReload({ - // TODO: remove REMIX_DEV_SERVER_WS_PORT in v2 port, timeoutMs = 1000, nonce = undefined, @@ -1799,13 +1798,25 @@ export const LiveReload = dangerouslySetInnerHTML={{ __html: js` function remixLiveReloadConnect(config) { - let protocol = location.protocol === "https:" ? "wss:" : "ws:"; - let host = location.hostname; - let port = ${port} || (window.__remixContext && window.__remixContext.dev && window.__remixContext.dev.port) || ${Number( - process.env.REMIX_DEV_SERVER_WS_PORT || 8002 - )}; - let socketPath = protocol + "//" + host + ":" + port + "/socket"; - let ws = new WebSocket(socketPath); + let REMIX_DEV_ORIGIN = ${JSON.stringify( + process.env.REMIX_DEV_ORIGIN + )}; + let protocol = + REMIX_DEV_ORIGIN ? new URL(REMIX_DEV_ORIGIN).protocol.replace(/^http/, "ws") : + location.protocol === "https:" ? "wss:" : "ws:"; // remove in v2? + let hostname = location.hostname; + let url = new URL(protocol + "//" + hostname + "/socket"); + + url.port = + ${port} || + REMIX_DEV_ORIGIN ? new URL(REMIX_DEV_ORIGIN).port : + Number(${ + // TODO: remove in v2 + process.env.REMIX_DEV_SERVER_WS_PORT + }) || + 8002; + + let ws = new WebSocket(url.href); ws.onmessage = async (message) => { let event = JSON.parse(message.data); if (event.type === "LOG") { diff --git a/packages/remix-server-runtime/build.ts b/packages/remix-server-runtime/build.ts index 7a5cbc5c606..00c5e82980a 100644 --- a/packages/remix-server-runtime/build.ts +++ b/packages/remix-server-runtime/build.ts @@ -15,7 +15,6 @@ export interface ServerBuild { publicPath: string; assetsBuildDirectory: string; future: FutureConfig; - dev?: { port: number }; } export interface HandleDocumentRequestFunction { diff --git a/packages/remix-server-runtime/dev.ts b/packages/remix-server-runtime/dev.ts index 2163e36b4d3..fe8287eb6bc 100644 --- a/packages/remix-server-runtime/dev.ts +++ b/packages/remix-server-runtime/dev.ts @@ -1,16 +1,25 @@ import type { ServerBuild } from "./build"; -export function broadcastDevReady(build: ServerBuild, origin?: string) { +export async function broadcastDevReady(build: ServerBuild, origin?: string) { origin ??= process.env.REMIX_DEV_HTTP_ORIGIN; if (!origin) throw Error("Dev server origin not set"); + let url = new URL(origin); + url.pathname = "ping"; - fetch(`${origin}/ping`, { + let response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ buildHash: build.assets.version }), }).catch((error) => { - console.error(`Could not reach Remix dev server at ${origin}`); + console.error(`Could not reach Remix dev server at ${url}`); + throw error; }); + if (!response.ok) { + console.error( + `Could not reach Remix dev server at ${url} (${response.status})` + ); + throw Error(await response.text()); + } } export function logDevReady(build: ServerBuild) { diff --git a/packages/remix-server-runtime/serverHandoff.ts b/packages/remix-server-runtime/serverHandoff.ts index 07bdee23107..583f2a21db1 100644 --- a/packages/remix-server-runtime/serverHandoff.ts +++ b/packages/remix-server-runtime/serverHandoff.ts @@ -21,7 +21,6 @@ export function createServerHandoffString(serverHandoff: { state: ValidateShape; url: string; future: FutureConfig; - dev?: { port: number }; }): string { // Uses faster alternative of jsesc to escape data returned from the loaders. // This string is inserted directly into the HTML in the `` element.