Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions packages/react-server/lib/dev/create-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ export default async function createServer(root, options) {
},
},
rsc: {
resolve: {
conditions: ["react-server"],
},
dev: {
createEnvironment: (name, config) =>
createRunnableDevEnvironment(name, config, {
Expand Down Expand Up @@ -541,6 +544,77 @@ export default async function createServer(root, options) {
const viteDevServer = await createViteDevServer(viteConfig);
viteCreateSpan.end();

// Inject a Connect-level CORS middleware at the very front of the stack so
// that Vite-handled requests (module transforms, static assets, HMR) also
// receive proper CORS headers. The react-server CORS middleware in the
// composed handler chain only covers requests that reach the SSR handler,
// but Vite's internal middlewares respond earlier and would otherwise send
// responses without any Access-Control-* headers.
if (corsEnabled) {
const _serverCors = serverCors || {};
const _originFn =
typeof _serverCors.origin === "function" ? _serverCors.origin : null;
const _staticOrigin = _originFn ? null : (_serverCors.origin ?? "*");
const _credentials = _serverCors.credentials ?? false;

// unshift onto Connect's stack so this runs before all Vite-internal
// middlewares (which are already registered by createViteDevServer).
viteDevServer.middlewares.stack.unshift({
route: "",
handle: function viteCorsShim(req, res, next) {
const requestOrigin = req.headers.origin;
if (!requestOrigin) return next();

let allowed;
if (_originFn) {
// The origin function expects a context-like object; build a minimal
// shim that matches what the react-server CORS middleware receives.
allowed = _originFn({
request: {
headers: {
get: (name) => req.headers[name.toLowerCase()],
},
},
});
} else {
allowed = _staticOrigin === true ? requestOrigin : _staticOrigin;
}

// allowed may be a promise when using the default dynamic origin
Promise.resolve(allowed).then((origin) => {
const effectiveOrigin =
origin === true ? requestOrigin : origin || requestOrigin;
res.setHeader("access-control-allow-origin", effectiveOrigin);
if (_credentials) {
res.setHeader("access-control-allow-credentials", "true");
}
if (req.method === "OPTIONS") {
res.setHeader(
"access-control-allow-methods",
_serverCors.allowMethods || "GET,HEAD,PUT,PATCH,POST,DELETE"
);
const allowHeaders =
_serverCors.allowHeaders ||
req.headers["access-control-request-headers"];
if (allowHeaders) {
res.setHeader("access-control-allow-headers", allowHeaders);
}
if (_serverCors.maxAge) {
res.setHeader(
"access-control-max-age",
String(_serverCors.maxAge)
);
}
res.statusCode = 204;
res.end();
return;
}
next();
});
},
});
}

if (config.envDir !== false) {
if (globalThis.__react_server_prev_env_keys__) {
for (const key of globalThis.__react_server_prev_env_keys__) {
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions test/__test__/basic.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,15 @@ test.skipIf(process.env.EDGE_ENTRY)(
}
);

test("react-server export condition", async () => {
await server("fixtures/react-server-condition.jsx");
await page.goto(hostname);
expect(await page.textContent("#message")).toBe(
"from react-server condition"
);
expect(await page.textContent("#source")).toBe("server");
});

test("navigation location", async () => {
await server("fixtures/navigation-location.jsx");
await page.goto(`${hostname}/pathname?foo=bar`);
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/react-server-condition-pkg/default.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const message = "from default condition";
export const source = "client";
10 changes: 10 additions & 0 deletions test/fixtures/react-server-condition-pkg/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "react-server-condition-pkg",
"type": "module",
"exports": {
".": {
"react-server": "./server.mjs",
"default": "./default.mjs"
}
}
}
2 changes: 2 additions & 0 deletions test/fixtures/react-server-condition-pkg/server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const message = "from react-server condition";
export const source = "server";
10 changes: 10 additions & 0 deletions test/fixtures/react-server-condition.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { message, source } from "react-server-condition-pkg";

export default function ReactServerCondition() {
return (
<div>
<span id="message">{message}</span>
<span id="source">{source}</span>
</div>
);
}
1 change: 1 addition & 0 deletions test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@lazarv/react-server": "workspace:*",
"idb-keyval": "^6.2.2",
"picomatch": "^4.0.2",
"react-server-condition-pkg": "file:fixtures/react-server-condition-pkg",
"rolldown": "1.0.0-rc.12",
"tinyglobby": "^0.2.13",
"unstorage": "^1.16.0",
Expand Down
Loading