diff --git a/.changeset/itchy-rabbits-fly.md b/.changeset/itchy-rabbits-fly.md
new file mode 100644
index 00000000000..c88e6e0f8be
--- /dev/null
+++ b/.changeset/itchy-rabbits-fly.md
@@ -0,0 +1,5 @@
+---
+"@remix-run/dev": patch
+---
+
+Attach CSS from shared chunks to routes in Vite build
diff --git a/integration/vite-build-test.ts b/integration/vite-build-test.ts
index 3972460e382..df13c3d8513 100644
--- a/integration/vite-build-test.ts
+++ b/integration/vite-build-test.ts
@@ -107,7 +107,7 @@ test.describe("Vite build", () => {
import { useEffect, useState } from "react";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
-
+
import { serverOnly1, serverOnly2 } from "../utils.server";
export const loader = () => {
@@ -138,6 +138,32 @@ test.describe("Vite build", () => {
`,
+ "app/routes/code-split1.tsx": js`
+ import { CodeSplitComponent } from "../code-split-component";
+
+ export default function CodeSplit1Route() {
+ return
;
+ }
+ `,
+ "app/routes/code-split2.tsx": js`
+ import { CodeSplitComponent } from "../code-split-component";
+
+ export default function CodeSplit2Route() {
+ return
;
+ }
+ `,
+ "app/code-split-component.tsx": js`
+ import classes from "./code-split.module.css";
+
+ export function CodeSplitComponent() {
+ return ok
+ }
+ `,
+ "app/code-split.module.css": js`
+ .test {
+ background-color: rgb(255, 170, 0);
+ }
+ `,
},
});
@@ -206,4 +232,26 @@ test.describe("Vite build", () => {
expect(pageErrors).toEqual([]);
});
+
+ test("supports code-split css", async ({ page }) => {
+ let pageErrors: unknown[] = [];
+ page.on("pageerror", (error) => pageErrors.push(error));
+
+ let app = new PlaywrightFixture(appFixture, page);
+ await app.goto("/code-split1");
+ expect(
+ await page
+ .locator("#code-split1 span")
+ .evaluate((e) => window.getComputedStyle(e).backgroundColor)
+ ).toBe("rgb(255, 170, 0)");
+
+ await app.goto("/code-split2");
+ expect(
+ await page
+ .locator("#code-split2 span")
+ .evaluate((e) => window.getComputedStyle(e).backgroundColor)
+ ).toBe("rgb(255, 170, 0)");
+
+ expect(pageErrors).toEqual([]);
+ });
});
diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts
index c3ea928b56e..9f401b7b01c 100644
--- a/packages/remix-dev/vite/plugin.ts
+++ b/packages/remix-dev/vite/plugin.ts
@@ -7,6 +7,7 @@ import {
type Connect,
type Plugin as VitePlugin,
type Manifest as ViteManifest,
+ type ManifestChunk,
type ResolvedConfig as ResolvedViteConfig,
type ViteDevServer,
type UserConfig as ViteUserConfig,
@@ -120,7 +121,7 @@ const getHash = (source: BinaryLike, maxLength?: number): string => {
const resolveBuildAssetPaths = (
pluginConfig: ResolvedRemixVitePluginConfig,
- manifest: ViteManifest,
+ viteManifest: ViteManifest,
absoluteFilePath: string
): Manifest["entry"] & { css: string[] } => {
let rootRelativeFilePath = path.relative(
@@ -128,10 +129,10 @@ const resolveBuildAssetPaths = (
absoluteFilePath
);
let manifestKey = normalizePath(rootRelativeFilePath);
- let manifestEntry = manifest[manifestKey];
+ let entryChunk = viteManifest[manifestKey];
- if (!manifestEntry) {
- let knownManifestKeys = Object.keys(manifest)
+ if (!entryChunk) {
+ let knownManifestKeys = Object.keys(viteManifest)
.map((key) => '"' + key + '"')
.join(", ");
throw new Error(
@@ -139,19 +140,50 @@ const resolveBuildAssetPaths = (
);
}
+ let chunks = resolveDependantChunks(viteManifest, entryChunk);
+
return {
- module: `${pluginConfig.publicPath}${manifestEntry.file}`,
+ module: `${pluginConfig.publicPath}${entryChunk.file}`,
imports:
- manifestEntry.imports?.map((imported) => {
- return `${pluginConfig.publicPath}${manifest[imported].file}`;
+ dedupe(chunks.flatMap((e) => e.imports ?? [])).map((imported) => {
+ return `${pluginConfig.publicPath}${viteManifest[imported].file}`;
}) ?? [],
css:
- manifestEntry.css?.map((href) => {
+ dedupe(chunks.flatMap((e) => e.css ?? [])).map((href) => {
return `${pluginConfig.publicPath}${href}`;
}) ?? [],
};
};
+function resolveDependantChunks(
+ viteManifest: ViteManifest,
+ entryChunk: ManifestChunk
+): ManifestChunk[] {
+ let chunks = new Set();
+
+ function walk(chunk: ManifestChunk) {
+ if (chunks.has(chunk)) {
+ return;
+ }
+
+ if (chunk.imports) {
+ for (let importKey of chunk.imports) {
+ walk(viteManifest[importKey]);
+ }
+ }
+
+ chunks.add(chunk);
+ }
+
+ walk(entryChunk);
+
+ return Array.from(chunks);
+}
+
+function dedupe(array: T[]): T[] {
+ return [...new Set(array)];
+}
+
const writeFileSafe = async (file: string, contents: string): Promise => {
await fs.mkdir(path.dirname(file), { recursive: true });
await fs.writeFile(file, contents);