rwsdk 0.1.10 → 0.1.12

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.
@@ -23,10 +23,13 @@ export const renderRscThenableToHtmlStream = async ({ thenable, Document, reques
23
23
  nonce: requestInfo.rw.nonce,
24
24
  onError(error, { componentStack }) {
25
25
  try {
26
+ if (!error) {
27
+ error = new Error(`A falsy value was thrown during rendering: ${String(error)}.`);
28
+ }
26
29
  const message = error
27
30
  ? (error.stack ?? error.message ?? error)
28
31
  : error;
29
- const wrappedMessage = `Error rendering RSC to HTML stream: ${message}\n\nComponent stack:\n${componentStack}`;
32
+ const wrappedMessage = `${message}\n\nComponent stack:${componentStack}`;
30
33
  if (error instanceof Error) {
31
34
  const wrappedError = new Error(wrappedMessage);
32
35
  wrappedError.stack = error.stack;
@@ -136,7 +136,15 @@ export const debugSync = async (opts) => {
136
136
  throw e;
137
137
  }
138
138
  // Initial sync for watch mode. We do it *after* acquiring the lock.
139
- await performSync(sdkDir, targetDir);
139
+ let initialSyncOk = false;
140
+ try {
141
+ await performSync(sdkDir, targetDir);
142
+ initialSyncOk = true;
143
+ }
144
+ catch (error) {
145
+ console.error("❌ Initial sync failed:", error);
146
+ console.log(" Still watching for changes...");
147
+ }
140
148
  const filesToWatch = [
141
149
  path.join(sdkDir, "src"),
142
150
  path.join(sdkDir, "types"),
@@ -169,8 +177,14 @@ export const debugSync = async (opts) => {
169
177
  /* ignore kill errors */
170
178
  });
171
179
  }
172
- await performSync(sdkDir, targetDir);
173
- runWatchedCommand();
180
+ try {
181
+ await performSync(sdkDir, targetDir);
182
+ runWatchedCommand();
183
+ }
184
+ catch (error) {
185
+ console.error("❌ Sync failed:", error);
186
+ console.log(" Still watching for changes...");
187
+ }
174
188
  });
175
189
  const cleanup = async () => {
176
190
  console.log("\nCleaning up...");
@@ -182,7 +196,9 @@ export const debugSync = async (opts) => {
182
196
  };
183
197
  process.on("SIGINT", cleanup);
184
198
  process.on("SIGTERM", cleanup);
185
- runWatchedCommand();
199
+ if (initialSyncOk) {
200
+ runWatchedCommand();
201
+ }
186
202
  };
187
203
  if (import.meta.url === new URL(process.argv[1], import.meta.url).href) {
188
204
  const args = process.argv.slice(2);
@@ -4,7 +4,6 @@ import debug from "debug";
4
4
  import { transformClientComponents } from "./transformClientComponents.mjs";
5
5
  import { transformServerFunctions } from "./transformServerFunctions.mjs";
6
6
  import { normalizeModulePath } from "./normalizeModulePath.mjs";
7
- import { invalidateModule } from "./invalidateModule.mjs";
8
7
  const log = debug("rwsdk:vite:rsc-directives-plugin");
9
8
  const verboseLog = debug("verbose:rwsdk:vite:rsc-directives-plugin");
10
9
  const getLoader = (filePath) => {
@@ -48,7 +47,7 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
48
47
  devServer.environments[environment].depsOptimizer?.registerMissingImport(resolvedId, fullPath);
49
48
  if (isAfterFirstResponse && !hadFile) {
50
49
  log("Invalidating cache for lookup module %s after adding module id=%s", lookupModule, id);
51
- invalidateModule(devServer, environment, `virtual:use-${kind}-lookup`);
50
+ //invalidateModule(devServer, environment, `virtual:use-${kind}-lookup`);
52
51
  }
53
52
  }
54
53
  };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Finds __vite_ssr_import__ and __vite_ssr_dynamic_import__ specifiers in the code.
3
+ * @param id The file identifier for language detection.
4
+ * @param code The code to search for SSR imports.
5
+ * @param log Optional logger function for debug output.
6
+ * @returns Object with arrays of static and dynamic import specifiers.
7
+ */
8
+ export declare function findSsrImportSpecifiers(id: string, code: string, log?: (...args: any[]) => void): {
9
+ imports: string[];
10
+ dynamicImports: string[];
11
+ };
@@ -0,0 +1,51 @@
1
+ import { parse as sgParse, Lang as SgLang, Lang } from "@ast-grep/napi";
2
+ import path from "path";
3
+ /**
4
+ * Finds __vite_ssr_import__ and __vite_ssr_dynamic_import__ specifiers in the code.
5
+ * @param id The file identifier for language detection.
6
+ * @param code The code to search for SSR imports.
7
+ * @param log Optional logger function for debug output.
8
+ * @returns Object with arrays of static and dynamic import specifiers.
9
+ */
10
+ export function findSsrImportSpecifiers(id, code, log) {
11
+ const ext = path.extname(id).toLowerCase();
12
+ const lang = ext === ".tsx" || ext === ".jsx" ? Lang.Tsx : SgLang.TypeScript;
13
+ const logger = log ?? (() => { });
14
+ const imports = [];
15
+ const dynamicImports = [];
16
+ try {
17
+ const root = sgParse(lang, code);
18
+ const patterns = [
19
+ {
20
+ pattern: `__vite_ssr_import__("$SPECIFIER")`,
21
+ list: imports,
22
+ },
23
+ {
24
+ pattern: `__vite_ssr_import__('$SPECIFIER')`,
25
+ list: imports,
26
+ },
27
+ {
28
+ pattern: `__vite_ssr_dynamic_import__("$SPECIFIER")`,
29
+ list: dynamicImports,
30
+ },
31
+ {
32
+ pattern: `__vite_ssr_dynamic_import__('$SPECIFIER')`,
33
+ list: dynamicImports,
34
+ },
35
+ ];
36
+ for (const { pattern, list } of patterns) {
37
+ const matches = root.root().findAll(pattern);
38
+ for (const match of matches) {
39
+ const specifier = match.getMatch("SPECIFIER")?.text();
40
+ if (specifier) {
41
+ list.push(specifier);
42
+ logger(`Found SSR import specifier: %s in pattern: %s`, specifier, pattern);
43
+ }
44
+ }
45
+ }
46
+ }
47
+ catch (err) {
48
+ logger("Error parsing code for SSR imports: %O", err);
49
+ }
50
+ return { imports, dynamicImports };
51
+ }
@@ -0,0 +1,2 @@
1
+ import type { ViteDevServer } from "vite";
2
+ export declare const invalidateClientModule: (devServer: ViteDevServer, filePath: string) => void;
@@ -0,0 +1,8 @@
1
+ import { invalidateModule } from "./invalidateModule.mjs";
2
+ import { normalizeModulePath } from "./normalizeModulePath.mjs";
3
+ import { VIRTUAL_SSR_PREFIX } from "./ssrBridgePlugin.mjs";
4
+ export const invalidateClientModule = (devServer, filePath) => {
5
+ invalidateModule(devServer, "ssr", filePath);
6
+ invalidateModule(devServer, "client", filePath);
7
+ invalidateModule(devServer, "worker", VIRTUAL_SSR_PREFIX + normalizeModulePath(devServer.config.root, filePath));
8
+ };
@@ -0,0 +1,2 @@
1
+ import type { ViteDevServer } from "vite";
2
+ export declare const invalidateModule: (devServer: ViteDevServer, environment: string, id: string) => void;
@@ -0,0 +1,14 @@
1
+ import debug from "debug";
2
+ const log = debug("rwsdk:vite:invalidate-module");
3
+ const verboseLog = debug("verbose:rwsdk:vite:invalidate-module");
4
+ export const invalidateModule = (devServer, environment, id) => {
5
+ const [rawId, _query] = id.split("?");
6
+ log("Invalidating module: id=%s, environment=%s", id, environment);
7
+ const moduleNode = devServer?.environments[environment]?.moduleGraph.idToModuleMap.get(rawId);
8
+ if (moduleNode) {
9
+ devServer?.environments[environment]?.moduleGraph.invalidateModule(moduleNode);
10
+ }
11
+ else {
12
+ verboseLog("Module not found: id=%s, environment=%s", id, environment);
13
+ }
14
+ };
@@ -0,0 +1,2 @@
1
+ import type { ViteDevServer } from "vite";
2
+ export declare const invalidateSSRModule: (devServer: ViteDevServer, filePath: string) => void;
@@ -0,0 +1,7 @@
1
+ import { invalidateModule } from "./invalidateModule.mjs";
2
+ import { normalizeModulePath } from "./normalizeModulePath.mjs";
3
+ import { VIRTUAL_SSR_PREFIX } from "./ssrBridgePlugin.mjs";
4
+ export const invalidateSSRModule = (devServer, filePath) => {
5
+ invalidateModule(devServer, "ssr", filePath);
6
+ invalidateModule(devServer, "worker", VIRTUAL_SSR_PREFIX + normalizeModulePath(devServer.config.root, filePath));
7
+ };
@@ -1,8 +1,12 @@
1
1
  import { resolve } from "node:path";
2
2
  import colors from "picocolors";
3
3
  import { readFile } from "node:fs/promises";
4
+ import debug from "debug";
5
+ import { VIRTUAL_SSR_PREFIX } from "./ssrBridgePlugin.mjs";
6
+ import { normalizeModulePath } from "./normalizeModulePath.mjs";
4
7
  import { getShortName } from "../lib/getShortName.mjs";
5
8
  import { pathExists } from "fs-extra";
9
+ const verboseLog = debug("verbose:rwsdk:vite:hmr-plugin");
6
10
  const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => {
7
11
  // Prevent infinite recursion
8
12
  if (seen.has(module))
@@ -68,10 +72,11 @@ export const miniflareHMRPlugin = (givenOptions) => [
68
72
  {
69
73
  name: "rwsdk:miniflare-hmr",
70
74
  async hotUpdate(ctx) {
75
+ verboseLog("Hot update: (env=%s) %s\nModule graph:\n\n%s", this.environment.name, ctx.file, dumpFullModuleGraph(ctx.server, this.environment.name));
71
76
  const environment = givenOptions.viteEnvironment.name;
72
77
  const entry = givenOptions.workerEntryPathname;
73
78
  if (!["client", environment].includes(this.environment.name)) {
74
- return;
79
+ return [];
75
80
  }
76
81
  // todo(justinvdm, 12 Dec 2024): Skip client references
77
82
  const modules = Array.from(ctx.server.environments[environment].moduleGraph.getModulesByFile(ctx.file) ?? []);
@@ -119,6 +124,11 @@ export const miniflareHMRPlugin = (givenOptions) => [
119
124
  if (m) {
120
125
  ctx.server.environments.client.moduleGraph.invalidateModule(m, new Set(), ctx.timestamp, true);
121
126
  }
127
+ const virtualSSRModule = ctx.server.environments[environment].moduleGraph.idToModuleMap.get(VIRTUAL_SSR_PREFIX +
128
+ normalizeModulePath(givenOptions.rootDir, ctx.file));
129
+ if (virtualSSRModule) {
130
+ ctx.server.environments.client.moduleGraph.invalidateModule(virtualSSRModule, new Set(), ctx.timestamp, true);
131
+ }
122
132
  ctx.server.environments.client.hot.send({
123
133
  type: "custom",
124
134
  event: "rsc:update",
@@ -131,3 +141,35 @@ export const miniflareHMRPlugin = (givenOptions) => [
131
141
  },
132
142
  },
133
143
  ];
144
+ function dumpFullModuleGraph(server, environment, { includeDisconnected = true } = {}) {
145
+ const moduleGraph = server.environments[environment].moduleGraph;
146
+ const seen = new Set();
147
+ const output = [];
148
+ function walk(node, depth = 0) {
149
+ const id = node.id || node.url;
150
+ if (!id || seen.has(id))
151
+ return;
152
+ seen.add(id);
153
+ const pad = " ".repeat(depth);
154
+ const suffix = node.id?.startsWith("virtual:") ? " [virtual]" : "";
155
+ output.push(`${pad}- ${id}${suffix}`);
156
+ for (const dep of node.importedModules) {
157
+ walk(dep, depth + 1);
158
+ }
159
+ }
160
+ // Start with all modules with no importers (roots)
161
+ const roots = Array.from(moduleGraph.urlToModuleMap.values()).filter((mod) => mod.importers.size === 0);
162
+ for (const root of roots) {
163
+ walk(root);
164
+ }
165
+ // If requested, show disconnected modules too
166
+ if (includeDisconnected) {
167
+ for (const mod of moduleGraph.urlToModuleMap.values()) {
168
+ const id = mod.id || mod.url;
169
+ if (!seen.has(id)) {
170
+ output.push(`- ${id} [disconnected]`);
171
+ }
172
+ }
173
+ }
174
+ return output.join("\n");
175
+ }
@@ -1,6 +1,6 @@
1
1
  import debug from "debug";
2
2
  import { SSR_BRIDGE_PATH } from "../lib/constants.mjs";
3
- import { invalidateModule } from "./invalidateModule.mjs";
3
+ import { findSsrImportSpecifiers } from "./findSsrSpecifiers.mjs";
4
4
  const log = debug("rwsdk:vite:ssr-bridge-plugin");
5
5
  const verboseLog = debug("verbose:rwsdk:vite:ssr-bridge-plugin");
6
6
  export const VIRTUAL_SSR_PREFIX = "virtual:rwsdk:ssr:";
@@ -54,7 +54,6 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
54
54
  // SSR modules, so we return the virtual id so that the dynamic loading
55
55
  // can happen in load()
56
56
  if (id.startsWith(VIRTUAL_SSR_PREFIX)) {
57
- invalidateModule(devServer, "worker", id);
58
57
  log("Returning virtual SSR id for dev: %s", id);
59
58
  return id;
60
59
  }
@@ -65,7 +64,6 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
65
64
  if (id === "rwsdk/__ssr_bridge" && this.environment.name === "worker") {
66
65
  const virtualId = `${VIRTUAL_SSR_PREFIX}${id}`;
67
66
  log("Bridge module case (dev): id=%s matches rwsdk/__ssr_bridge in worker environment, returning virtual id=%s", id, virtualId);
68
- invalidateModule(devServer, "worker", virtualId);
69
67
  return virtualId;
70
68
  }
71
69
  }
@@ -96,9 +94,28 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
96
94
  }
97
95
  const code = "code" in result ? result.code : undefined;
98
96
  log("Fetched SSR module code length: %d", code?.length || 0);
99
- // context(justinvdm, 27 May 2025): Prefix all imports in SSR modules so that they're separate in module graph from non-SSR
97
+ if (!code) {
98
+ return;
99
+ }
100
+ const { imports, dynamicImports } = findSsrImportSpecifiers(realId, code, verboseLog);
101
+ const allSpecifiers = [...new Set([...imports, ...dynamicImports])];
102
+ const switchCases = allSpecifiers
103
+ .map((specifier) => ` case "${specifier}": return import("${VIRTUAL_SSR_PREFIX}${specifier}");`)
104
+ .join("\n");
100
105
  const transformedCode = `
101
- await (async function(__vite_ssr_import__, __vite_ssr_dynamic_import__) {${code}})((id) => __vite_ssr_import__('/@id/${VIRTUAL_SSR_PREFIX}'+id), (id) => __vite_ssr_dynamic_import__('/@id/${VIRTUAL_SSR_PREFIX}'+id));
106
+ await (async function(__vite_ssr_import__, __vite_ssr_dynamic_import__) {${code}})(
107
+ (id, ...args) => ssrImport(id, false, ...args),
108
+ (id, ...args) => ssrImport(id, true, ...args)
109
+ );
110
+
111
+ function ssrImport(id, isDynamic, ...args) {
112
+ switch (id) {
113
+ ${switchCases}
114
+ }
115
+
116
+ const virtualId = '/@id/${VIRTUAL_SSR_PREFIX}' + id;
117
+ return isDynamic ? __vite_ssr_dynamic_import__(virtualId, ...args) : __vite_ssr_import__(virtualId, ...args);
118
+ }
102
119
  `;
103
120
  log("Transformed SSR module code length: %d", transformedCode.length);
104
121
  verboseLog("Transformed SSR module code for realId=%s: %s", realId, transformedCode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {