rwsdk 1.0.0-beta.30-test.20251119220440 → 1.0.0-beta.30-test.20251120213828
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.
- package/dist/lib/e2e/testHarness.mjs +6 -1
- package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
- package/dist/use-synced-state/SyncedStateServer.d.mts +21 -0
- package/dist/use-synced-state/{SyncStateServer.mjs → SyncedStateServer.mjs} +38 -34
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +19 -19
- package/dist/use-synced-state/__tests__/useSyncState.test.js +9 -9
- package/dist/use-synced-state/__tests__/useSyncedState.test.js +9 -9
- package/dist/use-synced-state/__tests__/worker.test.mjs +11 -11
- package/dist/use-synced-state/client-core.d.ts +26 -0
- package/dist/use-synced-state/client-core.js +39 -0
- package/dist/use-synced-state/client.d.ts +3 -28
- package/dist/use-synced-state/client.js +4 -39
- package/dist/use-synced-state/constants.d.mts +1 -1
- package/dist/use-synced-state/constants.mjs +1 -1
- package/dist/use-synced-state/useSyncedState.d.ts +3 -3
- package/dist/use-synced-state/useSyncedState.js +7 -7
- package/dist/use-synced-state/worker.d.mts +6 -7
- package/dist/use-synced-state/worker.mjs +25 -29
- package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
- package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +6 -7
- package/dist/vite/directivesPlugin.mjs +0 -4
- package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
- package/dist/vite/knownDepsResolverPlugin.mjs +6 -16
- package/dist/vite/redwoodPlugin.mjs +2 -0
- package/dist/vite/runDirectivesScan.mjs +13 -4
- package/dist/vite/ssrBridgePlugin.mjs +10 -7
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -4
- package/dist/vite/virtualPlugin.mjs +6 -7
- package/package.json +5 -4
- package/dist/use-synced-state/SyncStateServer.d.mts +0 -20
- package/dist/use-synced-state/useSyncState.d.ts +0 -20
- package/dist/use-synced-state/useSyncState.js +0 -58
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
2
|
type HookDeps = {
|
|
3
3
|
useState: typeof React.useState;
|
|
4
4
|
useEffect: typeof React.useEffect;
|
|
@@ -6,7 +6,7 @@ type HookDeps = {
|
|
|
6
6
|
useCallback: typeof React.useCallback;
|
|
7
7
|
};
|
|
8
8
|
type Setter<T> = (value: T | ((previous: T) => T)) => void;
|
|
9
|
-
export type
|
|
9
|
+
export type CreateSyncedStateHookOptions = {
|
|
10
10
|
url?: string;
|
|
11
11
|
hooks?: HookDeps;
|
|
12
12
|
};
|
|
@@ -15,6 +15,6 @@ export type CreateSyncStateHookOptions = {
|
|
|
15
15
|
* @param options Optional overrides for endpoint and React primitives.
|
|
16
16
|
* @returns Hook that syncs state through the sync state service.
|
|
17
17
|
*/
|
|
18
|
-
export declare const
|
|
18
|
+
export declare const createSyncedStateHook: (options?: CreateSyncedStateHookOptions) => <T>(initialValue: T, key: string) => [T, Setter<T>];
|
|
19
19
|
export declare const useSyncedState: <T>(initialValue: T, key: string) => [T, Setter<T>];
|
|
20
20
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { getSyncedStateClient } from "./client-core.js";
|
|
3
|
+
import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs";
|
|
4
4
|
const defaultDeps = {
|
|
5
5
|
useState: React.useState,
|
|
6
6
|
useEffect: React.useEffect,
|
|
@@ -12,15 +12,15 @@ const defaultDeps = {
|
|
|
12
12
|
* @param options Optional overrides for endpoint and React primitives.
|
|
13
13
|
* @returns Hook that syncs state through the sync state service.
|
|
14
14
|
*/
|
|
15
|
-
export const
|
|
16
|
-
const resolvedUrl = options.url ??
|
|
15
|
+
export const createSyncedStateHook = (options = {}) => {
|
|
16
|
+
const resolvedUrl = options.url ?? DEFAULT_SYNCED_STATE_PATH;
|
|
17
17
|
const deps = options.hooks ?? defaultDeps;
|
|
18
18
|
const { useState, useEffect, useRef, useCallback } = deps;
|
|
19
19
|
return function useSyncedState(initialValue, key) {
|
|
20
20
|
if (typeof window === "undefined" && !options.hooks) {
|
|
21
21
|
return [initialValue, () => { }];
|
|
22
22
|
}
|
|
23
|
-
const client =
|
|
23
|
+
const client = getSyncedStateClient(resolvedUrl);
|
|
24
24
|
const [value, setValue] = useState(initialValue);
|
|
25
25
|
const valueRef = useRef(value);
|
|
26
26
|
valueRef.current = value;
|
|
@@ -55,4 +55,4 @@ export const createSyncStateHook = (options = {}) => {
|
|
|
55
55
|
return [value, setSyncValue];
|
|
56
56
|
};
|
|
57
57
|
};
|
|
58
|
-
export const useSyncedState =
|
|
58
|
+
export const useSyncedState = createSyncedStateHook();
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
export type
|
|
1
|
+
import { SyncedStateServer } from "./SyncedStateServer.mjs";
|
|
2
|
+
export { SyncedStateServer };
|
|
3
|
+
export type SyncedStateRouteOptions = {
|
|
4
4
|
basePath?: string;
|
|
5
|
-
resetPath?: string;
|
|
6
5
|
durableObjectName?: string;
|
|
7
6
|
};
|
|
8
7
|
/**
|
|
9
8
|
* Registers routes that forward sync state requests to the configured Durable Object namespace.
|
|
10
9
|
* @param getNamespace Function that returns the Durable Object namespace from the Worker env.
|
|
11
|
-
* @param options Optional overrides for base path
|
|
12
|
-
* @returns Router entries for the sync state API
|
|
10
|
+
* @param options Optional overrides for base path and object name.
|
|
11
|
+
* @returns Router entries for the sync state API.
|
|
13
12
|
*/
|
|
14
|
-
export declare const
|
|
13
|
+
export declare const syncedStateRoutes: (getNamespace: (env: Cloudflare.Env) => DurableObjectNamespace<SyncedStateServer>, options?: SyncedStateRouteOptions) => import("../runtime/lib/router.js").RouteDefinition<`/${string}`, import("../runtime/worker.js").RequestInfo<any, import("../runtime/worker.js").DefaultAppContext>>[];
|
|
@@ -9,52 +9,51 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var
|
|
12
|
+
var _SyncedStateProxy_stub, _SyncedStateProxy_keyHandler;
|
|
13
13
|
import { RpcTarget, newWorkersRpcResponse } from "capnweb";
|
|
14
14
|
import { env } from "cloudflare:workers";
|
|
15
15
|
import { route } from "../runtime/entries/router";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
export {
|
|
16
|
+
import { SyncedStateServer, } from "./SyncedStateServer.mjs";
|
|
17
|
+
import { DEFAULT_SYNCED_STATE_PATH } from "./constants.mjs";
|
|
18
|
+
export { SyncedStateServer };
|
|
19
19
|
const DEFAULT_SYNC_STATE_NAME = "syncedState";
|
|
20
|
-
class
|
|
20
|
+
class SyncedStateProxy extends RpcTarget {
|
|
21
21
|
constructor(stub, keyHandler) {
|
|
22
22
|
super();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
__classPrivateFieldSet(this,
|
|
26
|
-
__classPrivateFieldSet(this,
|
|
23
|
+
_SyncedStateProxy_stub.set(this, void 0);
|
|
24
|
+
_SyncedStateProxy_keyHandler.set(this, void 0);
|
|
25
|
+
__classPrivateFieldSet(this, _SyncedStateProxy_stub, stub, "f");
|
|
26
|
+
__classPrivateFieldSet(this, _SyncedStateProxy_keyHandler, keyHandler, "f");
|
|
27
27
|
}
|
|
28
28
|
async getState(key) {
|
|
29
|
-
const transformedKey = __classPrivateFieldGet(this,
|
|
30
|
-
return __classPrivateFieldGet(this,
|
|
29
|
+
const transformedKey = __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f") ? await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f").call(this, key) : key;
|
|
30
|
+
return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").getState(transformedKey);
|
|
31
31
|
}
|
|
32
32
|
async setState(value, key) {
|
|
33
|
-
const transformedKey = __classPrivateFieldGet(this,
|
|
34
|
-
return __classPrivateFieldGet(this,
|
|
33
|
+
const transformedKey = __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f") ? await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f").call(this, key) : key;
|
|
34
|
+
return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").setState(value, transformedKey);
|
|
35
35
|
}
|
|
36
36
|
async subscribe(key, client) {
|
|
37
|
-
const transformedKey = __classPrivateFieldGet(this,
|
|
38
|
-
return __classPrivateFieldGet(this,
|
|
37
|
+
const transformedKey = __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f") ? await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f").call(this, key) : key;
|
|
38
|
+
return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").subscribe(transformedKey, client);
|
|
39
39
|
}
|
|
40
40
|
async unsubscribe(key, client) {
|
|
41
|
-
const transformedKey = __classPrivateFieldGet(this,
|
|
42
|
-
return __classPrivateFieldGet(this,
|
|
41
|
+
const transformedKey = __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f") ? await __classPrivateFieldGet(this, _SyncedStateProxy_keyHandler, "f").call(this, key) : key;
|
|
42
|
+
return __classPrivateFieldGet(this, _SyncedStateProxy_stub, "f").unsubscribe(transformedKey, client);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
_SyncedStateProxy_stub = new WeakMap(), _SyncedStateProxy_keyHandler = new WeakMap();
|
|
46
46
|
/**
|
|
47
47
|
* Registers routes that forward sync state requests to the configured Durable Object namespace.
|
|
48
48
|
* @param getNamespace Function that returns the Durable Object namespace from the Worker env.
|
|
49
|
-
* @param options Optional overrides for base path
|
|
50
|
-
* @returns Router entries for the sync state API
|
|
49
|
+
* @param options Optional overrides for base path and object name.
|
|
50
|
+
* @returns Router entries for the sync state API.
|
|
51
51
|
*/
|
|
52
|
-
export const
|
|
53
|
-
const basePath = options.basePath ??
|
|
54
|
-
const resetPath = options.resetPath ?? `${basePath}/reset`;
|
|
52
|
+
export const syncedStateRoutes = (getNamespace, options = {}) => {
|
|
53
|
+
const basePath = options.basePath ?? DEFAULT_SYNCED_STATE_PATH;
|
|
55
54
|
const durableObjectName = options.durableObjectName ?? DEFAULT_SYNC_STATE_NAME;
|
|
56
55
|
const forwardRequest = async (request) => {
|
|
57
|
-
const keyHandler =
|
|
56
|
+
const keyHandler = SyncedStateServer.getKeyHandler();
|
|
58
57
|
if (!keyHandler) {
|
|
59
58
|
const namespace = getNamespace(env);
|
|
60
59
|
const id = namespace.idFromName(durableObjectName);
|
|
@@ -63,11 +62,8 @@ export const syncStateRoutes = (getNamespace, options = {}) => {
|
|
|
63
62
|
const namespace = getNamespace(env);
|
|
64
63
|
const id = namespace.idFromName(durableObjectName);
|
|
65
64
|
const coordinator = namespace.get(id);
|
|
66
|
-
const proxy = new
|
|
65
|
+
const proxy = new SyncedStateProxy(coordinator, keyHandler);
|
|
67
66
|
return newWorkersRpcResponse(request, proxy);
|
|
68
67
|
};
|
|
69
|
-
return [
|
|
70
|
-
route(basePath, ({ request }) => forwardRequest(request)),
|
|
71
|
-
route(resetPath, ({ request }) => forwardRequest(request)),
|
|
72
|
-
];
|
|
68
|
+
return [route(basePath, ({ request }) => forwardRequest(request))];
|
|
73
69
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Plugin that performs initialization workarounds that must run before
|
|
4
|
+
* the Cloudflare plugin's `configureServer` hook executes.
|
|
5
|
+
*
|
|
6
|
+
* Cloudflare plugin v1.15.0 executes the worker entry file during
|
|
7
|
+
* `configureServer` to detect exports, which triggers SSR code evaluation
|
|
8
|
+
* before normal server initialization completes. This plugin ensures
|
|
9
|
+
* required systems are initialized beforehand.
|
|
10
|
+
*/
|
|
11
|
+
export declare const cloudflarePreInitPlugin: () => Plugin;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin that performs initialization workarounds that must run before
|
|
3
|
+
* the Cloudflare plugin's `configureServer` hook executes.
|
|
4
|
+
*
|
|
5
|
+
* Cloudflare plugin v1.15.0 executes the worker entry file during
|
|
6
|
+
* `configureServer` to detect exports, which triggers SSR code evaluation
|
|
7
|
+
* before normal server initialization completes. This plugin ensures
|
|
8
|
+
* required systems are initialized beforehand.
|
|
9
|
+
*/
|
|
10
|
+
export const cloudflarePreInitPlugin = () => {
|
|
11
|
+
return {
|
|
12
|
+
name: "rwsdk:cloudflare-pre-init",
|
|
13
|
+
// context(justinvdm, 20 Jan 2025): This plugin must run before the
|
|
14
|
+
// Cloudflare plugin's `configureServer` hook. The Cloudflare plugin
|
|
15
|
+
// executes the worker entry file during `configureServer` to detect
|
|
16
|
+
// exports, and blocks the rest of the configureServer plugins until the
|
|
17
|
+
// request is complete. We must initialize required systems (SSR dependency
|
|
18
|
+
// optimizer + our own plugins to them, as well as CSS plugin's moduleCache)
|
|
19
|
+
// before this happens to prevent errors.
|
|
20
|
+
enforce: "pre",
|
|
21
|
+
async configureServer(server) {
|
|
22
|
+
// context(justinvdm, 20 Jan 2025): Initialize SSR dependency optimizer before
|
|
23
|
+
// Cloudflare plugin triggers SSR code evaluation. This ensures dependencies
|
|
24
|
+
// in `optimizeDeps.include` (like `react-dom/server.edge`) are correctly
|
|
25
|
+
// registered before they are discovered lazily.
|
|
26
|
+
if (server.environments.ssr?.depsOptimizer) {
|
|
27
|
+
await server.environments.ssr.depsOptimizer.init();
|
|
28
|
+
}
|
|
29
|
+
// context(justinvdm, 20 Jan 2025): Initialize CSS plugin's shared moduleCache
|
|
30
|
+
// before CSS modules are processed. The Cloudflare plugin's export detection
|
|
31
|
+
// can trigger CSS module processing in the SSR environment during `configureServer`,
|
|
32
|
+
// which happens before `initServer` runs. Vite's CSS plugin uses a shared
|
|
33
|
+
// `moduleCache` that is initialized in the client environment's `buildStart` hook.
|
|
34
|
+
// By calling `buildStart` here (which is idempotent), we ensure the CSS plugin's
|
|
35
|
+
// cache is initialized before CSS modules are processed, preventing "Cannot read
|
|
36
|
+
// properties of undefined (reading 'set')" errors.
|
|
37
|
+
await server.environments.client.pluginContainer.buildStart();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -101,9 +101,12 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
|
|
|
101
101
|
log("Skipping optimizeDeps and aliasing for environment: %s", env);
|
|
102
102
|
}
|
|
103
103
|
},
|
|
104
|
-
resolveId(source) {
|
|
105
|
-
// Skip during directive scanning to avoid performance issues
|
|
106
|
-
|
|
104
|
+
resolveId(source, _importer, options) {
|
|
105
|
+
// Skip during our directive scanning to avoid performance issues
|
|
106
|
+
// context(justinvdm, 20 Jan 2025): We check options.custom?.rwsdk?.directiveScan to distinguish
|
|
107
|
+
// between our directive scan (which should skip) and external calls like Cloudflare's early
|
|
108
|
+
// dispatch (which should be handled normally).
|
|
109
|
+
if (options?.custom?.rwsdk?.directiveScan === true) {
|
|
107
110
|
return;
|
|
108
111
|
}
|
|
109
112
|
if (source !== `${config.virtualModuleName}.js`) {
|
|
@@ -144,10 +147,6 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
|
|
|
144
147
|
return source;
|
|
145
148
|
},
|
|
146
149
|
async load(id) {
|
|
147
|
-
// Skip during directive scanning to avoid performance issues
|
|
148
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
150
|
if (id === config.virtualModuleName + ".js") {
|
|
152
151
|
log("Loading %s module with %d files", config.virtualModuleName, files.size);
|
|
153
152
|
const environment = this.environment?.name || "client";
|
|
@@ -56,10 +56,6 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
|
|
|
56
56
|
});
|
|
57
57
|
},
|
|
58
58
|
async transform(code, id) {
|
|
59
|
-
// Skip during directive scanning to avoid performance issues
|
|
60
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
59
|
if (isBuild &&
|
|
64
60
|
this.environment?.name === "worker" &&
|
|
65
61
|
process.env.RWSDK_BUILD_PASS !== "worker") {
|
|
@@ -4,10 +4,6 @@ export const injectVitePreamble = ({ clientEntryPoints, projectRootDir, }) => ({
|
|
|
4
4
|
name: "rwsdk:inject-vite-preamble",
|
|
5
5
|
apply: "serve",
|
|
6
6
|
transform(code, id) {
|
|
7
|
-
// Skip during directive scanning to avoid performance issues
|
|
8
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
7
|
if (this.environment.name !== "client") {
|
|
12
8
|
return;
|
|
13
9
|
}
|
|
@@ -135,23 +135,10 @@ export const knownDepsResolverPlugin = ({ projectRootDir, }) => {
|
|
|
135
135
|
return [
|
|
136
136
|
{
|
|
137
137
|
name: "rwsdk:known-dependencies-resolver:config",
|
|
138
|
-
enforce: "pre",
|
|
139
138
|
config(config, { command }) {
|
|
140
139
|
isBuild = command === "build";
|
|
141
140
|
log("Configuring plugin for command=%s", command);
|
|
142
141
|
},
|
|
143
|
-
async configureServer(server) {
|
|
144
|
-
// context(justinvdm, 19 Nov 2025): This hook must run before the
|
|
145
|
-
// Cloudflare plugin's `configureServer` hook, so we use `enforce: 'pre'`.
|
|
146
|
-
// The Cloudflare plugin's hook executes the worker entry file to discover
|
|
147
|
-
// its exports. This can trigger the evaluation of SSR code. We must initialize
|
|
148
|
-
// the SSR dependency optimizer *before* that happens to ensure that any
|
|
149
|
-
// dependencies in `optimizeDeps.include` (like `react-dom/server.edge`)
|
|
150
|
-
// are correctly registered before they are discovered lazily.
|
|
151
|
-
if (server.environments.ssr?.depsOptimizer) {
|
|
152
|
-
await server.environments.ssr.depsOptimizer.init();
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
142
|
configResolved(config) {
|
|
156
143
|
log("Setting up resolve aliases and optimizeDeps for each environment");
|
|
157
144
|
// Set up aliases and optimizeDeps for each environment
|
|
@@ -191,9 +178,12 @@ export const knownDepsResolverPlugin = ({ projectRootDir, }) => {
|
|
|
191
178
|
{
|
|
192
179
|
name: "rwsdk:known-dependencies-resolver:resolveId",
|
|
193
180
|
enforce: "pre",
|
|
194
|
-
async resolveId(id, importer) {
|
|
195
|
-
// Skip during directive scanning to avoid performance issues
|
|
196
|
-
|
|
181
|
+
async resolveId(id, importer, options) {
|
|
182
|
+
// Skip during our directive scanning to avoid performance issues
|
|
183
|
+
// context(justinvdm, 20 Jan 2025): We check options.custom?.rwsdk?.directiveScan to distinguish
|
|
184
|
+
// between our directive scan (which should skip) and external calls like Cloudflare's early
|
|
185
|
+
// dispatch (which should be handled normally).
|
|
186
|
+
if (options?.custom?.rwsdk?.directiveScan === true) {
|
|
197
187
|
return;
|
|
198
188
|
}
|
|
199
189
|
if (!isBuild) {
|
|
@@ -10,6 +10,7 @@ import { pathExists } from "fs-extra";
|
|
|
10
10
|
import { $ } from "../lib/$.mjs";
|
|
11
11
|
import { findWranglerConfig } from "../lib/findWranglerConfig.mjs";
|
|
12
12
|
import { hasPkgScript } from "../lib/hasPkgScript.mjs";
|
|
13
|
+
import { cloudflarePreInitPlugin } from "./cloudflarePreInitPlugin.mjs";
|
|
13
14
|
import { configPlugin } from "./configPlugin.mjs";
|
|
14
15
|
import { devServerTimingPlugin } from "./devServerTimingPlugin.mjs";
|
|
15
16
|
import { directiveModulesDevPlugin } from "./directiveModulesDevPlugin.mjs";
|
|
@@ -106,6 +107,7 @@ export const redwoodPlugin = async (options = {}) => {
|
|
|
106
107
|
projectRootDir,
|
|
107
108
|
}),
|
|
108
109
|
knownDepsResolverPlugin({ projectRootDir }),
|
|
110
|
+
cloudflarePreInitPlugin(),
|
|
109
111
|
tsconfigPaths({ root: projectRootDir }),
|
|
110
112
|
shouldIncludeCloudflarePlugin
|
|
111
113
|
? cloudflare({
|
|
@@ -87,8 +87,6 @@ export function classifyModule({ contents, inheritedEnv, }) {
|
|
|
87
87
|
}
|
|
88
88
|
export const runDirectivesScan = async ({ rootConfig, environments, clientFiles, serverFiles, entries: initialEntries, }) => {
|
|
89
89
|
deferredLog("\n… (rwsdk) Scanning for 'use client' and 'use server' directives...");
|
|
90
|
-
// Set environment variable to indicate scanning is in progress
|
|
91
|
-
process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE = "true";
|
|
92
90
|
try {
|
|
93
91
|
const fileContentCache = new Map();
|
|
94
92
|
const directiveCheckCache = new Map();
|
|
@@ -197,6 +195,19 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
197
195
|
log("Resolution result:", resolved);
|
|
198
196
|
const resolvedPath = resolved?.id;
|
|
199
197
|
if (resolvedPath && path.isAbsolute(resolvedPath)) {
|
|
198
|
+
try {
|
|
199
|
+
const stats = await fsp.stat(resolvedPath);
|
|
200
|
+
if (stats.isDirectory()) {
|
|
201
|
+
log("Resolved path is a directory, marking as external to avoid scan error:", resolvedPath);
|
|
202
|
+
return { external: true };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
// This can happen for virtual modules or special paths that don't
|
|
207
|
+
// exist on the filesystem. We can safely externalize them.
|
|
208
|
+
log("Could not stat resolved path, marking as external:", resolvedPath);
|
|
209
|
+
return { external: true };
|
|
210
|
+
}
|
|
200
211
|
// Normalize the path for esbuild compatibility
|
|
201
212
|
const normalizedPath = normalizeModulePath(resolvedPath, rootConfig.root, { absolute: true });
|
|
202
213
|
log("Normalized path:", normalizedPath);
|
|
@@ -290,8 +301,6 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
290
301
|
throw new Error(`RWSDK directive scan failed:\n${e.stack}`);
|
|
291
302
|
}
|
|
292
303
|
finally {
|
|
293
|
-
// Always clear the scanning flag when done
|
|
294
|
-
delete process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE;
|
|
295
304
|
deferredLog("✔ (rwsdk) Done scanning for 'use client' and 'use server' directives.");
|
|
296
305
|
process.env.VERBOSE &&
|
|
297
306
|
log("Client/server files after scanning: client=%O, server=%O", Array.from(clientFiles), Array.from(serverFiles));
|
|
@@ -58,12 +58,14 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
58
58
|
config.optimizeDeps.esbuildOptions.plugins.push({
|
|
59
59
|
name: "rwsdk-ssr-external",
|
|
60
60
|
setup(build) {
|
|
61
|
+
console.log("[TIMING] rwsdk-ssr-external.setup: START - Plugin setup function called");
|
|
61
62
|
log("Setting up esbuild plugin to mark rwsdk/__ssr paths as external for worker");
|
|
62
63
|
build.onResolve({ filter: /.*$/ }, (args) => {
|
|
63
64
|
process.env.VERBOSE &&
|
|
64
65
|
log("Esbuild onResolve called for path=%s, args=%O", args.path, args);
|
|
65
66
|
if (args.path === "rwsdk/__ssr_bridge" ||
|
|
66
67
|
args.path.startsWith(VIRTUAL_SSR_PREFIX)) {
|
|
68
|
+
console.log(`[TIMING] rwsdk-ssr-external.onResolve: Intercepted ${args.path}`);
|
|
67
69
|
log("Marking as external: %s", args.path);
|
|
68
70
|
return {
|
|
69
71
|
path: args.path,
|
|
@@ -71,14 +73,19 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
71
73
|
};
|
|
72
74
|
}
|
|
73
75
|
});
|
|
76
|
+
console.log("[TIMING] rwsdk-ssr-external.setup: COMPLETE - onResolve hook registered");
|
|
74
77
|
},
|
|
75
78
|
});
|
|
76
79
|
log("Worker environment esbuild configuration complete");
|
|
77
80
|
}
|
|
78
81
|
},
|
|
79
|
-
async resolveId(id, importer) {
|
|
80
|
-
// Skip during directive scanning to avoid performance issues
|
|
81
|
-
|
|
82
|
+
async resolveId(id, importer, options) {
|
|
83
|
+
// Skip during our directive scanning to avoid performance issues
|
|
84
|
+
// context(justinvdm, 20 Jan 2025): We check options.custom?.rwsdk?.directiveScan to distinguish
|
|
85
|
+
// between our directive scan (which should skip) and external calls like Cloudflare's early
|
|
86
|
+
// dispatch (which should be handled normally). This prevents race conditions where external
|
|
87
|
+
// calls happen during directive scanning.
|
|
88
|
+
if (options?.custom?.rwsdk?.directiveScan === true) {
|
|
82
89
|
return;
|
|
83
90
|
}
|
|
84
91
|
// context(justinvdm, 19 Nov 2025):
|
|
@@ -137,10 +144,6 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
137
144
|
}
|
|
138
145
|
},
|
|
139
146
|
async load(id) {
|
|
140
|
-
// Skip during directive scanning to avoid performance issues
|
|
141
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
147
|
if (id.startsWith(VIRTUAL_SSR_PREFIX) &&
|
|
145
148
|
this.environment.name === "worker") {
|
|
146
149
|
const realId = id.slice(VIRTUAL_SSR_PREFIX.length);
|
|
@@ -319,10 +319,6 @@ export const transformJsxScriptTagsPlugin = ({ clientEntryPoints, projectRootDir
|
|
|
319
319
|
isBuild = config.command === "build";
|
|
320
320
|
},
|
|
321
321
|
async transform(code, id) {
|
|
322
|
-
// Skip during directive scanning to avoid performance issues
|
|
323
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
322
|
if (isBuild &&
|
|
327
323
|
this.environment?.name === "worker" &&
|
|
328
324
|
process.env.RWSDK_BUILD_PASS !== "worker") {
|
|
@@ -3,9 +3,12 @@ export const virtualPlugin = (name, load) => {
|
|
|
3
3
|
name = "virtual:" + name;
|
|
4
4
|
return {
|
|
5
5
|
name: `rwsdk:virtual-${name}`,
|
|
6
|
-
resolveId(source, _importer,
|
|
7
|
-
// Skip during directive scanning to avoid performance issues
|
|
8
|
-
|
|
6
|
+
resolveId(source, _importer, options) {
|
|
7
|
+
// Skip during our directive scanning to avoid performance issues
|
|
8
|
+
// context(justinvdm, 20 Jan 2025): We check options.custom?.rwsdk?.directiveScan to distinguish
|
|
9
|
+
// between our directive scan (which should skip) and external calls like Cloudflare's early
|
|
10
|
+
// dispatch (which should be handled normally).
|
|
11
|
+
if (options?.custom?.rwsdk?.directiveScan === true) {
|
|
9
12
|
return;
|
|
10
13
|
}
|
|
11
14
|
if (source === name || source.startsWith(`${name}?`)) {
|
|
@@ -14,10 +17,6 @@ export const virtualPlugin = (name, load) => {
|
|
|
14
17
|
return;
|
|
15
18
|
},
|
|
16
19
|
load(id, options) {
|
|
17
|
-
// Skip during directive scanning to avoid performance issues
|
|
18
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
20
|
if (id === `\0${name}` || id.startsWith(`\0${name}?`)) {
|
|
22
21
|
return load.apply(this, [id, options]);
|
|
23
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rwsdk",
|
|
3
|
-
"version": "1.0.0-beta.30-test.
|
|
3
|
+
"version": "1.0.0-beta.30-test.20251120213828",
|
|
4
4
|
"description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -110,8 +110,8 @@
|
|
|
110
110
|
"default": "./dist/use-synced-state/client.js"
|
|
111
111
|
},
|
|
112
112
|
"./use-synced-state/worker": {
|
|
113
|
-
"types": "./dist/use-synced-state/worker.d.
|
|
114
|
-
"
|
|
113
|
+
"types": "./dist/use-synced-state/worker.d.mts",
|
|
114
|
+
"workerd": "./dist/use-synced-state/worker.mjs"
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
117
|
"keywords": [
|
|
@@ -157,7 +157,6 @@
|
|
|
157
157
|
"@types/react-dom": "~19.1.2",
|
|
158
158
|
"@types/react-is": "~19.0.0",
|
|
159
159
|
"@vitejs/plugin-react": "~5.0.0",
|
|
160
|
-
"capnweb": "~0.2.0",
|
|
161
160
|
"chokidar": "~4.0.0",
|
|
162
161
|
"debug": "~4.4.0",
|
|
163
162
|
"decompress": "~4.2.1",
|
|
@@ -187,6 +186,7 @@
|
|
|
187
186
|
},
|
|
188
187
|
"peerDependencies": {
|
|
189
188
|
"@cloudflare/vite-plugin": "^1.13.10",
|
|
189
|
+
"capnweb": "~0.2.0",
|
|
190
190
|
"react": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
|
|
191
191
|
"react-dom": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
|
|
192
192
|
"react-server-dom-webpack": ">=19.2.0-0 <19.3.0 || >=19.3.0-0 <20.0.0",
|
|
@@ -196,6 +196,7 @@
|
|
|
196
196
|
"packageManager": "pnpm@10.0.0+sha512.b8fef5494bd3fe4cbd4edabd0745df2ee5be3e4b0b8b08fa643aa3e4c6702ccc0f00d68fa8a8c9858a735a0032485a44990ed2810526c875e416f001b17df12b",
|
|
197
197
|
"devDependencies": {
|
|
198
198
|
"@cloudflare/vite-plugin": "1.13.3",
|
|
199
|
+
"capnweb": "~0.2.0",
|
|
199
200
|
"@types/debug": "~4.1.12",
|
|
200
201
|
"@types/js-beautify": "~1.14.3",
|
|
201
202
|
"@types/lodash": "~4.17.16",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { RpcStub } from "capnweb";
|
|
2
|
-
import { DurableObject } from "cloudflare:workers";
|
|
3
|
-
export type SyncStateValue = unknown;
|
|
4
|
-
type OnSetHandler = (key: string, value: SyncStateValue) => void;
|
|
5
|
-
type OnGetHandler = (key: string, value: SyncStateValue | undefined) => void;
|
|
6
|
-
/**
|
|
7
|
-
* Durable Object that keeps shared state for multiple clients and notifies subscribers.
|
|
8
|
-
*/
|
|
9
|
-
export declare class SyncStateServer extends DurableObject {
|
|
10
|
-
#private;
|
|
11
|
-
static registerKeyHandler(handler: (key: string) => Promise<string>): void;
|
|
12
|
-
static getKeyHandler(): ((key: string) => Promise<string>) | null;
|
|
13
|
-
static registerSetStateHandler(handler: OnSetHandler | null): void;
|
|
14
|
-
static registerGetStateHandler(handler: OnGetHandler | null): void;
|
|
15
|
-
getState(key: string): SyncStateValue;
|
|
16
|
-
setState(value: SyncStateValue, key: string): void;
|
|
17
|
-
subscribe(key: string, client: RpcStub<(value: SyncStateValue) => void>): void;
|
|
18
|
-
unsubscribe(key: string, client: RpcStub<(value: SyncStateValue) => void>): void;
|
|
19
|
-
}
|
|
20
|
-
export {};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { React } from "../runtime/client/client";
|
|
2
|
-
type HookDeps = {
|
|
3
|
-
useState: typeof React.useState;
|
|
4
|
-
useEffect: typeof React.useEffect;
|
|
5
|
-
useRef: typeof React.useRef;
|
|
6
|
-
useCallback: typeof React.useCallback;
|
|
7
|
-
};
|
|
8
|
-
type Setter<T> = (value: T | ((previous: T) => T)) => void;
|
|
9
|
-
export type CreateSyncStateHookOptions = {
|
|
10
|
-
url?: string;
|
|
11
|
-
hooks?: HookDeps;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Builds a `useSyncedState` hook configured with optional endpoint and hook overrides.
|
|
15
|
-
* @param options Optional overrides for endpoint and React primitives.
|
|
16
|
-
* @returns Hook that syncs state through the sync state service.
|
|
17
|
-
*/
|
|
18
|
-
export declare const createSyncStateHook: (options?: CreateSyncStateHookOptions) => <T>(initialValue: T, key: string) => [T, Setter<T>];
|
|
19
|
-
export declare const useSyncedState: <T>(initialValue: T, key: string) => [T, Setter<T>];
|
|
20
|
-
export {};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { React } from "../runtime/client/client";
|
|
2
|
-
import { getSyncStateClient } from "./client";
|
|
3
|
-
import { DEFAULT_SYNC_STATE_PATH } from "./constants.mjs";
|
|
4
|
-
const defaultDeps = {
|
|
5
|
-
useState: React.useState,
|
|
6
|
-
useEffect: React.useEffect,
|
|
7
|
-
useRef: React.useRef,
|
|
8
|
-
useCallback: React.useCallback,
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Builds a `useSyncedState` hook configured with optional endpoint and hook overrides.
|
|
12
|
-
* @param options Optional overrides for endpoint and React primitives.
|
|
13
|
-
* @returns Hook that syncs state through the sync state service.
|
|
14
|
-
*/
|
|
15
|
-
export const createSyncStateHook = (options = {}) => {
|
|
16
|
-
const resolvedUrl = options.url ?? DEFAULT_SYNC_STATE_PATH;
|
|
17
|
-
const deps = options.hooks ?? defaultDeps;
|
|
18
|
-
const { useState, useEffect, useRef, useCallback } = deps;
|
|
19
|
-
return function useSyncedState(initialValue, key) {
|
|
20
|
-
if (typeof window === "undefined" && !options.hooks) {
|
|
21
|
-
return [initialValue, () => { }];
|
|
22
|
-
}
|
|
23
|
-
const client = getSyncStateClient(resolvedUrl);
|
|
24
|
-
const [value, setValue] = useState(initialValue);
|
|
25
|
-
const valueRef = useRef(value);
|
|
26
|
-
valueRef.current = value;
|
|
27
|
-
const setSyncValue = useCallback((nextValue) => {
|
|
28
|
-
const resolved = typeof nextValue === "function"
|
|
29
|
-
? nextValue(valueRef.current)
|
|
30
|
-
: nextValue;
|
|
31
|
-
setValue(resolved);
|
|
32
|
-
valueRef.current = resolved;
|
|
33
|
-
void client.setState(resolved, key);
|
|
34
|
-
}, [client, key, setValue, valueRef]);
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
let isActive = true;
|
|
37
|
-
const handleUpdate = (next) => {
|
|
38
|
-
if (isActive) {
|
|
39
|
-
setValue(next);
|
|
40
|
-
valueRef.current = next;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
void client.getState(key).then((existing) => {
|
|
44
|
-
if (existing !== undefined && isActive) {
|
|
45
|
-
setValue(existing);
|
|
46
|
-
valueRef.current = existing;
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
void client.subscribe(key, handleUpdate);
|
|
50
|
-
return () => {
|
|
51
|
-
isActive = false;
|
|
52
|
-
void client.unsubscribe(key, handleUpdate);
|
|
53
|
-
};
|
|
54
|
-
}, [client, key, setValue, valueRef]);
|
|
55
|
-
return [value, setSyncValue];
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
export const useSyncedState = createSyncStateHook();
|