diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts index 72ef162fde5..f0d4ebe3888 100644 --- a/integration/helpers/vite.ts +++ b/integration/helpers/vite.ts @@ -42,7 +42,7 @@ export const EXPRESS_SERVER = (args: { }) => String.raw` import { createRequestHandler } from "@remix-run/express"; - import { installGlobals } from "@remix-run/node"; + import { installGlobals, getServerBuild } from "@remix-run/node"; import express from "express"; installGlobals(); @@ -71,9 +71,7 @@ export const EXPRESS_SERVER = (args: { app.all( "*", createRequestHandler({ - build: viteDevServer - ? () => viteDevServer.ssrLoadModule("virtual:remix/server-build") - : await import("./build/index.js"), + build: await getServerBuild("./build/server/index.js", viteDevServer), getLoadContext: () => (${JSON.stringify(args.loadContext ?? {})}), }) ); diff --git a/integration/vite-basename-test.ts b/integration/vite-basename-test.ts index 115a653cc50..22dae7cac3f 100644 --- a/integration/vite-basename-test.ts +++ b/integration/vite-basename-test.ts @@ -95,7 +95,7 @@ const customServerFile = ({ return String.raw` import { createRequestHandler } from "@remix-run/express"; - import { installGlobals } from "@remix-run/node"; + import { installGlobals, getServerBuild } from "@remix-run/node"; import express from "express"; installGlobals(); @@ -115,9 +115,7 @@ const customServerFile = ({ app.all( "${basename}*", createRequestHandler({ - build: viteDevServer - ? () => viteDevServer.ssrLoadModule("virtual:remix/server-build") - : await import("./build/server/index.js"), + build: await getServerBuild("./build/server/index.js", viteDevServer), }) ); app.get("*", (_req, res) => { diff --git a/packages/remix-cloudflare/index.ts b/packages/remix-cloudflare/index.ts index e9937361263..93e07b41c35 100644 --- a/packages/remix-cloudflare/index.ts +++ b/packages/remix-cloudflare/index.ts @@ -15,6 +15,7 @@ export { defer, broadcastDevReady, logDevReady, + getServerBuild, isCookie, isSession, json, diff --git a/packages/remix-deno/index.ts b/packages/remix-deno/index.ts index 08c67e64f07..f2df2ff6f0f 100644 --- a/packages/remix-deno/index.ts +++ b/packages/remix-deno/index.ts @@ -17,6 +17,7 @@ export { broadcastDevReady, createSession, defer, + getServerBuild, isCookie, isSession, json, diff --git a/packages/remix-node/index.ts b/packages/remix-node/index.ts index 1caccaf7ba0..a53ab959bad 100644 --- a/packages/remix-node/index.ts +++ b/packages/remix-node/index.ts @@ -27,6 +27,7 @@ export { defer, broadcastDevReady, logDevReady, + getServerBuild, isCookie, isSession, json, diff --git a/packages/remix-server-runtime/__tests__/build-test.ts b/packages/remix-server-runtime/__tests__/build-test.ts new file mode 100644 index 00000000000..ec19bd837b9 --- /dev/null +++ b/packages/remix-server-runtime/__tests__/build-test.ts @@ -0,0 +1,15 @@ +import { getServerBuild } from "../build"; + +it("getServerBuild throws when path is relative", async () => { + await expect(() => getServerBuild("./build/server/index.js")).rejects.toThrow( + "Server build path must be absolute, but received relative path: ./build/server/index.js" + ); +}); + +it("getServerBuild throws when build does not exist", async () => { + await expect(() => + getServerBuild("/this/path/doesnt/exist.js") + ).rejects.toThrow( + "Could not import server build from '/this/path/doesnt/exist.js'. Did you forget to run 'remix vite:build' first?" + ); +}); diff --git a/packages/remix-server-runtime/build.ts b/packages/remix-server-runtime/build.ts index 778b627ebb6..9b38b95757d 100644 --- a/packages/remix-server-runtime/build.ts +++ b/packages/remix-server-runtime/build.ts @@ -1,8 +1,41 @@ +import type { ViteDevServer } from "vite"; +import path from "pathe"; + import type { ActionFunctionArgs, LoaderFunctionArgs } from "./routeModules"; import type { AssetsManifest, EntryContext, FutureConfig } from "./entry"; import type { ServerRouteManifest } from "./routes"; import type { AppLoadContext } from "./data"; +export async function getServerBuild( + buildPath: string, + { + viteDevServer, + }: { + viteDevServer?: ViteDevServer; + } = {} +): Promise Promise)> { + if (viteDevServer) { + return () => + viteDevServer.ssrLoadModule( + "virtual:remix/server-build" + ) as Promise; + } + + if (!path.isAbsolute(buildPath)) { + throw new Error( + `Server build path must be absolute, but received relative path: ${buildPath}` + ); + } + + // Convert file path meant for `import` to URL for Windows compatibility + let buildURL = "file:///" + encodeURI(buildPath); + return import(buildURL).catch(() => { + throw Error( + `Could not import server build from '${buildPath}'. Did you forget to run 'remix vite:build' first?` + ); + }); +} + // NOTE: IF you modify `ServerBuild`, be sure to modify the // `remix-dev/server-build.ts` file to reflect the new field as well diff --git a/packages/remix-server-runtime/index.ts b/packages/remix-server-runtime/index.ts index 2d813bf522e..5bd2d9c8ef8 100644 --- a/packages/remix-server-runtime/index.ts +++ b/packages/remix-server-runtime/index.ts @@ -80,3 +80,4 @@ export type { UploadHandler, UploadHandlerPart, } from "./reexport"; +export { getServerBuild } from "./reexport"; diff --git a/packages/remix-server-runtime/package.json b/packages/remix-server-runtime/package.json index b418b48aae0..5e3650941d1 100644 --- a/packages/remix-server-runtime/package.json +++ b/packages/remix-server-runtime/package.json @@ -20,19 +20,25 @@ "@types/cookie": "^0.6.0", "@web3-storage/multipart-parser": "^1.0.0", "cookie": "^0.6.0", + "pathe": "^1.1.2", "set-cookie-parser": "^2.4.8", "source-map": "^0.7.3" }, "devDependencies": { "@types/set-cookie-parser": "^2.4.1", - "typescript": "^5.1.6" + "typescript": "^5.1.6", + "vite": "^5.1.0" }, "peerDependencies": { - "typescript": "^5.1.0" + "typescript": "^5.1.0", + "vite": "^5.1.0" }, "peerDependenciesMeta": { "typescript": { "optional": true + }, + "vite": { + "optional": true } }, "engines": { diff --git a/packages/remix-server-runtime/reexport.ts b/packages/remix-server-runtime/reexport.ts index cc7fc46f507..58beb2c62ef 100644 --- a/packages/remix-server-runtime/reexport.ts +++ b/packages/remix-server-runtime/reexport.ts @@ -7,6 +7,7 @@ export type { ServerBuild, ServerEntryModule, } from "./build"; +export { getServerBuild } from "./build"; export type { UploadHandlerPart, UploadHandler } from "./formData"; export type { diff --git a/packages/remix-server-runtime/tsconfig.json b/packages/remix-server-runtime/tsconfig.json index 9b9f1024aa5..95fea3df9f0 100644 --- a/packages/remix-server-runtime/tsconfig.json +++ b/packages/remix-server-runtime/tsconfig.json @@ -5,6 +5,7 @@ "lib": ["ES2022"], "target": "ES2022", "composite": true, + "module": "ESNext", "moduleResolution": "Bundler", "allowSyntheticDefaultImports": true, diff --git a/yarn.lock b/yarn.lock index bef19c75aa2..c0c0a1d08f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10747,6 +10747,11 @@ pathe@^1.0.0, pathe@^1.1.0: resolved "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz#e2e13f6c62b31a3289af4ba19886c230f295ec03" integrity sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + peek-stream@^1.1.0: version "1.1.3" resolved "https://registry.npmjs.org/peek-stream/-/peek-stream-1.1.3.tgz" @@ -13494,6 +13499,17 @@ vite@5.1.3: optionalDependencies: fsevents "~2.3.2" +vite@^5.1.0: + version "5.1.4" + resolved "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz#14e9d3e7a6e488f36284ef13cebe149f060bcfb6" + integrity sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg== + dependencies: + esbuild "^0.19.3" + postcss "^8.4.35" + rollup "^4.2.0" + optionalDependencies: + fsevents "~2.3.3" + w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073"