diff --git a/.changeset/tidy-bags-listen.md b/.changeset/tidy-bags-listen.md new file mode 100644 index 000000000..b4d9b280a --- /dev/null +++ b/.changeset/tidy-bags-listen.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Prefix production entry scripts, stylesheets, modulepreloads, and serialized manifest paths with Vite's configured base URL. diff --git a/packages/start/src/server/manifest/prod-ssr-manifest.spec.ts b/packages/start/src/server/manifest/prod-ssr-manifest.spec.ts new file mode 100644 index 000000000..583685f02 --- /dev/null +++ b/packages/start/src/server/manifest/prod-ssr-manifest.spec.ts @@ -0,0 +1,82 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("solid-start:client-vite-manifest", () => ({ + clientViteManifest: { + "src/entry-client.tsx": { + file: "_build/assets/entry-client.js", + css: ["_build/assets/entry-client.css"], + imports: ["_shared.js"], + isEntry: true, + }, + "_shared.js": { + file: "_build/assets/shared.js", + }, + }, +})); + +vi.stubEnv("START_CLIENT_ENTRY", "./src/entry-client.tsx"); + +const { getSsrProdManifest } = await import("./prod-ssr-manifest.ts"); + +describe("getSsrProdManifest", () => { + beforeEach(() => { + vi.stubEnv("BASE_URL", "/foo/"); + }); + + it("prefixes entry paths with the Vite base", () => { + expect(getSsrProdManifest().path("./src/entry-client.tsx")).toBe( + "/foo/_build/assets/entry-client.js", + ); + }); + + it("prefixes stylesheet and modulepreload URLs with the Vite base", async () => { + const assets = await getSsrProdManifest().getAssets("./src/entry-client.tsx"); + + expect(assets).toEqual([ + { + tag: "link", + attrs: { + href: "/foo/_build/assets/entry-client.css", + key: "_build/assets/entry-client.css", + rel: "stylesheet", + }, + }, + { + tag: "link", + attrs: { + href: "/foo/_build/assets/shared.js", + key: "_build/assets/shared.js", + rel: "modulepreload", + }, + }, + { + tag: "link", + attrs: { + href: "/foo/_build/assets/entry-client.js", + key: "_build/assets/entry-client.js", + rel: "modulepreload", + }, + }, + ]); + }); + + it("prefixes serialized manifest paths with the Vite base", async () => { + await expect(getSsrProdManifest().json()).resolves.toMatchObject({ + "src/entry-client.tsx": { + output: "/foo/_build/assets/entry-client.js", + }, + }); + }); + + it("preserves root-based URLs when the Vite base is root", async () => { + vi.stubEnv("BASE_URL", "/"); + const manifest = getSsrProdManifest(); + + expect(manifest.path("./src/entry-client.tsx")).toBe("/_build/assets/entry-client.js"); + await expect(manifest.getAssets("./src/entry-client.tsx")).resolves.toMatchObject([ + { attrs: { href: "/_build/assets/entry-client.css" } }, + { attrs: { href: "/_build/assets/shared.js" } }, + { attrs: { href: "/_build/assets/entry-client.js" } }, + ]); + }); +}); diff --git a/packages/start/src/server/manifest/prod-ssr-manifest.ts b/packages/start/src/server/manifest/prod-ssr-manifest.ts index 89d4f02ef..597adc788 100644 --- a/packages/start/src/server/manifest/prod-ssr-manifest.ts +++ b/packages/start/src/server/manifest/prod-ssr-manifest.ts @@ -13,7 +13,7 @@ export function getSsrProdManifest() { const viteManifestEntry = clientViteManifest[id /*import.meta.env.START_CLIENT_ENTRY*/]; if (!viteManifestEntry) throw new Error(`No entry found in vite manifest for '${id}'`); - return join("/", viteManifestEntry.file); + return join(import.meta.env.BASE_URL, viteManifestEntry.file); }, async getAssets(id) { if (id.startsWith("./")) id = id.slice(2); @@ -29,7 +29,7 @@ export function getSsrProdManifest() { for (const entryKey of entryKeys) { json[entryKey] = { - output: join("/", viteManifest[entryKey]!.file), + output: join(import.meta.env.BASE_URL, viteManifest[entryKey]!.file), assets: await this.getAssets(entryKey), }; } @@ -54,7 +54,7 @@ function createHtmlTagsForAssets(assets: string[]) { .map(asset => ({ tag: "link", attrs: { - href: "/" + asset, + href: join(import.meta.env.BASE_URL, asset), key: asset, ...(asset.endsWith(".css") ? { rel: "stylesheet" } : { rel: "modulepreload" }), },