rwsdk 1.2.2 → 1.2.4-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ type DecodeReply = (data: string | FormData, moduleMap: null) => Promise<unknown
3
3
  export interface RscActionHandlerDeps {
4
4
  getServerModuleExport: GetServerModuleExport;
5
5
  decodeReply: DecodeReply;
6
+ allowedOrigins?: readonly string[];
6
7
  }
7
- export declare function rscActionHandler(req: Request, { getServerModuleExport, decodeReply }: RscActionHandlerDeps): Promise<unknown>;
8
+ export declare function rscActionHandler(req: Request, { getServerModuleExport, decodeReply, allowedOrigins }: RscActionHandlerDeps): Promise<unknown>;
8
9
  export {};
@@ -2,9 +2,45 @@
2
2
  // The real getServerModuleExport and decodeReply require the react-server environment
3
3
  // (virtual module lookup, react-server-dom-webpack), so we accept them as injected
4
4
  // dependencies. worker.ts passes the real implementations; tests pass fakes.
5
- export async function rscActionHandler(req, { getServerModuleExport, decodeReply }) {
5
+ // context(justinvdm, 2026-04-20): Origin validation for non-GET action requests.
6
+ // Method enforcement alone does not distinguish a legitimate same-origin POST
7
+ // from a POST driven by a same-site sibling origin (e.g. sibling subdomain,
8
+ // another localhost port). SameSite=Lax cookies are attached in both cases, and
9
+ // the action request's Content-Type is CORS-safelisted, so no preflight fires.
10
+ // We require the request's Origin header to match the app's own origin, unless
11
+ // the caller origin is listed in allowedOrigins.
12
+ function validateSameOrigin(req, allowedOrigins) {
13
+ const origin = req.headers.get("Origin");
14
+ if (!origin) {
15
+ return {
16
+ valid: false,
17
+ response: new Response("Missing Origin header", { status: 403 }),
18
+ };
19
+ }
20
+ const selfOrigin = new URL(req.url).origin;
21
+ if (origin === selfOrigin) {
22
+ return { valid: true };
23
+ }
24
+ if (allowedOrigins && allowedOrigins.includes(origin)) {
25
+ return { valid: true };
26
+ }
27
+ return {
28
+ valid: false,
29
+ response: new Response("Origin not allowed", { status: 403 }),
30
+ };
31
+ }
32
+ export async function rscActionHandler(req, { getServerModuleExport, decodeReply, allowedOrigins }) {
6
33
  const url = new URL(req.url);
7
34
  const contentType = req.headers.get("content-type");
35
+ // context(justinvdm, 2026-04-20): Enforce Origin/Host match for non-GET action
36
+ // requests. GET (serverQuery) is expected to be idempotent and is not subject
37
+ // to this check.
38
+ if (req.method !== "GET") {
39
+ const originCheck = validateSameOrigin(req, allowedOrigins);
40
+ if (!originCheck.valid) {
41
+ return originCheck.response;
42
+ }
43
+ }
8
44
  let args = [];
9
45
  if (req.method === "GET") {
10
46
  const argsParam = url.searchParams.get("args");
@@ -6,15 +6,30 @@ function createDeps(action) {
6
6
  decodeReply: vi.fn().mockResolvedValue([]),
7
7
  };
8
8
  }
9
- function makeRequest(url, method, body) {
9
+ function makeRequest(url, method, body, extraHeaders) {
10
10
  const init = { method };
11
+ const headers = {};
11
12
  if (body !== undefined) {
12
13
  init.body = body;
13
- init.headers = { "content-type": "application/json" };
14
+ headers["content-type"] = "application/json";
15
+ }
16
+ if (extraHeaders) {
17
+ Object.assign(headers, extraHeaders);
18
+ }
19
+ if (Object.keys(headers).length > 0) {
20
+ init.headers = headers;
14
21
  }
15
22
  return new Request(url, init);
16
23
  }
17
24
  const ACTION_URL = "https://app.test/page?__rsc_action_id=%2Factions.tsx%23doThing";
25
+ const SELF_ORIGIN = "https://app.test";
26
+ function postWithOrigin(origin) {
27
+ const headers = {};
28
+ if (origin !== undefined) {
29
+ headers["Origin"] = origin;
30
+ }
31
+ return makeRequest(ACTION_URL, "POST", "[]", headers);
32
+ }
18
33
  describe("rscActionHandler method enforcement", () => {
19
34
  it("rejects GET for an action with method POST", async () => {
20
35
  const action = Object.assign(vi.fn(), { method: "POST" });
@@ -30,7 +45,7 @@ describe("rscActionHandler method enforcement", () => {
30
45
  it("rejects POST for an action with method GET", async () => {
31
46
  const action = Object.assign(vi.fn(), { method: "GET" });
32
47
  const deps = createDeps(action);
33
- const req = makeRequest(ACTION_URL, "POST", "[]");
48
+ const req = makeRequest(ACTION_URL, "POST", "[]", { Origin: SELF_ORIGIN });
34
49
  const result = await rscActionHandler(req, deps);
35
50
  expect(result).toBeInstanceOf(Response);
36
51
  const res = result;
@@ -43,7 +58,7 @@ describe("rscActionHandler method enforcement", () => {
43
58
  method: "POST",
44
59
  });
45
60
  const deps = createDeps(action);
46
- const req = makeRequest(ACTION_URL, "POST", "[]");
61
+ const req = makeRequest(ACTION_URL, "POST", "[]", { Origin: SELF_ORIGIN });
47
62
  const result = await rscActionHandler(req, deps);
48
63
  expect(result).toBe("ok");
49
64
  expect(action).toHaveBeenCalled();
@@ -61,7 +76,9 @@ describe("rscActionHandler method enforcement", () => {
61
76
  it("defaults to POST for actions without .method property", async () => {
62
77
  const action = vi.fn().mockReturnValue("ok");
63
78
  const deps = createDeps(action);
64
- const postReq = makeRequest(ACTION_URL, "POST", "[]");
79
+ const postReq = makeRequest(ACTION_URL, "POST", "[]", {
80
+ Origin: SELF_ORIGIN,
81
+ });
65
82
  const postResult = await rscActionHandler(postReq, deps);
66
83
  expect(postResult).toBe("ok");
67
84
  expect(action).toHaveBeenCalled();
@@ -120,9 +137,101 @@ describe("rscActionHandler method enforcement", () => {
120
137
  getServerModuleExport: vi.fn().mockResolvedValue(action),
121
138
  decodeReply: vi.fn().mockResolvedValue(["decoded-arg"]),
122
139
  };
123
- const req = makeRequest(ACTION_URL, "POST", "serialized-body");
140
+ const req = makeRequest(ACTION_URL, "POST", "serialized-body", {
141
+ Origin: SELF_ORIGIN,
142
+ });
124
143
  await rscActionHandler(req, deps);
125
144
  expect(deps.decodeReply).toHaveBeenCalledWith("serialized-body", null);
126
145
  expect(action).toHaveBeenCalledWith("decoded-arg");
127
146
  });
128
147
  });
148
+ describe("rscActionHandler origin enforcement", () => {
149
+ it("allows POST whose Origin matches the request's own origin", async () => {
150
+ const action = Object.assign(vi.fn().mockReturnValue("ok"), {
151
+ method: "POST",
152
+ });
153
+ const deps = createDeps(action);
154
+ const req = postWithOrigin(SELF_ORIGIN);
155
+ const result = await rscActionHandler(req, deps);
156
+ expect(result).toBe("ok");
157
+ expect(action).toHaveBeenCalled();
158
+ });
159
+ it("rejects POST from a different origin with 403", async () => {
160
+ const action = Object.assign(vi.fn(), { method: "POST" });
161
+ const deps = createDeps(action);
162
+ const req = postWithOrigin("https://evil.test");
163
+ const result = await rscActionHandler(req, deps);
164
+ expect(result).toBeInstanceOf(Response);
165
+ expect(result.status).toBe(403);
166
+ expect(action).not.toHaveBeenCalled();
167
+ });
168
+ it("rejects POST from a same-site sibling subdomain with 403", async () => {
169
+ // context(justinvdm, 2026-04-20): The exact scenario in the advisory —
170
+ // Origin sits under the same registrable domain so SameSite=Lax cookies
171
+ // are attached, but the Origin header does not match the app's own origin.
172
+ const action = Object.assign(vi.fn(), { method: "POST" });
173
+ const deps = createDeps(action);
174
+ const req = postWithOrigin("https://evil.test.example");
175
+ const result = await rscActionHandler(req, deps);
176
+ expect(result).toBeInstanceOf(Response);
177
+ expect(result.status).toBe(403);
178
+ expect(action).not.toHaveBeenCalled();
179
+ });
180
+ it("rejects POST missing the Origin header with 403", async () => {
181
+ const action = Object.assign(vi.fn(), { method: "POST" });
182
+ const deps = createDeps(action);
183
+ const req = postWithOrigin(undefined);
184
+ const result = await rscActionHandler(req, deps);
185
+ expect(result).toBeInstanceOf(Response);
186
+ expect(result.status).toBe(403);
187
+ expect(action).not.toHaveBeenCalled();
188
+ });
189
+ it("allows POST from an origin in the allowedOrigins list", async () => {
190
+ const action = Object.assign(vi.fn().mockReturnValue("ok"), {
191
+ method: "POST",
192
+ });
193
+ const deps = {
194
+ ...createDeps(action),
195
+ allowedOrigins: ["https://trusted.example"],
196
+ };
197
+ const req = postWithOrigin("https://trusted.example");
198
+ const result = await rscActionHandler(req, deps);
199
+ expect(result).toBe("ok");
200
+ expect(action).toHaveBeenCalled();
201
+ });
202
+ it("rejects POST from an origin not in the allowedOrigins list", async () => {
203
+ const action = Object.assign(vi.fn(), { method: "POST" });
204
+ const deps = {
205
+ ...createDeps(action),
206
+ allowedOrigins: ["https://trusted.example"],
207
+ };
208
+ const req = postWithOrigin("https://not-trusted.example");
209
+ const result = await rscActionHandler(req, deps);
210
+ expect(result).toBeInstanceOf(Response);
211
+ expect(result.status).toBe(403);
212
+ expect(action).not.toHaveBeenCalled();
213
+ });
214
+ it("does not apply the origin check to GET (serverQuery) requests", async () => {
215
+ // context(justinvdm, 2026-04-20): GET is expected to be idempotent and is
216
+ // out of scope for the origin check. A top-level GET navigation from
217
+ // another origin does not send an Origin header at all, so enforcing here
218
+ // would reject legitimate navigations.
219
+ const action = Object.assign(vi.fn().mockReturnValue("data"), {
220
+ method: "GET",
221
+ });
222
+ const deps = createDeps(action);
223
+ const req = makeRequest(ACTION_URL + "&args=%5B%5D", "GET");
224
+ const result = await rscActionHandler(req, deps);
225
+ expect(result).toBe("data");
226
+ expect(action).toHaveBeenCalled();
227
+ });
228
+ it("runs origin check before invoking the action", async () => {
229
+ const action = Object.assign(vi.fn(), { method: "POST" });
230
+ const deps = createDeps(action);
231
+ const req = postWithOrigin("https://evil.test");
232
+ await rscActionHandler(req, deps);
233
+ expect(deps.getServerModuleExport).not.toHaveBeenCalled();
234
+ expect(deps.decodeReply).not.toHaveBeenCalled();
235
+ expect(action).not.toHaveBeenCalled();
236
+ });
237
+ });
@@ -1,4 +1,7 @@
1
1
  export declare function registerServerReference(action: Function, id: string, name: string): Function;
2
2
  export declare function registerClientReference<Target extends Record<string, unknown>>(ssrModule: Target, id: string, exportName: string): {};
3
3
  export declare function __smokeTestActionHandler(timestamp?: number): Promise<unknown>;
4
+ export declare function createRscActionHandler(options?: {
5
+ allowedOrigins?: readonly string[];
6
+ }): (req: Request) => Promise<unknown>;
4
7
  export declare function rscActionHandler(req: Request): Promise<unknown>;
@@ -48,6 +48,13 @@ export async function __smokeTestActionHandler(timestamp) {
48
48
  await new Promise((resolve) => setTimeout(resolve, 0));
49
49
  return { status: "ok", timestamp };
50
50
  }
51
+ export function createRscActionHandler(options = {}) {
52
+ return (req) => rscActionHandlerImpl(req, {
53
+ getServerModuleExport,
54
+ decodeReply,
55
+ allowedOrigins: options.allowedOrigins,
56
+ });
57
+ }
51
58
  export async function rscActionHandler(req) {
52
59
  return rscActionHandlerImpl(req, { getServerModuleExport, decodeReply });
53
60
  }
@@ -17,6 +17,6 @@ export const assembleDocument = ({ requestInfo, pageElement, shouldSSR, }) => {
17
17
  };
18
18
  const Document = requestInfo.rw.Document;
19
19
  return (_jsxs(Document, { ...requestInfo, children: [_jsx("script", { nonce: requestInfo.rw.nonce, dangerouslySetInnerHTML: {
20
- __html: `globalThis.__RWSDK_CONTEXT = ${JSON.stringify(clientContext)}`,
20
+ __html: `globalThis.__RWSDK_CONTEXT = ${JSON.stringify(clientContext)};if(!globalThis.__webpack_require__){globalThis.__webpack_require__=function(id){throw new Error("rwsdk: __webpack_require__ called before client init for: "+id)};globalThis.__webpack_require__.u=function(){}}`,
21
21
  } }), _jsx(Stylesheets, { requestInfo: requestInfo }), _jsx(Preloads, { requestInfo: requestInfo }), _jsx("div", { id: "hydrate-root", children: pageElement })] }));
22
22
  };
@@ -20,7 +20,7 @@ export const renderDocumentHtmlStream = async ({ rscPayloadStream, Document, req
20
20
  };
21
21
  // Create the outer document with a marker for injection
22
22
  const documentElement = (_jsxs(Document, { ...requestInfo, children: [_jsx("script", { nonce: requestInfo.rw.nonce, dangerouslySetInnerHTML: {
23
- __html: `globalThis.__RWSDK_CONTEXT = ${JSON.stringify(clientContext)}`,
23
+ __html: `globalThis.__RWSDK_CONTEXT = ${JSON.stringify(clientContext)};if(!globalThis.__webpack_require__){globalThis.__webpack_require__=function(id){throw new Error("rwsdk: __webpack_require__ called before client init for: "+id)};globalThis.__webpack_require__.u=function(){}}`,
24
24
  } }), _jsx(Stylesheets, { requestInfo: requestInfo }), _jsx(Preloads, { requestInfo: requestInfo }), _jsx("div", { id: "hydrate-root", children: _jsx("div", { id: "rwsdk-app-start" }) })] }));
25
25
  const outerHtmlStream = await renderHtmlStream({
26
26
  node: documentElement,
@@ -23,6 +23,20 @@ const findCssForModule = (scriptId, manifest) => {
23
23
  css.add(href);
24
24
  }
25
25
  }
26
+ // context(justinvdm, 2026-04-23): Rolldown associates CSS with the
27
+ // chunk that imports the CSS file, which may be a dynamic import target
28
+ // rather than the entry chunk. Walk both static and dynamic imports to
29
+ // find CSS from transitive dependencies.
30
+ if (entry.imports) {
31
+ for (const importId of entry.imports) {
32
+ inner(importId);
33
+ }
34
+ }
35
+ if (entry.dynamicImports) {
36
+ for (const importId of entry.dynamicImports) {
37
+ inner(importId);
38
+ }
39
+ }
26
40
  };
27
41
  inner(scriptId);
28
42
  return Array.from(css);
@@ -37,5 +51,13 @@ export const Stylesheets = async ({ requestInfo, }) => {
37
51
  allStylesheets.add(toAbsoluteHref(entry));
38
52
  }
39
53
  }
54
+ for (const [, entry] of Object.entries(manifest)) {
55
+ if (entry.isEntry) {
56
+ const css = findCssForModule(entry.src ?? "", manifest);
57
+ for (const href of css) {
58
+ allStylesheets.add(toAbsoluteHref(href));
59
+ }
60
+ }
61
+ }
40
62
  return (_jsx(_Fragment, { children: Array.from(allStylesheets).map((href) => (_jsx("link", { rel: "stylesheet", href: href, precedence: "first" }, href))) }));
41
63
  };
@@ -19,7 +19,7 @@ export type ServerFunctionWrap = (fn: Function, args: any[], type: "action" | "q
19
19
  * handler function, its arguments, and the type ("action" or "query").
20
20
  * Interruptors run *outside* the wrapper.
21
21
  *
22
- * Throws if called more than once.
22
+ * Only one wrapper is active at a time; the most recent call wins.
23
23
  *
24
24
  * @example
25
25
  * ```ts
@@ -7,7 +7,7 @@ let globalWrap;
7
7
  * handler function, its arguments, and the type ("action" or "query").
8
8
  * Interruptors run *outside* the wrapper.
9
9
  *
10
- * Throws if called more than once.
10
+ * Only one wrapper is active at a time; the most recent call wins.
11
11
  *
12
12
  * @example
13
13
  * ```ts
@@ -23,10 +23,6 @@ let globalWrap;
23
23
  * ```
24
24
  */
25
25
  export function registerServerFunctionWrap(wrap) {
26
- if (globalWrap) {
27
- throw new Error("registerServerFunctionWrap() has already been called. " +
28
- "Only one wrapper can be registered.");
29
- }
30
26
  globalWrap = wrap;
31
27
  }
32
28
  /**
@@ -86,11 +86,17 @@ describe("registerServerFunctionWrap", () => {
86
86
  expect(wrapSpy).not.toHaveBeenCalled();
87
87
  expect(result).toBeInstanceOf(Response);
88
88
  });
89
- it("throws if called more than once", () => {
90
- registerServerFunctionWrap(async (fn, args) => fn(...args));
91
- expect(() => {
92
- registerServerFunctionWrap(async (fn, args) => fn(...args));
93
- }).toThrow("registerServerFunctionWrap() has already been called");
89
+ it("replaces the wrapper if called more than once", async () => {
90
+ const first = vi.fn((fn, args, _type) => fn(...args));
91
+ const second = vi.fn((fn, args, _type) => fn(...args));
92
+ registerServerFunctionWrap(first);
93
+ registerServerFunctionWrap(second);
94
+ const action = serverAction(async function run() {
95
+ return "ok";
96
+ });
97
+ await action();
98
+ expect(first).not.toHaveBeenCalled();
99
+ expect(second).toHaveBeenCalledOnce();
94
100
  });
95
101
  it("without registration, handlers work normally", async () => {
96
102
  const action = serverAction(async function echo(msg) {
@@ -12,7 +12,10 @@ export type AppDefinition<Routes extends readonly Route<any>[], T extends Reques
12
12
  fetch: (request: Request, env: Env, cf: ExecutionContext) => Promise<Response>;
13
13
  __rwRoutes: Routes;
14
14
  };
15
- export declare const defineApp: <T extends RequestInfo = RequestInfo<any, DefaultAppContext>, Routes extends readonly Route<T>[] = readonly Route<T>[]>(routes: Routes) => AppDefinition<Routes, T>;
15
+ export interface DefineAppOptions {
16
+ allowedOrigins?: readonly string[];
17
+ }
18
+ export declare const defineApp: <T extends RequestInfo = RequestInfo<any, DefaultAppContext>, Routes extends readonly Route<T>[] = readonly Route<T>[]>(routes: Routes, options?: DefineAppOptions) => AppDefinition<Routes, T>;
16
19
  export declare const DefaultDocument: React.FC<{
17
20
  children: React.ReactNode;
18
21
  }>;
@@ -4,14 +4,17 @@ import { renderDocumentHtmlStream } from "./render/renderDocumentHtmlStream";
4
4
  import { renderToRscStream } from "./render/renderToRscStream";
5
5
  import { injectRSCPayload } from "rsc-html-stream/server";
6
6
  import { ErrorResponse } from "./error";
7
- import { rscActionHandler } from "./register/worker";
7
+ import { createRscActionHandler } from "./register/worker";
8
8
  import { getRequestInfo, runWithRequestInfo, runWithRequestInfoOverrides, } from "./requestInfo/worker";
9
9
  import { ssrWebpackRequire } from "./imports/worker";
10
10
  import { defineRoutes } from "./lib/router";
11
11
  import { generateNonce } from "./lib/utils";
12
12
  export * from "./requestInfo/types";
13
- export const defineApp = (routes) => {
13
+ export const defineApp = (routes, options = {}) => {
14
14
  const router = defineRoutes(routes);
15
+ const rscActionHandler = createRscActionHandler({
16
+ allowedOrigins: options.allowedOrigins,
17
+ });
15
18
  return {
16
19
  __rwRoutes: routes,
17
20
  fetch: async (request, env, cf) => {
@@ -0,0 +1,10 @@
1
+ import type { EnvironmentOptions } from "vite";
2
+ interface OptimizeDepsPlugin {
3
+ name: string;
4
+ resolveId?: (id: string, importer: string | undefined, opts: {
5
+ kind: string;
6
+ }) => any;
7
+ load?: (id: string) => any;
8
+ }
9
+ export declare function addOptimizeDepsPlugin(config: EnvironmentOptions, plugin: OptimizeDepsPlugin): void;
10
+ export {};
@@ -0,0 +1,6 @@
1
+ export function addOptimizeDepsPlugin(config, plugin) {
2
+ config.optimizeDeps ??= {};
3
+ config.optimizeDeps.rolldownOptions ??= {};
4
+ config.optimizeDeps.rolldownOptions.plugins ??= [];
5
+ config.optimizeDeps.rolldownOptions.plugins.push(plugin);
6
+ }
@@ -6,8 +6,12 @@ import { externalModules } from "./constants.mjs";
6
6
  import { ssrBridgeWrapPlugin } from "./ssrBridgeWrapPlugin.mjs";
7
7
  export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clientFiles, serverFiles, clientEntryPoints, esbuildOptions, }) => ({
8
8
  name: "rwsdk:config",
9
- config: async (_, { command }) => {
9
+ config: async (config, { command }) => {
10
10
  const mode = process.env.NODE_ENV;
11
+ // context(justinvdm, 2026-05-06): Only set a sourcemap default if the user
12
+ // hasn't already configured it in their vite config. This lets users opt in
13
+ // or out explicitly while still providing a sensible mode-aware default.
14
+ const sourcemap = config.build?.sourcemap ?? (mode === "development");
11
15
  const workerConfig = {
12
16
  resolve: {
13
17
  conditions: [
@@ -45,11 +49,13 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
45
49
  ],
46
50
  exclude: [],
47
51
  entries: [workerEntryPathname],
48
- esbuildOptions: {
49
- jsx: "automatic",
50
- jsxImportSource: "react",
51
- define: {
52
- "process.env.NODE_ENV": JSON.stringify(mode),
52
+ rolldownOptions: {
53
+ transform: {
54
+ jsx: "react-jsx",
55
+ define: {
56
+ "process.env.NODE_ENV": JSON.stringify(mode),
57
+ "__webpack_require__": "globalThis.__webpack_require__",
58
+ },
53
59
  },
54
60
  },
55
61
  },
@@ -65,9 +71,12 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
65
71
  appType: "custom",
66
72
  mode,
67
73
  logLevel: silent ? "silent" : "info",
74
+ resolve: {
75
+ tsconfigPaths: true,
76
+ },
68
77
  build: {
69
78
  minify: mode !== "development",
70
- sourcemap: true,
79
+ sourcemap,
71
80
  },
72
81
  define: {
73
82
  "process.env.NODE_ENV": JSON.stringify(mode),
@@ -99,12 +108,13 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
99
108
  "rwsdk/turnstile",
100
109
  ],
101
110
  entries: [],
102
- esbuildOptions: {
103
- jsx: "automatic",
104
- jsxImportSource: "react",
105
- plugins: [],
106
- define: {
107
- "process.env.NODE_ENV": JSON.stringify(mode),
111
+ rolldownOptions: {
112
+ transform: {
113
+ jsx: "react-jsx",
114
+ define: {
115
+ "process.env.NODE_ENV": JSON.stringify(mode),
116
+ "__webpack_require__": "globalThis.__webpack_require__",
117
+ },
108
118
  },
109
119
  },
110
120
  },
@@ -136,12 +146,13 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
136
146
  "rwsdk/realtime/durableObject",
137
147
  "rwsdk/realtime/worker",
138
148
  ],
139
- esbuildOptions: {
140
- jsx: "automatic",
141
- jsxImportSource: "react",
142
- plugins: [],
143
- define: {
144
- "process.env.NODE_ENV": JSON.stringify(mode),
149
+ rolldownOptions: {
150
+ transform: {
151
+ jsx: "react-jsx",
152
+ define: {
153
+ "process.env.NODE_ENV": JSON.stringify(mode),
154
+ "__webpack_require__": "globalThis.__webpack_require__",
155
+ },
145
156
  },
146
157
  },
147
158
  },
@@ -1,6 +1,7 @@
1
1
  import debug from "debug";
2
2
  import MagicString from "magic-string";
3
3
  import path from "path";
4
+ import { addOptimizeDepsPlugin } from "./addOptimizeDepsPlugin.mjs";
4
5
  import { VENDOR_CLIENT_BARREL_EXPORT_PATH, VENDOR_SERVER_BARREL_EXPORT_PATH, } from "../lib/constants.mjs";
5
6
  export function generateLookupMap({ files, isDev, kind, exportName, }) {
6
7
  const s = new MagicString(`
@@ -55,32 +56,27 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
55
56
  return;
56
57
  }
57
58
  log("Configuring environment: env=%s", env);
58
- viteConfig.optimizeDeps ??= {};
59
- viteConfig.optimizeDeps.esbuildOptions ??= {};
60
- viteConfig.optimizeDeps.esbuildOptions.plugins ??= [];
61
- viteConfig.optimizeDeps.esbuildOptions.plugins.push({
59
+ const escapedVirtualModuleName = config.virtualModuleName.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
60
+ const escapedPrefixedModuleName = `/@id/${config.virtualModuleName}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
61
+ const lookupFilter = new RegExp(`^(${escapedVirtualModuleName}|${escapedPrefixedModuleName})\\.js$`);
62
+ addOptimizeDepsPlugin(viteConfig, {
62
63
  name: `rwsdk:${config.pluginName}`,
63
- setup(build) {
64
- log("Setting up esbuild plugin for %s", config.virtualModuleName);
65
- // Handle both direct virtual module name and /@id/ prefixed version
66
- const escapedVirtualModuleName = config.virtualModuleName.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
67
- const escapedPrefixedModuleName = `/@id/${config.virtualModuleName}`.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
68
- build.onResolve({
69
- filter: new RegExp(`^(${escapedVirtualModuleName}|${escapedPrefixedModuleName})\\.js$`),
70
- }, () => {
64
+ resolveId(id) {
65
+ if (lookupFilter.test(id)) {
71
66
  process.env.VERBOSE &&
72
- log("Esbuild onResolve: marking %s as external", config.virtualModuleName);
67
+ log("Marking %s as external", config.virtualModuleName);
73
68
  return {
74
- path: `${config.virtualModuleName}.js`,
69
+ id: `${config.virtualModuleName}.js`,
75
70
  external: true,
76
71
  };
77
- });
72
+ }
78
73
  },
79
74
  });
80
75
  const shouldOptimizeForEnv = !config.optimizeForEnvironments ||
81
76
  config.optimizeForEnvironments.includes(env);
82
77
  if (shouldOptimizeForEnv) {
83
78
  log("Applying optimizeDeps and aliasing for environment: %s", env);
79
+ viteConfig.optimizeDeps ??= {};
84
80
  viteConfig.optimizeDeps.include ??= [];
85
81
  for (const file of files) {
86
82
  if (file.includes("node_modules")) {
@@ -99,52 +99,36 @@ export const directiveModulesDevPlugin = ({ clientFiles, serverFiles, projectRoo
99
99
  else if (envName === "worker") {
100
100
  entries.push(APP_SERVER_BARREL_PATH);
101
101
  }
102
- env.optimizeDeps.esbuildOptions ??= {};
103
- env.optimizeDeps.esbuildOptions.plugins ??= [];
104
- env.optimizeDeps.esbuildOptions.plugins.unshift({
102
+ const appBarrelPaths = [
103
+ APP_CLIENT_BARREL_PATH,
104
+ APP_SERVER_BARREL_PATH,
105
+ ];
106
+ const appBarrelFilter = new RegExp(`(${appBarrelPaths
107
+ .map((p) => p.replace(/\\/g, "\\\\"))
108
+ .join("|")})$`);
109
+ const BARREL_PREFIX = "\0rwsdk-app-barrel:";
110
+ env.optimizeDeps.rolldownOptions ??= {};
111
+ env.optimizeDeps.rolldownOptions.plugins ??= [];
112
+ env.optimizeDeps.rolldownOptions.plugins.unshift({
105
113
  name: "rwsdk:app-barrel-blocker",
106
- setup(build) {
107
- const appBarrelPaths = [
108
- APP_CLIENT_BARREL_PATH,
109
- APP_SERVER_BARREL_PATH,
110
- ];
111
- const appBarrelFilter = new RegExp(`(${appBarrelPaths
112
- .map((p) => p.replace(/\\/g, "\\\\"))
113
- .join("|")})$`);
114
- build.onResolve({ filter: /.*/ }, async (args) => {
115
- // Block all resolutions until the scan is complete.
116
- await scanPromise;
117
- // Handle app barrel files
118
- if (appBarrelFilter.test(args.path)) {
119
- return {
120
- path: args.path,
121
- namespace: "rwsdk-app-barrel-ns",
122
- };
123
- }
124
- // context(justinvdm, 11 Sep 2025): Prevent Vite from
125
- // externalizing our application files. If we don't, paths
126
- // imported in our application barrel files will be marked as
127
- // external, and thus not scanned for dependencies.
128
- if (args.path.startsWith("/") &&
129
- (args.path.includes("/src/") ||
130
- args.path.includes("/generated/")) &&
131
- !args.path.includes("node_modules")) {
132
- // By returning a result, we claim the module and prevent vite:dep-scan
133
- // from marking it as external.
134
- return {
135
- path: args.path,
136
- };
137
- }
138
- });
139
- build.onLoad({ filter: /.*/, namespace: "rwsdk-app-barrel-ns" }, (args) => {
140
- const isServerBarrel = args.path.includes("app-server-barrel");
114
+ async resolveId(id) {
115
+ await scanPromise;
116
+ if (appBarrelFilter.test(id)) {
117
+ return `${BARREL_PREFIX}${id}`;
118
+ }
119
+ if (id.startsWith("/") &&
120
+ (id.includes("/src/") || id.includes("/generated/")) &&
121
+ !id.includes("node_modules")) {
122
+ return id;
123
+ }
124
+ },
125
+ load(id) {
126
+ if (id.startsWith(BARREL_PREFIX)) {
127
+ const barrelPath = id.slice(BARREL_PREFIX.length);
128
+ const isServerBarrel = barrelPath.includes("app-server-barrel");
141
129
  const files = isServerBarrel ? serverFiles : clientFiles;
142
- const content = generateAppBarrelContent(files, projectRootDir);
143
- return {
144
- contents: content,
145
- loader: "js",
146
- };
147
- });
130
+ return generateAppBarrelContent(files, projectRootDir);
131
+ }
148
132
  },
149
133
  });
150
134
  }
@@ -2,6 +2,7 @@ import debug from "debug";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
5
+ import { addOptimizeDepsPlugin } from "./addOptimizeDepsPlugin.mjs";
5
6
  import { transformClientComponents } from "./transformClientComponents.mjs";
6
7
  import { transformServerFunctions } from "./transformServerFunctions.mjs";
7
8
  const log = debug("rwsdk:vite:rsc-directives-plugin");
@@ -92,100 +93,56 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
92
93
  return;
93
94
  }
94
95
  process.env.VERBOSE && log("Configuring environment: env=%s", env);
95
- config.optimizeDeps ??= {};
96
- config.optimizeDeps.esbuildOptions ??= {};
97
- config.optimizeDeps.esbuildOptions.plugins ??= [];
98
- config.optimizeDeps.esbuildOptions.plugins.push({
99
- name: "rsc-directives-esbuild-transform",
100
- setup(build) {
101
- log("Setting up esbuild plugin for environment: %s", env);
102
- build.onLoad({ filter: /\.(js|ts|jsx|tsx|mts|mjs|cjs)$/ }, async (args) => {
103
- process.env.VERBOSE &&
104
- log("Esbuild onLoad called for environment=%s, path=%s", env, args.path);
105
- const normalizedPath = normalizeModulePath(args.path, projectRootDir);
106
- // context(justinvdm,2025-06-15): If we're in app code,
107
- // we will be doing the transform work in the vite plugin hooks,
108
- // the only reason we're in esbuild land for app code is for
109
- // dependency discovery, so we can skip transform work
110
- // and use heuristics instead - see below inside if block
111
- if (!args.path.includes("node_modules")) {
112
- if (clientFiles.has(normalizedPath)) {
113
- // context(justinvdm,2025-06-15): If this is a client file:
114
- // * for ssr and client envs we can skip so esbuild looks at the
115
- // original source code to discovery dependencies
116
- // * for worker env, the transform would have just created
117
- // references and dropped all imports, so we can just return empty code
118
- if (env === "client" || env === "ssr") {
119
- log("Esbuild onLoad skipping client module in app code for client or ssr env, path=%s", args.path);
120
- return undefined;
121
- }
122
- else {
123
- log("Esbuild onLoad returning empty code for server module in app code for worker env, path=%s to bypass esbuild dependency discovery", args.path);
124
- return {
125
- contents: "",
126
- loader: "js",
127
- };
128
- }
129
- }
130
- else if (serverFiles.has(normalizedPath)) {
131
- // context(justinvdm,2025-06-15): If this is a server file:
132
- // * for worker env, we can skip so esbuild looks at the
133
- // original source code to discovery dependencies
134
- // * for ssr and client envs, the transform would have just created
135
- // references and dropped all imports, so we can just return empty code
136
- if (env === "worker") {
137
- log("Esbuild onLoad skipping server module in app code for worker env, path=%s", args.path);
138
- return undefined;
139
- }
140
- else if (env === "ssr" || env === "client") {
141
- log("Esbuild onLoad returning empty code for server module in app code for ssr or client env, path=%s", args.path);
142
- return {
143
- contents: "",
144
- loader: "js",
145
- };
146
- }
147
- }
96
+ const directivesFileFilter = /\.(js|ts|jsx|tsx|mts|mjs|cjs)$/;
97
+ async function handleDirectivesLoad(filePath) {
98
+ const normalizedPath = normalizeModulePath(filePath, projectRootDir);
99
+ if (!filePath.includes("node_modules")) {
100
+ if (clientFiles.has(normalizedPath)) {
101
+ if (env === "client" || env === "ssr") {
102
+ return undefined;
148
103
  }
149
- let code;
150
- try {
151
- code = await fs.readFile(args.path, "utf-8");
104
+ else {
105
+ return { code: "", moduleType: "js" };
152
106
  }
153
- catch {
154
- process.env.VERBOSE &&
155
- log("Failed to read file: %s, environment=%s", args.path, env);
107
+ }
108
+ else if (serverFiles.has(normalizedPath)) {
109
+ if (env === "worker") {
156
110
  return undefined;
157
111
  }
158
- const clientResult = await transformClientComponents(code, normalizedPath, {
159
- environmentName: env,
160
- clientFiles,
161
- isEsbuild: true,
162
- });
163
- if (clientResult) {
164
- process.env.VERBOSE &&
165
- log("Esbuild client component transformation successful for environment=%s, path=%s", env, args.path);
166
- process.env.VERBOSE &&
167
- log("Esbuild client component transformation for environment=%s, path=%s, code: %j", env, args.path, clientResult.code);
168
- return {
169
- contents: clientResult.code,
170
- loader: getLoader(args.path),
171
- };
172
- }
173
- const serverResult = transformServerFunctions(code, normalizedPath, env, serverFiles);
174
- if (serverResult) {
175
- process.env.VERBOSE &&
176
- log("Esbuild server function transformation successful for environment=%s, path=%s", env, args.path);
177
- return {
178
- contents: serverResult.code,
179
- loader: getLoader(args.path),
180
- };
112
+ else if (env === "ssr" || env === "client") {
113
+ return { code: "", moduleType: "js" };
181
114
  }
182
- process.env.VERBOSE &&
183
- log("Esbuild no transformation applied for environment=%s, path=%s", env, args.path);
184
- });
115
+ }
116
+ }
117
+ let code;
118
+ try {
119
+ code = await fs.readFile(filePath, "utf-8");
120
+ }
121
+ catch {
122
+ return undefined;
123
+ }
124
+ const clientResult = await transformClientComponents(code, normalizedPath, { environmentName: env, clientFiles, isEsbuild: true });
125
+ if (clientResult) {
126
+ return { code: clientResult.code, moduleType: getLoader(filePath) };
127
+ }
128
+ const serverResult = transformServerFunctions(code, normalizedPath, env, serverFiles);
129
+ if (serverResult) {
130
+ return { code: serverResult.code, moduleType: getLoader(filePath) };
131
+ }
132
+ return undefined;
133
+ }
134
+ addOptimizeDepsPlugin(config, {
135
+ name: "rsc-directives-transform",
136
+ async load(id) {
137
+ if (!directivesFileFilter.test(id)) {
138
+ return;
139
+ }
140
+ const result = await handleDirectivesLoad(id);
141
+ if (result) {
142
+ return { code: result.code, moduleType: result.moduleType };
143
+ }
185
144
  },
186
145
  });
187
- process.env.VERBOSE &&
188
- log("Environment configuration complete for env=%s", env);
189
146
  },
190
147
  };
191
148
  };
@@ -91,44 +91,34 @@ export const knownDepsResolverPlugin = ({ projectRootDir, }) => {
91
91
  // Log a clean summary instead of all the individual mappings
92
92
  const totalMappings = Object.values(ENV_IMPORT_MAPPINGS).reduce((sum, mappings) => sum + mappings.size, 0);
93
93
  log("Known dependencies resolver configured with %d total mappings across %d environments", totalMappings, Object.keys(ENV_IMPORT_MAPPINGS).length);
94
- function createEsbuildResolverPlugin(envName, mappings) {
94
+ function createResolverPlugin(envName, mappings) {
95
95
  if (!mappings) {
96
96
  return null;
97
97
  }
98
- // Create reverse mapping from slugified names to original imports
99
- // Vite converts "react-dom/server.edge" -> "react-dom_server__edge"
100
- // Pattern: / becomes _, . becomes __
101
98
  const slugifiedToOriginal = new Map();
102
- for (const [original, resolved] of mappings) {
99
+ for (const [original] of mappings) {
103
100
  const slugified = original.replace(/\//g, "_").replace(/\./g, "__");
104
101
  slugifiedToOriginal.set(slugified, original);
105
102
  }
106
103
  return {
107
- name: `rwsdk:known-dependencies-resolver-esbuild-${envName}`,
108
- setup(build) {
109
- build.onResolve({ filter: /.*/ }, (args) => {
110
- let resolved = mappings.get(args.path);
111
- // If not found, check if it's a slugified version
112
- if (!resolved) {
113
- const originalImport = slugifiedToOriginal.get(args.path);
114
- if (originalImport) {
115
- resolved = mappings.get(originalImport);
116
- }
117
- }
118
- if (!resolved) {
119
- resolved = resolveKnownImport(args.path, envName, projectRootDir);
104
+ name: `rwsdk:known-dependencies-resolver-${envName}`,
105
+ resolveId(id) {
106
+ let resolved = mappings.get(id);
107
+ if (!resolved) {
108
+ const originalImport = slugifiedToOriginal.get(id);
109
+ if (originalImport) {
110
+ resolved = mappings.get(originalImport);
120
111
  }
121
- // Resolve for both entry points (importer === '') and regular imports
122
- // Entry points come from optimizeDeps.include and are critical to intercept
123
- if (resolved) {
124
- if (args.path === "react-server-dom-webpack/client.edge") {
125
- return;
126
- }
127
- return {
128
- path: resolved,
129
- };
112
+ }
113
+ if (!resolved) {
114
+ resolved = resolveKnownImport(id, envName, projectRootDir);
115
+ }
116
+ if (resolved) {
117
+ if (id === "react-server-dom-webpack/client.edge") {
118
+ return;
130
119
  }
131
- });
120
+ return resolved;
121
+ }
132
122
  },
133
123
  };
134
124
  }
@@ -152,17 +142,20 @@ export const knownDepsResolverPlugin = ({ projectRootDir, }) => {
152
142
  config.environments[envName] = {};
153
143
  }
154
144
  const envConfig = config.environments[envName];
155
- const esbuildPlugin = createEsbuildResolverPlugin(envName, mappings);
156
- if (esbuildPlugin && mappings) {
145
+ if (mappings) {
157
146
  envConfig.optimizeDeps ??= {};
158
- envConfig.optimizeDeps.esbuildOptions ??= {};
159
- envConfig.optimizeDeps.esbuildOptions.define ??= {};
160
- envConfig.optimizeDeps.esbuildOptions.define["process.env.NODE_ENV"] = JSON.stringify(process.env.NODE_ENV);
161
- envConfig.optimizeDeps.esbuildOptions.plugins ??= [];
162
- envConfig.optimizeDeps.esbuildOptions.plugins.push(esbuildPlugin);
147
+ envConfig.optimizeDeps.rolldownOptions ??= {};
148
+ envConfig.optimizeDeps.rolldownOptions.transform ??= {};
149
+ envConfig.optimizeDeps.rolldownOptions.transform.define ??= {};
150
+ envConfig.optimizeDeps.rolldownOptions.transform.define["process.env.NODE_ENV"] = JSON.stringify(process.env.NODE_ENV);
151
+ envConfig.optimizeDeps.rolldownOptions.plugins ??= [];
152
+ const plugin = createResolverPlugin(envName, mappings);
153
+ if (plugin) {
154
+ envConfig.optimizeDeps.rolldownOptions.plugins.push(plugin);
155
+ }
163
156
  envConfig.optimizeDeps.include ??= [];
164
157
  envConfig.optimizeDeps.include.push(...predefinedImports);
165
- log("Added esbuild plugin and optimizeDeps includes for environment: %s", envName);
158
+ log("Added optimizeDeps plugin and includes for environment: %s", envName);
166
159
  }
167
160
  const aliases = ensureAliasArray(envConfig);
168
161
  for (const [find, replacement] of mappings) {
@@ -9,7 +9,7 @@ export function linkWorkerBundle({ code, manifestContent, projectRootDir, base,
9
9
  const manifest = JSON.parse(manifestContent);
10
10
  // 1. Replace the manifest placeholder with the actual manifest content.
11
11
  log("Injecting manifest into worker bundle");
12
- newCode = newCode.replace(/['"]__RWSDK_MANIFEST_PLACEHOLDER__['"]/, manifestContent);
12
+ newCode = newCode.replace(/['"`]__RWSDK_MANIFEST_PLACEHOLDER__['"`]/, manifestContent);
13
13
  // 2. Replace asset placeholders with their final hashed paths.
14
14
  log("Replacing asset placeholders in final worker bundle");
15
15
  for (const [key, value] of Object.entries(manifest)) {
@@ -1,4 +1,5 @@
1
1
  import { resolve } from "node:path";
2
+ import { addOptimizeDepsPlugin } from "./addOptimizeDepsPlugin.mjs";
2
3
  import { checkPrismaStatus } from "./checkIsUsingPrisma.mjs";
3
4
  import { ensureAliasArray } from "./ensureAliasArray.mjs";
4
5
  import { invalidateCacheIfPrismaClientChanged } from "./invalidateCacheIfPrismaClientChanged.mjs";
@@ -21,17 +22,13 @@ export const prismaPlugin = async ({ projectRootDir, }) => {
21
22
  return;
22
23
  }
23
24
  const wasmPath = resolve(projectRootDir, "node_modules/.prisma/client/wasm.js");
24
- config.optimizeDeps ??= {};
25
- config.optimizeDeps.esbuildOptions ??= {};
26
- config.optimizeDeps.esbuildOptions.plugins ??= [];
27
- config.optimizeDeps.esbuildOptions.plugins.push({
25
+ const prismaFilter = /^.prisma\/client\/default/;
26
+ addOptimizeDepsPlugin(config, {
28
27
  name: "rwsdk:prisma",
29
- setup(build) {
30
- build.onResolve({ filter: /^.prisma\/client\/default/ }, async () => {
31
- return {
32
- path: wasmPath,
33
- };
34
- });
28
+ resolveId(id) {
29
+ if (prismaFilter.test(id)) {
30
+ return wasmPath;
31
+ }
35
32
  },
36
33
  });
37
34
  ensureAliasArray(config).push({
@@ -6,7 +6,6 @@ import { devServerConstantPlugin } from "./devServerConstant.mjs";
6
6
  import { hasOwnCloudflareVitePlugin } from "./hasOwnCloudflareVitePlugin.mjs";
7
7
  import { hasOwnReactVitePlugin } from "./hasOwnReactVitePlugin.mjs";
8
8
  import reactPlugin from "@vitejs/plugin-react";
9
- import tsconfigPaths from "vite-tsconfig-paths";
10
9
  import { pathExists } from "fs-extra";
11
10
  import { $ } from "../lib/$.mjs";
12
11
  import { findWranglerConfig } from "../lib/findWranglerConfig.mjs";
@@ -112,7 +111,6 @@ export const redwoodPlugin = async (options = {}) => {
112
111
  }),
113
112
  knownDepsResolverPlugin({ projectRootDir }),
114
113
  cloudflarePreInitPlugin(),
115
- tsconfigPaths({ root: projectRootDir }),
116
114
  shouldIncludeCloudflarePlugin
117
115
  ? cloudflare({
118
116
  viteEnvironment: { name: "worker" },
@@ -1,6 +1,7 @@
1
1
  import debug from "debug";
2
2
  import MagicString from "magic-string";
3
3
  import { INTERMEDIATE_SSR_BRIDGE_PATH } from "../lib/constants.mjs";
4
+ import { addOptimizeDepsPlugin } from "./addOptimizeDepsPlugin.mjs";
4
5
  import { externalModulesSet } from "./constants.mjs";
5
6
  import { findSsrImportCallSites } from "./findSsrSpecifiers.mjs";
6
7
  const log = debug("rwsdk:vite:ssr-bridge-plugin");
@@ -49,31 +50,18 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
49
50
  configEnvironment(env, config) {
50
51
  log("Configuring environment: env=%s", env);
51
52
  if (env === "worker") {
52
- // Configure esbuild to mark rwsdk/__ssr paths as external for worker environment
53
- log("Configuring esbuild options for worker environment");
54
53
  config.optimizeDeps ??= {};
55
- config.optimizeDeps.esbuildOptions ??= {};
56
- config.optimizeDeps.esbuildOptions.plugins ??= [];
57
54
  config.optimizeDeps.include ??= [];
58
- config.optimizeDeps.esbuildOptions.plugins.push({
55
+ addOptimizeDepsPlugin(config, {
59
56
  name: "rwsdk-ssr-external",
60
- setup(build) {
61
- log("Setting up esbuild plugin to mark rwsdk/__ssr paths as external for worker");
62
- build.onResolve({ filter: /.*$/ }, (args) => {
63
- process.env.VERBOSE &&
64
- log("Esbuild onResolve called for path=%s, args=%O", args.path, args);
65
- if (args.path === "rwsdk/__ssr_bridge" ||
66
- args.path.startsWith(VIRTUAL_SSR_PREFIX)) {
67
- log("Marking as external: %s", args.path);
68
- return {
69
- path: args.path,
70
- external: true,
71
- };
72
- }
73
- });
57
+ resolveId(id) {
58
+ if (id === "rwsdk/__ssr_bridge" ||
59
+ id.startsWith(VIRTUAL_SSR_PREFIX)) {
60
+ log("Marking as external: %s", id);
61
+ return { id, external: true };
62
+ }
74
63
  },
75
64
  });
76
- log("Worker environment esbuild configuration complete");
77
65
  }
78
66
  },
79
67
  async resolveId(id, importer, options) {
@@ -1,6 +1,7 @@
1
1
  import debug from "debug";
2
2
  import fs from "node:fs/promises";
3
3
  import { RW_STATE_EXPORT_PATH } from "../lib/constants.mjs";
4
+ import { addOptimizeDepsPlugin } from "./addOptimizeDepsPlugin.mjs";
4
5
  import { maybeResolveEnvImport } from "./envResolvers.mjs";
5
6
  const log = debug("rwsdk:vite:state-plugin");
6
7
  const VIRTUAL_STATE_PREFIX = "virtual:rwsdk:state:";
@@ -22,22 +23,14 @@ export const statePlugin = ({ projectRootDir, }) => {
22
23
  },
23
24
  configEnvironment(env, config) {
24
25
  if (env === "worker") {
25
- config.optimizeDeps ??= {};
26
- config.optimizeDeps.esbuildOptions ??= {};
27
- config.optimizeDeps.esbuildOptions.plugins ??= [];
28
- config.optimizeDeps.esbuildOptions.plugins.push({
26
+ const stateFilter = new RegExp(`^(${RW_STATE_EXPORT_PATH}|${VIRTUAL_STATE_PREFIX}.*)$`);
27
+ addOptimizeDepsPlugin(config, {
29
28
  name: "rwsdk-state-external",
30
- setup(build) {
31
- build.onResolve({
32
- // context(justinvdm, 13 Oct 2025): Vite dep optimizer slugifies the export path
33
- filter: new RegExp(`^(${RW_STATE_EXPORT_PATH}|${VIRTUAL_STATE_PREFIX}.*)$`),
34
- }, (args) => {
35
- log("Marking as external: %s", args.path);
36
- return {
37
- path: args.path,
38
- external: true,
39
- };
40
- });
29
+ resolveId(id) {
30
+ if (stateFilter.test(id)) {
31
+ log("Marking as external: %s", id);
32
+ return { id, external: true };
33
+ }
41
34
  },
42
35
  });
43
36
  }
@@ -105,9 +105,9 @@ export async function transformJsxScriptTagsCode(code, clientEntryPoints, manife
105
105
  .forEach((callExpr) => {
106
106
  const expression = callExpr.getExpression();
107
107
  const expressionText = expression.getText();
108
- if (expressionText !== "jsx" &&
109
- expressionText !== "jsxs" &&
110
- expressionText !== "jsxDEV") {
108
+ if (!expressionText.endsWith("jsx") &&
109
+ !expressionText.endsWith("jsxs") &&
110
+ !expressionText.endsWith("jsxDEV")) {
111
111
  return;
112
112
  }
113
113
  const args = callExpr.getArguments();
@@ -1,153 +1,2 @@
1
- import type { ResolvedConfig } from "vite";
2
- export declare const vitePreamblePlugin: () => {
3
- configResolved(config: ResolvedConfig): void;
4
- hotUpdate?: import("rollup").ObjectHook<(this: import("rollup").MinimalPluginContext & {
5
- environment: import("vite").DevEnvironment;
6
- }, options: import("vite").HotUpdateOptions) => Array<import("vite").EnvironmentModuleNode> | void | Promise<Array<import("vite").EnvironmentModuleNode> | void>>;
7
- resolveId?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, source: string, importer: string | undefined, options: {
8
- attributes: Record<string, string>;
9
- custom?: import("rollup").CustomPluginOptions;
10
- ssr?: boolean | undefined;
11
- isEntry: boolean;
12
- }) => Promise<import("rollup").ResolveIdResult> | import("rollup").ResolveIdResult, {
13
- filter?: {
14
- id?: RegExp | RegExp[] | {
15
- include?: RegExp | RegExp[] | undefined;
16
- exclude?: RegExp | RegExp[] | undefined;
17
- };
18
- };
19
- }>;
20
- load?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, id: string, options?: {
21
- ssr?: boolean | undefined;
22
- }) => Promise<import("rollup").LoadResult> | import("rollup").LoadResult, {
23
- filter?: {
24
- id?: string | RegExp | (string | RegExp)[] | {
25
- include?: string | RegExp | (string | RegExp)[] | undefined;
26
- exclude?: string | RegExp | (string | RegExp)[] | undefined;
27
- };
28
- };
29
- }>;
30
- transform?: import("rollup").ObjectHook<(this: import("rollup").TransformPluginContext, code: string, id: string, options?: {
31
- ssr?: boolean | undefined;
32
- }) => Promise<import("rollup").TransformResult> | import("rollup").TransformResult, {
33
- filter?: {
34
- id?: string | RegExp | (string | RegExp)[] | {
35
- include?: string | RegExp | (string | RegExp)[] | undefined;
36
- exclude?: string | RegExp | (string | RegExp)[] | undefined;
37
- };
38
- code?: string | RegExp | (string | RegExp)[] | {
39
- include?: string | RegExp | (string | RegExp)[] | undefined;
40
- exclude?: string | RegExp | (string | RegExp)[] | undefined;
41
- };
42
- };
43
- }>;
44
- sharedDuringBuild?: boolean;
45
- perEnvironmentStartEndDuringDev?: boolean;
46
- perEnvironmentWatchChangeDuringDev?: boolean;
47
- enforce?: "pre" | "post";
48
- apply?: "serve" | "build" | ((this: void, config: import("vite").UserConfig, env: import("vite").ConfigEnv) => boolean);
49
- applyToEnvironment?: (environment: {
50
- name: string;
51
- getTopLevelConfig(): ResolvedConfig;
52
- config: ResolvedConfig & {
53
- define?: Record<string, any>;
54
- resolve: Required<import("vite").ResolveOptions>;
55
- consumer: "client" | "server";
56
- keepProcessEnv?: boolean;
57
- optimizeDeps: import("vite").DepOptimizationOptions;
58
- dev: import("vite").ResolvedDevEnvironmentOptions;
59
- build: import("vite").ResolvedBuildEnvironmentOptions;
60
- plugins: readonly import("vite").Plugin[];
61
- };
62
- logger: import("vite").Logger;
63
- }) => boolean | Promise<boolean> | import("vite").PluginOption;
64
- config?: import("rollup").ObjectHook<(this: import("vite").ConfigPluginContext, config: import("vite").UserConfig, env: import("vite").ConfigEnv) => Omit<import("vite").UserConfig, "plugins"> | null | void | Promise<Omit<import("vite").UserConfig, "plugins"> | null | void>>;
65
- configEnvironment?: import("rollup").ObjectHook<(this: import("vite").ConfigPluginContext, name: string, config: import("vite").EnvironmentOptions, env: import("vite").ConfigEnv & {
66
- isSsrTargetWebworker?: boolean;
67
- }) => import("vite").EnvironmentOptions | null | void | Promise<import("vite").EnvironmentOptions | null | void>>;
68
- configureServer?: import("rollup").ObjectHook<import("vite").ServerHook>;
69
- configurePreviewServer?: import("rollup").ObjectHook<import("vite").PreviewServerHook>;
70
- transformIndexHtml?: import("vite").IndexHtmlTransform;
71
- buildApp?: import("rollup").ObjectHook<import("vite").BuildAppHook>;
72
- handleHotUpdate?: import("rollup").ObjectHook<(this: import("vite").MinimalPluginContextWithoutEnvironment, ctx: import("vite").HmrContext) => Array<import("vite").ModuleNode> | void | Promise<Array<import("vite").ModuleNode> | void>>;
73
- api?: any;
74
- cacheKey?: string | undefined;
75
- name: string;
76
- version?: string | undefined;
77
- renderError?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, error?: Error | undefined) => void | Promise<void>, {
78
- sequential?: boolean;
79
- }> | undefined;
80
- augmentChunkHash?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, chunk: import("rollup").RenderedChunk) => string | void, {}> | undefined;
81
- generateBundle?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, options: import("rollup").NormalizedOutputOptions, bundle: import("rollup").OutputBundle, isWrite: boolean) => void | Promise<void>, {}> | undefined;
82
- outputOptions?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, options: import("rollup").OutputOptions) => import("rollup").OutputOptions | import("rollup").NullValue, {}> | undefined;
83
- renderChunk?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, code: string, chunk: import("rollup").RenderedChunk, options: import("rollup").NormalizedOutputOptions, meta: {
84
- chunks: Record<string, import("rollup").RenderedChunk>;
85
- }) => string | {
86
- code: string;
87
- map?: import("rollup").SourceMapInput;
88
- } | import("rollup").NullValue | Promise<string | {
89
- code: string;
90
- map?: import("rollup").SourceMapInput;
91
- } | import("rollup").NullValue>, {}> | undefined;
92
- renderDynamicImport?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, options: {
93
- customResolution: string | null;
94
- format: import("rollup").InternalModuleFormat;
95
- moduleId: string;
96
- targetModuleId: string | null;
97
- chunk: import("rollup").PreRenderedChunkWithFileName;
98
- targetChunk: import("rollup").PreRenderedChunkWithFileName | null;
99
- getTargetChunkImports: () => import("rollup").DynamicImportTargetChunk[] | null;
100
- targetModuleAttributes: Record<string, string>;
101
- }) => {
102
- left: string;
103
- right: string;
104
- } | import("rollup").NullValue, {}> | undefined;
105
- renderStart?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, outputOptions: import("rollup").NormalizedOutputOptions, inputOptions: import("rollup").NormalizedInputOptions) => void | Promise<void>, {
106
- sequential?: boolean;
107
- }> | undefined;
108
- resolveFileUrl?: import("rollup").ObjectHook<import("rollup").ResolveFileUrlHook, {}> | undefined;
109
- resolveImportMeta?: import("rollup").ObjectHook<import("rollup").ResolveImportMetaHook, {}> | undefined;
110
- writeBundle?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, options: import("rollup").NormalizedOutputOptions, bundle: import("rollup").OutputBundle) => void | Promise<void>, {
111
- sequential?: boolean;
112
- }> | undefined;
113
- footer?: import("rollup").ObjectHook<import("rollup").AddonHook, {}> | undefined;
114
- banner?: import("rollup").ObjectHook<import("rollup").AddonHook, {}> | undefined;
115
- intro?: import("rollup").ObjectHook<import("rollup").AddonHook, {}> | undefined;
116
- outro?: import("rollup").ObjectHook<import("rollup").AddonHook, {}> | undefined;
117
- buildEnd?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, error?: Error | undefined) => void | Promise<void>, {
118
- sequential?: boolean;
119
- }> | undefined;
120
- buildStart?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, options: import("rollup").NormalizedInputOptions) => void | Promise<void>, {
121
- sequential?: boolean;
122
- }> | undefined;
123
- closeBundle?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, error?: Error | undefined) => void | Promise<void>, {
124
- sequential?: boolean;
125
- }> | undefined;
126
- closeWatcher?: import("rollup").ObjectHook<(this: import("rollup").PluginContext) => void | Promise<void>, {
127
- sequential?: boolean;
128
- }> | undefined;
129
- moduleParsed?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, info: import("rollup").ModuleInfo) => void | Promise<void>, {
130
- sequential?: boolean;
131
- }> | undefined;
132
- onLog?: import("rollup").ObjectHook<(this: import("rollup").MinimalPluginContext, level: import("rollup").LogLevel, log: import("rollup").RollupLog) => boolean | import("rollup").NullValue, {}> | undefined;
133
- options?: import("rollup").ObjectHook<(this: import("rollup").MinimalPluginContext, options: import("rollup").InputOptions) => import("rollup").InputOptions | import("rollup").NullValue | Promise<import("rollup").InputOptions | import("rollup").NullValue>, {}> | undefined;
134
- resolveDynamicImport?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, specifier: string | import("rollup").AstNode, importer: string, options: {
135
- attributes: Record<string, string>;
136
- importerAttributes: Record<string, string>;
137
- }) => import("rollup").ResolveIdResult | Promise<import("rollup").ResolveIdResult>, {}> | undefined;
138
- shouldTransformCachedModule?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, options: {
139
- ast: import("rollup").ProgramNode;
140
- attributes: Record<string, string>;
141
- code: string;
142
- id: string;
143
- meta: import("rollup").CustomPluginOptions;
144
- moduleSideEffects: boolean | "no-treeshake";
145
- resolvedSources: import("rollup").ResolvedIdMap;
146
- syntheticNamedExports: boolean | string;
147
- }) => boolean | import("rollup").NullValue | Promise<boolean | import("rollup").NullValue>, {}> | undefined;
148
- watchChange?: import("rollup").ObjectHook<(this: import("rollup").PluginContext, id: string, change: {
149
- event: import("rollup").ChangeEvent;
150
- }) => void | Promise<void>, {
151
- sequential?: boolean;
152
- }> | undefined;
153
- };
1
+ import type { Plugin } from "vite";
2
+ export declare const vitePreamblePlugin: () => Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "1.2.2",
3
+ "version": "1.2.4-canary.0",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -163,7 +163,7 @@
163
163
  "@types/react": "~19.2.14",
164
164
  "@types/react-dom": "~19.2.3",
165
165
  "@types/react-is": "~19.2.0",
166
- "@vitejs/plugin-react": "~5.1.4",
166
+ "@vitejs/plugin-react": "~6.0.1",
167
167
  "chokidar": "~5.0.0",
168
168
  "debug": "~4.4.3",
169
169
  "decompress": "~4.2.1",
@@ -189,8 +189,7 @@
189
189
  "tmp-promise": "~3.0.3",
190
190
  "ts-morph": "~27.0.2",
191
191
  "unique-names-generator": "~4.7.1",
192
- "vibe-rules": "~0.3.91",
193
- "vite-tsconfig-paths": "~6.1.1"
192
+ "vibe-rules": "~0.3.91"
194
193
  },
195
194
  "peerDependencies": {
196
195
  "@cloudflare/vite-plugin": "^1.26.1",
@@ -198,7 +197,7 @@
198
197
  "react": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
199
198
  "react-dom": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
200
199
  "react-server-dom-webpack": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
201
- "vite": "^6.2.6 || 7.x",
200
+ "vite": "^6.2.6 || 7.x || 8.x",
202
201
  "wrangler": "^4.77.0"
203
202
  },
204
203
  "peerDependenciesMeta": {
@@ -220,7 +219,7 @@
220
219
  "semver": "~7.7.4",
221
220
  "tsx": "~4.21.0",
222
221
  "typescript": "~6.0.2",
223
- "vite": "~7.3.2",
222
+ "vite": "~8.0.9",
224
223
  "vitest": "~4.1.2"
225
224
  }
226
225
  }