Skip to content

Commit ea1f97b

Browse files
committed
Merge branch 'v7' into markdalgleish/remove-build-end-public-path
2 parents 73c0f44 + b2c8f9c commit ea1f97b

11 files changed

+176
-145
lines changed

.changeset/remove-manifest-option.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
"@react-router/dev": major
3+
---
4+
5+
For Remix consumers migrating to React Router, the Vite plugin's `manifest` option has been removed.
6+
7+
The `manifest` option been superseded by the more powerful `buildEnd` hook since it's passed the `buildManifest` argument. You can still write the build manifest to disk if needed, but you'll most likely find it more convenient to write any logic depending on the build manifest within the `buildEnd` hook itself.
8+
9+
If you were using the `manifest` option, you can replace it with a `buildEnd` hook that writes the manifest to disk like this:
10+
11+
```js
12+
import { vitePlugin as reactRouter } from "@react-router/dev";
13+
import { writeFile } from "node:fs/promises";
14+
15+
export default {
16+
plugins: [
17+
reactRouter({
18+
async buildEnd({ buildManifest }) {
19+
await writeFile(
20+
"build/manifest.json",
21+
JSON.stringify(buildManifest, null, 2),
22+
"utf-8"
23+
);
24+
},
25+
}),
26+
],
27+
};
28+
```

.changeset/vite-manifest-location.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@react-router/dev": major
3+
---
4+
5+
For Remix consumers migrating to React Router, Vite manifests (i.e. `.vite/manifest.json`) are now written within each build subdirectory, e.g. `build/client/.vite/manifest.json` and `build/server/.vite/manifest.json` instead of `build/.vite/client-manifest.json` and `build/.vite/server-manifest.json`. This means that the build output is now much closer to what you'd expect from a typical Vite project.
6+
7+
Originally the Remix Vite plugin moved all Vite manifests to a root-level `build/.vite` directory to avoid accidentally serving them in production, particularly from the client build. This was later improved with additional logic that deleted these Vite manifest files at the end of the build process unless Vite's `build.manifest` had been enabled within the app's Vite config. This greatly reduced the risk of accidentally serving the Vite manifests in production since they're only present when explicitly asked for. As a result, we can now assume that consumers will know that they need to manage these additional files themselves, and React Router can safely generate a more standard Vite build output.

integration/helpers/vite-template/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
"@types/react": "^18.2.20",
3030
"@types/react-dom": "^18.2.7",
3131
"eslint": "^8.38.0",
32-
"node-fetch": "^3.3.2",
3332
"typescript": "^5.1.6",
3433
"vite": "^5.1.0",
3534
"vite-env-only": "^2.0.0",

integration/vite-manifests-test.ts

+29-22
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import dedent from "dedent";
66

77
import { createProject, build, viteConfig } from "./helpers/vite.js";
88

9+
const js = String.raw;
10+
911
function createRoute(path: string) {
1012
return {
11-
[`app/routes/${path}`]: `
13+
[`app/routes/${path}`]: js`
1214
export default function Route() {
1315
return <p>Path: ${path}</p>;
1416
}
@@ -23,7 +25,7 @@ const TEST_ROUTES = [
2325
];
2426

2527
const files = {
26-
"app/root.tsx": `
28+
"app/root.tsx": js`
2729
import { Links, Meta, Outlet, Scripts } from "react-router-dom";
2830
2931
export default function Root() {
@@ -49,36 +51,43 @@ test.describe(() => {
4951

5052
test.beforeAll(async () => {
5153
cwd = await createProject({
52-
"vite.config.ts": dedent`
54+
"vite.config.ts": dedent(js`
5355
import { vitePlugin as reactRouter } from "@react-router/dev";
5456
5557
export default {
5658
build: { manifest: true },
57-
plugins: [reactRouter({ manifest: true })],
59+
plugins: [reactRouter({
60+
buildEnd: async ({ buildManifest }) => {
61+
let fs = await import("node:fs");
62+
await fs.promises.writeFile(
63+
"build/test-manifest.json",
64+
JSON.stringify(buildManifest, null, 2),
65+
"utf-8",
66+
);
67+
},
68+
})],
5869
}
59-
`,
70+
`),
6071
...files,
6172
});
6273

6374
build({ cwd });
6475
});
6576

6677
test("Vite / manifests enabled / Vite manifests", () => {
67-
let viteManifestFiles = fs.readdirSync(path.join(cwd, "build", ".vite"));
78+
let viteManifestFilesClient = fs.readdirSync(
79+
path.join(cwd, "build", "client", ".vite")
80+
);
81+
expect(viteManifestFilesClient).toEqual(["manifest.json"]);
6882

69-
expect(viteManifestFiles).toEqual([
70-
"client-manifest.json",
71-
"server-manifest.json",
72-
]);
83+
let viteManifestFilesServer = fs.readdirSync(
84+
path.join(cwd, "build", "server", ".vite")
85+
);
86+
expect(viteManifestFilesServer).toEqual(["manifest.json"]);
7387
});
7488

7589
test("Vite / manifests enabled / React Router build manifest", async () => {
76-
let manifestPath = path.join(
77-
cwd,
78-
"build",
79-
".react-router",
80-
"manifest.json"
81-
);
90+
let manifestPath = path.join(cwd, "build", "test-manifest.json");
8291
expect(JSON.parse(fs.readFileSync(manifestPath, "utf8"))).toEqual({
8392
routes: {
8493
root: {
@@ -122,12 +131,10 @@ test.describe(() => {
122131
});
123132

124133
test("Vite / manifest disabled / Vite manifests", () => {
125-
let manifestDir = path.join(cwd, "build", ".vite");
126-
expect(fs.existsSync(manifestDir)).toBe(false);
127-
});
134+
let manifestDirClient = path.join(cwd, "build", "client", ".vite");
135+
expect(fs.existsSync(manifestDirClient)).toBe(false);
128136

129-
test("Vite / manifest disabled / React Router build manifest doesn't exist", async () => {
130-
let manifestDir = path.join(cwd, "build", ".react-router");
131-
expect(fs.existsSync(manifestDir)).toBe(false);
137+
let manifestDirServer = path.join(cwd, "build", "server", ".vite");
138+
expect(fs.existsSync(manifestDirServer)).toBe(false);
132139
});
133140
});

integration/vite-prerender-test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ test.describe("Prerendering", () => {
124124

125125
let clientDir = path.join(fixture.projectDir, "build", "client");
126126
expect(fs.readdirSync(clientDir).sort()).toEqual([
127+
".vite",
127128
"_root.data",
128129
"about",
129130
"about.data",
@@ -176,6 +177,7 @@ test.describe("Prerendering", () => {
176177

177178
let clientDir = path.join(fixture.projectDir, "build", "client");
178179
expect(fs.readdirSync(clientDir).sort()).toEqual([
180+
".vite",
179181
"_root.data",
180182
"about",
181183
"about.data",

integration/vite-presets-test.ts

-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ test("Vite / presets", async () => {
213213
"buildDirectory",
214214
"buildEnd",
215215
"future",
216-
"manifest",
217216
"prerender",
218217
"routes",
219218
"serverBuildFile",

integration/vite-server-bundles-test.ts

+26-20
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
viteConfig,
1313
} from "./helpers/vite.js";
1414

15+
const js = String.raw;
16+
1517
const withBundleServer = async (
1618
cwd: string,
1719
serverBundle: string,
@@ -27,7 +29,7 @@ const ROUTE_FILE_COMMENT = "// THIS IS A ROUTE FILE";
2729

2830
function createRoute(path: string) {
2931
return {
30-
[`app/routes/${path}`]: `
32+
[`app/routes/${path}`]: js`
3133
${ROUTE_FILE_COMMENT}
3234
import { Outlet } from "react-router-dom";
3335
import { useState, useEffect } from "react";
@@ -73,7 +75,7 @@ const TEST_ROUTES = [
7375
];
7476

7577
const files = {
76-
"app/root.tsx": `
78+
"app/root.tsx": js`
7779
${ROUTE_FILE_COMMENT}
7880
import { Links, Meta, Outlet, Scripts } from "react-router-dom";
7981
@@ -118,14 +120,20 @@ test.describe(() => {
118120
test.beforeAll(async () => {
119121
port = await getPort();
120122
cwd = await createProject({
121-
"vite.config.ts": dedent`
123+
"vite.config.ts": dedent(js`
122124
import { vitePlugin as reactRouter } from "@react-router/dev";
123125
124126
export default {
125127
${await viteConfig.server({ port })}
126128
build: { manifest: true },
127129
plugins: [reactRouter({
128-
manifest: true,
130+
buildEnd: async ({ buildManifest }) => {
131+
let fs = await import("node:fs");
132+
await fs.promises.writeFile(
133+
"build/test-manifest.json",
134+
JSON.stringify(buildManifest, null, 2)
135+
);
136+
},
129137
serverBundles: async ({ branch }) => {
130138
// Smoke test to ensure we can read the route files via 'route.file'
131139
await Promise.all(branch.map(async (route) => {
@@ -158,7 +166,7 @@ test.describe(() => {
158166
}
159167
})]
160168
}
161-
`,
169+
`),
162170
...files,
163171
});
164172
});
@@ -306,24 +314,22 @@ test.describe(() => {
306314
});
307315

308316
test("Vite / server bundles / build / Vite manifests", () => {
309-
let viteManifestFiles = fs.readdirSync(path.join(cwd, "build", ".vite"));
310-
311-
expect(viteManifestFiles).toEqual([
312-
"client-manifest.json",
313-
"server-bundle-a-manifest.json",
314-
"server-bundle-b-manifest.json",
315-
"server-bundle-c-manifest.json",
316-
"server-root-manifest.json",
317-
]);
317+
[
318+
["client"],
319+
["server", "bundle-a"],
320+
["server", "bundle-b"],
321+
["server", "bundle-c"],
322+
["server", "root"],
323+
].forEach((buildPaths) => {
324+
let viteManifestFiles = fs.readdirSync(
325+
path.join(cwd, "build", ...buildPaths, ".vite")
326+
);
327+
expect(viteManifestFiles).toEqual(["manifest.json"]);
328+
});
318329
});
319330

320331
test("Vite / server bundles / build / React Router build manifest", () => {
321-
let manifestPath = path.join(
322-
cwd,
323-
"build",
324-
".react-router",
325-
"manifest.json"
326-
);
332+
let manifestPath = path.join(cwd, "build", "test-manifest.json");
327333
expect(JSON.parse(fs.readFileSync(manifestPath, "utf8"))).toEqual({
328334
serverBundles: {
329335
"bundle-c": {

integration/vite-spa-mode-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -964,10 +964,10 @@ test.describe("SPA Mode", () => {
964964

965965
test("only generates client Vite manifest", () => {
966966
let viteManifestFiles = fs.readdirSync(
967-
path.join(fixture.projectDir, "build", ".vite")
967+
path.join(fixture.projectDir, "build", "client", ".vite")
968968
);
969969

970-
expect(viteManifestFiles).toEqual(["client-manifest.json"]);
970+
expect(viteManifestFiles).toEqual(["manifest.json"]);
971971
});
972972
});
973973
});

packages/remix-dev/config.ts

-13
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,6 @@ export type VitePluginConfig = {
137137
* A function that is called after the full React Router build is complete.
138138
*/
139139
buildEnd?: BuildEndHook;
140-
/**
141-
* Whether to write a `"manifest.json"` file to the build directory.`
142-
* Defaults to `false`.
143-
*/
144-
manifest?: boolean;
145140
/**
146141
* An array of URLs to prerender to HTML files at build time. Can also be a
147142
* function returning an array to dynamically generate URLs.
@@ -194,11 +189,6 @@ export type ResolvedVitePluginConfig = Readonly<{
194189
* Enabled future flags
195190
*/
196191
future: FutureConfig;
197-
/**
198-
* Whether to write a `"manifest.json"` file to the build directory.`
199-
* Defaults to `false`.
200-
*/
201-
manifest: boolean;
202192
/**
203193
* An array of URLs to prerender to HTML files at build time.
204194
*/
@@ -363,7 +353,6 @@ export async function resolveReactRouterConfig({
363353
let defaults = {
364354
basename: "/",
365355
buildDirectory: "build",
366-
manifest: false,
367356
serverBuildFile: "index.js",
368357
serverModuleFormat: "esm",
369358
ssr: true,
@@ -376,7 +365,6 @@ export async function resolveReactRouterConfig({
376365
buildEnd,
377366
future: userFuture,
378367
ignoredRouteFiles,
379-
manifest,
380368
routes: userRoutesFunction,
381369
prerender: prerenderConfig,
382370
serverBuildFile,
@@ -464,7 +452,6 @@ export async function resolveReactRouterConfig({
464452
buildDirectory,
465453
buildEnd,
466454
future,
467-
manifest,
468455
prerender,
469456
routes,
470457
serverBuildFile,

0 commit comments

Comments
 (0)