Skip to content

Commit 5962867

Browse files
committed
Merge branch 'release-next'
2 parents 5344e4b + e650acf commit 5962867

File tree

44 files changed

+2265
-1102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2265
-1102
lines changed

CHANGELOG.md

Lines changed: 257 additions & 130 deletions
Large diffs are not rendered by default.

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
- JackPriceBurns
170170
- jacob-briscoe
171171
- jacob-ebey
172+
- jadlr
172173
- JaffParker
173174
- jakkku
174175
- JakubDrozd

integration/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Minor Changes
66

77
- Unstable Vite support for Node-based Remix apps ([#7590](https://github.com/remix-run/remix/pull/7590))
8+
89
- `remix build` 👉 `vite build && vite build --ssr`
910
- `remix dev` 👉 `vite dev`
1011

integration/helpers/express.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import tsx from "dedent";
2+
3+
export function server() {
4+
return tsx`
5+
import { createRequestHandler } from "@react-router/express";
6+
import express from "express";
7+
8+
const port = process.env.PORT ?? 3000
9+
const hmrPort = process.env.HMR_PORT ?? 3001
10+
11+
const app = express();
12+
13+
const getLoadContext = () => ({});
14+
15+
if (process.env.NODE_ENV === "production") {
16+
app.use(
17+
"/assets",
18+
express.static("build/client/assets", { immutable: true, maxAge: "1y" })
19+
);
20+
app.use(express.static("build/client", { maxAge: "1h" }));
21+
app.all("*", createRequestHandler({
22+
build: await import("./build/index.js"),
23+
getLoadContext,
24+
}));
25+
} else {
26+
const viteDevServer = await import("vite").then(
27+
(vite) => vite.createServer({
28+
server: {
29+
middlewareMode: true,
30+
hmr: { port: hmrPort },
31+
},
32+
})
33+
);
34+
app.use(viteDevServer.middlewares);
35+
app.all("*", createRequestHandler({
36+
build:() => viteDevServer.ssrLoadModule("virtual:react-router/server-build"),
37+
getLoadContext,
38+
}));
39+
}
40+
41+
app.listen(port, () => console.log('http://localhost:' + port));
42+
`;
43+
}
44+
45+
export function rsc() {
46+
return tsx`
47+
import { createRequestListener } from "@mjackson/node-fetch-server";
48+
import express from "express";
49+
50+
const port = process.env.PORT ?? 3000
51+
const hmrPort = process.env.HMR_PORT ?? 3001
52+
53+
const app = express();
54+
55+
if (process.env.NODE_ENV === "production") {
56+
app.use(
57+
"/assets",
58+
express.static("build/client/assets", { immutable: true, maxAge: "1y" })
59+
);
60+
app.all("*", createRequestListener((await import("./build/server/index.js")).default));
61+
} else {
62+
const viteDevServer = await import("vite").then(
63+
(vite) => vite.createServer({
64+
server: {
65+
middlewareMode: true,
66+
hmr: { port: hmrPort },
67+
},
68+
})
69+
);
70+
app.use(viteDevServer.middlewares);
71+
}
72+
73+
app.listen(port, () => console.log('http://localhost:' + port));
74+
`;
75+
}

integration/helpers/fixtures.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { ChildProcess } from "node:child_process";
2+
import * as fs from "node:fs/promises";
3+
import { fileURLToPath } from "node:url";
4+
5+
import { test as base } from "@playwright/test";
6+
import {
7+
execa,
8+
ExecaError,
9+
type Options,
10+
parseCommandString,
11+
type ResultPromise,
12+
} from "execa";
13+
import * as Path from "pathe";
14+
15+
import type { TemplateName } from "./vite.js";
16+
17+
declare module "@playwright/test" {
18+
interface Page {
19+
errors: Error[];
20+
}
21+
}
22+
23+
const __filename = fileURLToPath(import.meta.url);
24+
const ROOT = Path.join(__filename, "../../..");
25+
const TMP = Path.join(ROOT, ".tmp/integration");
26+
const templatePath = (templateName: string) =>
27+
Path.resolve(ROOT, "integration/helpers", templateName);
28+
29+
type Edits = Record<string, string | ((contents: string) => string)>;
30+
31+
async function applyEdits(cwd: string, edits: Edits) {
32+
const promises = Object.entries(edits).map(async ([file, transform]) => {
33+
const filepath = Path.join(cwd, file);
34+
await fs.writeFile(
35+
filepath,
36+
typeof transform === "function"
37+
? transform(await fs.readFile(filepath, "utf8"))
38+
: transform,
39+
"utf8",
40+
);
41+
return;
42+
});
43+
await Promise.all(promises);
44+
}
45+
46+
export const test = base.extend<{
47+
template: TemplateName;
48+
files: Edits;
49+
cwd: string;
50+
edit: (edits: Edits) => Promise<void>;
51+
$: (
52+
command: string,
53+
options?: Pick<Options, "env" | "timeout">,
54+
) => ResultPromise<{ reject: false }> & {
55+
buffer: { stdout: string; stderr: string };
56+
};
57+
}>({
58+
template: ["vite-6-template", { option: true }],
59+
files: [{}, { option: true }],
60+
page: async ({ page }, use) => {
61+
page.errors = [];
62+
page.on("pageerror", (error: Error) => page.errors.push(error));
63+
await use(page);
64+
},
65+
66+
cwd: async ({ template, files }, use, testInfo) => {
67+
await fs.mkdir(TMP, { recursive: true });
68+
const cwd = await fs.mkdtemp(Path.join(TMP, template + "-"));
69+
testInfo.attach("cwd", { body: cwd });
70+
71+
await fs.cp(templatePath(template), cwd, {
72+
errorOnExist: true,
73+
recursive: true,
74+
});
75+
76+
await applyEdits(cwd, files);
77+
78+
await use(cwd);
79+
},
80+
81+
edit: async ({ cwd }, use) => {
82+
await use(async (edits) => applyEdits(cwd, edits));
83+
},
84+
85+
$: async ({ cwd }, use) => {
86+
const spawn = execa({
87+
cwd,
88+
env: {
89+
NO_COLOR: "1",
90+
FORCE_COLOR: "0",
91+
},
92+
reject: false,
93+
});
94+
95+
let testHasEnded = false;
96+
const processes: Array<ResultPromise> = [];
97+
const unexpectedErrors: Array<Error> = [];
98+
99+
await use((command, options = {}) => {
100+
const [file, ...args] = parseCommandString(command);
101+
102+
const p = spawn(file, args, options);
103+
if (p instanceof ChildProcess) {
104+
processes.push(p);
105+
}
106+
107+
p.then((result) => {
108+
if (!(result instanceof Error)) return result;
109+
110+
// Once the test has ended, this process will be killed as part of its teardown resulting in an ExecaError.
111+
// We only care about surfacing errors that occurred during test execution, not during teardown.
112+
const expectedError = testHasEnded && result instanceof ExecaError;
113+
if (expectedError) return result;
114+
unexpectedErrors.push(result);
115+
});
116+
117+
const buffer = { stdout: "", stderr: "" };
118+
p.stdout?.on("data", (data) => (buffer.stdout += data.toString()));
119+
p.stderr?.on("data", (data) => (buffer.stderr += data.toString()));
120+
return Object.assign(p, { buffer });
121+
});
122+
123+
testHasEnded = true;
124+
processes.forEach((p) => p.kill());
125+
126+
// Throw any unexpected errors that occurred during test execution
127+
if (unexpectedErrors.length > 0) {
128+
const errorMessage =
129+
unexpectedErrors.length === 1
130+
? `Unexpected process error: ${unexpectedErrors[0].message}`
131+
: `${unexpectedErrors.length} unexpected process errors:\n${unexpectedErrors.map((e, i) => `${i + 1}. ${e.message}`).join("\n")}`;
132+
133+
const error = new Error(errorMessage);
134+
error.stack = unexpectedErrors[0].stack;
135+
throw error;
136+
}
137+
},
138+
});

integration/helpers/stream.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { Readable } from "node:stream";
2+
3+
export async function match(
4+
stream: Readable,
5+
pattern: string | RegExp,
6+
options: {
7+
/** Measured in ms */
8+
timeout?: number;
9+
} = {},
10+
): Promise<RegExpMatchArray> {
11+
// Prepare error outside of promise so that stacktrace points to caller of `matchLine`
12+
const timeoutError = new Error(
13+
`Timed out - Could not find pattern: ${pattern}`,
14+
);
15+
return new Promise(async (resolve, reject) => {
16+
const timeout = setTimeout(
17+
() => reject(timeoutError),
18+
options.timeout ?? 10_000,
19+
);
20+
stream.on("data", (data) => {
21+
const line: string = data.toString();
22+
const matches = line.match(pattern);
23+
if (matches) {
24+
resolve(matches);
25+
clearTimeout(timeout);
26+
}
27+
});
28+
});
29+
}

integration/helpers/templates.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const templates = [
2+
// Vite Major templates
3+
{ name: "vite-5-template", displayName: "Vite 5" },
4+
{ name: "vite-6-template", displayName: "Vite 6" },
5+
{ name: "vite-7-beta-template", displayName: "Vite 7 Beta" },
6+
{ name: "vite-rolldown-template", displayName: "Vite Rolldown" },
7+
8+
// RSC templates
9+
{ name: "rsc-vite", displayName: "RSC (Vite)" },
10+
{ name: "rsc-parcel", displayName: "RSC (Parcel)" },
11+
{ name: "rsc-vite-framework", displayName: "RSC Framework" },
12+
13+
// Cloudflare
14+
// { name: "cloudflare-dev-proxy-template", displayName: "Cloudflare Dev Proxy" },
15+
{ name: "vite-plugin-cloudflare-template", displayName: "Cloudflare" },
16+
] as const;
17+
18+
export type Template = (typeof templates)[number];
19+
20+
export function getTemplates(names?: Array<Template["name"]>) {
21+
if (names === undefined) return templates;
22+
return templates.filter(({ name }) => names.includes(name));
23+
}
24+
25+
export const viteMajorTemplates = getTemplates([
26+
"vite-5-template",
27+
"vite-6-template",
28+
"vite-7-beta-template",
29+
"vite-rolldown-template",
30+
]);

integration/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"cheerio": "^1.0.0-rc.12",
2626
"cross-spawn": "^7.0.3",
2727
"dedent": "^0.7.0",
28-
"execa": "^5.1.1",
28+
"execa": "^9.6.0",
2929
"express": "^4.19.2",
3030
"get-port": "^5.1.1",
3131
"glob": "8.0.3",

0 commit comments

Comments
 (0)