rwsdk 0.2.0-alpha.9 → 0.2.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.
Files changed (48) hide show
  1. package/dist/runtime/client/client.d.ts +10 -0
  2. package/dist/runtime/{client.js → client/client.js} +13 -10
  3. package/dist/runtime/client/navigation.d.ts +9 -0
  4. package/dist/runtime/client/navigation.js +88 -0
  5. package/dist/runtime/{clientNavigation.test.js → client/navigation.test.js} +1 -1
  6. package/dist/runtime/client/setWebpackRequire.d.ts +1 -0
  7. package/dist/runtime/client/setWebpackRequire.js +2 -0
  8. package/dist/runtime/{client.d.ts → client/types.d.ts} +4 -10
  9. package/dist/runtime/client/types.js +1 -0
  10. package/dist/runtime/entries/client.d.ts +2 -2
  11. package/dist/runtime/entries/client.js +2 -2
  12. package/dist/runtime/imports/client.d.ts +3 -3
  13. package/dist/runtime/imports/client.js +7 -6
  14. package/dist/runtime/imports/ssr.d.ts +3 -3
  15. package/dist/runtime/imports/ssr.js +3 -3
  16. package/dist/runtime/imports/worker.d.ts +3 -3
  17. package/dist/runtime/imports/worker.js +3 -3
  18. package/dist/runtime/lib/manifest.d.ts +11 -2
  19. package/dist/runtime/lib/manifest.js +1 -1
  20. package/dist/runtime/lib/memoizeOnId.d.ts +1 -0
  21. package/dist/runtime/lib/memoizeOnId.js +11 -0
  22. package/dist/runtime/lib/realtime/client.d.ts +1 -1
  23. package/dist/runtime/lib/realtime/client.js +1 -1
  24. package/dist/runtime/lib/router.d.ts +3 -3
  25. package/dist/runtime/lib/router.js +77 -33
  26. package/dist/runtime/register/ssr.d.ts +1 -1
  27. package/dist/runtime/register/ssr.js +2 -2
  28. package/dist/runtime/render/preloads.d.ts +6 -0
  29. package/dist/runtime/render/preloads.js +40 -0
  30. package/dist/runtime/render/renderRscThenableToHtmlStream.js +2 -1
  31. package/dist/runtime/render/stylesheets.js +1 -1
  32. package/dist/runtime/requestInfo/types.d.ts +3 -1
  33. package/dist/runtime/worker.js +1 -1
  34. package/dist/scripts/debug-sync.mjs +159 -33
  35. package/dist/scripts/worker-run.mjs +8 -3
  36. package/dist/vite/hasOwnReactVitePlugin.d.mts +3 -0
  37. package/dist/vite/hasOwnReactVitePlugin.mjs +14 -0
  38. package/dist/vite/miniflareHMRPlugin.mjs +17 -2
  39. package/dist/vite/reactConditionsResolverPlugin.d.mts +3 -4
  40. package/dist/vite/reactConditionsResolverPlugin.mjs +71 -48
  41. package/dist/vite/redwoodPlugin.d.mts +1 -0
  42. package/dist/vite/redwoodPlugin.mjs +9 -3
  43. package/dist/vite/transformJsxScriptTagsPlugin.mjs +106 -91
  44. package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +341 -110
  45. package/package.json +22 -4
  46. /package/dist/runtime/{imports → client}/ClientOnly.d.ts +0 -0
  47. /package/dist/runtime/{imports → client}/ClientOnly.js +0 -0
  48. /package/dist/runtime/{clientNavigation.test.d.ts → client/navigation.test.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { Project, Node, SyntaxKind } from "ts-morph";
1
+ import { Project, Node, SyntaxKind, } from "ts-morph";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { pathExists } from "fs-extra";
4
4
  import debug from "debug";
@@ -28,23 +28,18 @@ function hasJsxFunctions(text) {
28
28
  text.includes('jsxDEV("link"') ||
29
29
  text.includes("jsxDEV('link'"));
30
30
  }
31
- // Transform import statements in script content using ts-morph
32
31
  function transformScriptImports(scriptContent, manifest) {
33
32
  const scriptProject = new Project({ useInMemoryFileSystem: true });
34
33
  try {
35
- // Wrap in a function to make it valid JavaScript
36
34
  const wrappedContent = `function __wrapper() {${scriptContent}}`;
37
35
  const scriptFile = scriptProject.createSourceFile("script.js", wrappedContent);
38
36
  let hasChanges = false;
39
37
  const entryPoints = [];
40
- // Find all CallExpressions that look like import("path")
41
38
  scriptFile
42
39
  .getDescendantsOfKind(SyntaxKind.CallExpression)
43
40
  .forEach((callExpr) => {
44
41
  const expr = callExpr.getExpression();
45
- // Check for both "import()" and "await import()" patterns
46
42
  const isImport = expr.getText() === "import";
47
- // Check for await import pattern
48
43
  const isAwaitImport = expr.getKind() === SyntaxKind.PropertyAccessExpression &&
49
44
  expr.getText().endsWith(".import");
50
45
  if (isImport || isAwaitImport) {
@@ -54,7 +49,7 @@ function transformScriptImports(scriptContent, manifest) {
54
49
  if (importPath.startsWith("/")) {
55
50
  log("Found dynamic import with root-relative path: %s", importPath);
56
51
  entryPoints.push(importPath);
57
- const path = importPath.slice(1); // Remove leading slash
52
+ const path = importPath.slice(1);
58
53
  if (manifest[path]) {
59
54
  const transformedSrc = `/${manifest[path].file}`;
60
55
  args[0].setLiteralValue(transformedSrc);
@@ -65,19 +60,15 @@ function transformScriptImports(scriptContent, manifest) {
65
60
  }
66
61
  });
67
62
  if (hasChanges) {
68
- // Extract the transformed content from inside the wrapper function
69
63
  const fullText = scriptFile.getFullText();
70
- // Find content between the first { and the last }
71
64
  const startPos = fullText.indexOf("{") + 1;
72
65
  const endPos = fullText.lastIndexOf("}");
73
66
  const transformedContent = fullText.substring(startPos, endPos);
74
67
  return { content: transformedContent, hasChanges: true, entryPoints };
75
68
  }
76
- // Return the original content when no changes are made
77
69
  return { content: scriptContent, hasChanges: false, entryPoints };
78
70
  }
79
71
  catch (error) {
80
- // If parsing fails, fall back to the original content
81
72
  console.warn("Failed to parse inline script content:", error);
82
73
  return { content: undefined, hasChanges: false, entryPoints: [] };
83
74
  }
@@ -90,17 +81,15 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
90
81
  }
91
82
  const project = new Project({ useInMemoryFileSystem: true });
92
83
  const sourceFile = project.createSourceFile("temp.tsx", code);
93
- let hasModifications = false;
84
+ const modifications = [];
94
85
  const needsRequestInfoImportRef = { value: false };
95
- // Check for existing imports up front
86
+ const entryPointsPerCallExpr = new Map();
96
87
  let hasRequestInfoImport = false;
97
88
  let sdkWorkerImportDecl;
98
- // Scan for imports only once
99
89
  sourceFile.getImportDeclarations().forEach((importDecl) => {
100
90
  const moduleSpecifier = importDecl.getModuleSpecifierValue();
101
91
  if (moduleSpecifier === "rwsdk/worker") {
102
92
  sdkWorkerImportDecl = importDecl;
103
- // Check if requestInfo is already imported
104
93
  if (importDecl
105
94
  .getNamedImports()
106
95
  .some((namedImport) => namedImport.getName() === "requestInfo")) {
@@ -108,97 +97,83 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
108
97
  }
109
98
  }
110
99
  });
111
- // Look for jsx function calls (jsx, jsxs, jsxDEV)
112
100
  sourceFile
113
101
  .getDescendantsOfKind(SyntaxKind.CallExpression)
114
102
  .forEach((callExpr) => {
115
103
  const expression = callExpr.getExpression();
116
104
  const expressionText = expression.getText();
117
- // Only process jsx/jsxs/jsxDEV calls
118
105
  if (expressionText !== "jsx" &&
119
106
  expressionText !== "jsxs" &&
120
107
  expressionText !== "jsxDEV") {
121
108
  return;
122
109
  }
123
- // Get arguments of the jsx call
124
110
  const args = callExpr.getArguments();
125
111
  if (args.length < 2)
126
112
  return;
127
- // First argument should be the element type
128
113
  const elementType = args[0];
129
114
  if (!Node.isStringLiteral(elementType))
130
115
  return;
131
116
  const tagName = elementType.getLiteralValue();
132
117
  const entryPoints = [];
133
- // Process script and link tags
134
118
  if (tagName === "script" || tagName === "link") {
135
- // Second argument should be the props object
136
119
  const propsArg = args[1];
137
- // Handle object literals with properties
138
120
  if (Node.isObjectLiteralExpression(propsArg)) {
139
121
  const properties = propsArg.getProperties();
140
- // Variables to track script attributes
141
122
  let hasDangerouslySetInnerHTML = false;
142
123
  let hasNonce = false;
143
124
  let hasStringLiteralChildren = false;
144
125
  let hasSrc = false;
145
- // Variables to track link attributes
146
126
  let isPreload = false;
147
127
  let hrefValue = null;
148
128
  for (const prop of properties) {
149
129
  if (Node.isPropertyAssignment(prop)) {
150
130
  const propName = prop.getName();
151
131
  const initializer = prop.getInitializer();
152
- // Check for existing nonce
153
132
  if (propName === "nonce") {
154
133
  hasNonce = true;
155
134
  }
156
- // Check for dangerouslySetInnerHTML
157
135
  if (propName === "dangerouslySetInnerHTML") {
158
136
  hasDangerouslySetInnerHTML = true;
159
137
  }
160
- // Check for src attribute
161
138
  if (tagName === "script" && propName === "src") {
162
139
  hasSrc = true;
163
- // Also process src for manifest transformation if needed
164
140
  if (Node.isStringLiteral(initializer) ||
165
141
  Node.isNoSubstitutionTemplateLiteral(initializer)) {
166
142
  const srcValue = initializer.getLiteralValue();
167
143
  if (srcValue.startsWith("/")) {
168
144
  entryPoints.push(srcValue);
169
- const path = srcValue.slice(1); // Remove leading slash
145
+ const path = srcValue.slice(1);
170
146
  if (manifest[path]) {
171
147
  const transformedSrc = `/${manifest[path].file}`;
172
- initializer.setLiteralValue(transformedSrc);
173
- hasModifications = true;
148
+ modifications.push({
149
+ type: "literalValue",
150
+ node: initializer,
151
+ value: transformedSrc,
152
+ });
174
153
  }
175
154
  }
176
155
  }
177
156
  }
178
- // Check for string literal children
179
157
  if (tagName === "script" &&
180
158
  propName === "children" &&
181
159
  (Node.isStringLiteral(initializer) ||
182
160
  Node.isNoSubstitutionTemplateLiteral(initializer))) {
183
161
  hasStringLiteralChildren = true;
184
162
  const scriptContent = initializer.getLiteralValue();
185
- // Transform import statements in script content using ts-morph
186
163
  const { content: transformedContent, hasChanges: contentHasChanges, entryPoints: dynamicEntryPoints, } = transformScriptImports(scriptContent, manifest);
187
164
  entryPoints.push(...dynamicEntryPoints);
188
165
  if (contentHasChanges && transformedContent) {
189
- // Get the raw text with quotes to determine the exact format
190
166
  const isTemplateLiteral = Node.isNoSubstitutionTemplateLiteral(initializer);
191
- if (isTemplateLiteral) {
192
- // Simply wrap the transformed content in backticks
193
- initializer.replaceWithText("`" + transformedContent + "`");
194
- }
195
- else {
196
- initializer.replaceWithText(JSON.stringify(transformedContent));
197
- }
198
- hasModifications = true;
167
+ const replacementText = isTemplateLiteral
168
+ ? "`" + transformedContent + "`"
169
+ : JSON.stringify(transformedContent);
170
+ modifications.push({
171
+ type: "replaceText",
172
+ node: initializer,
173
+ text: replacementText,
174
+ });
199
175
  }
200
176
  }
201
- // For link tags, first check if it's a preload/modulepreload
202
177
  if (tagName === "link") {
203
178
  if (propName === "rel" &&
204
179
  (Node.isStringLiteral(initializer) ||
@@ -216,28 +191,26 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
216
191
  }
217
192
  }
218
193
  }
219
- // Add nonce to script tags if needed
220
194
  if (tagName === "script" &&
221
195
  !hasNonce &&
222
196
  !hasDangerouslySetInnerHTML &&
223
197
  (hasStringLiteralChildren || hasSrc)) {
224
- // Add nonce property to the props object
225
- propsArg.addPropertyAssignment({
198
+ modifications.push({
199
+ type: "addProperty",
200
+ node: propsArg,
226
201
  name: "nonce",
227
202
  initializer: "requestInfo.rw.nonce",
228
203
  });
229
204
  if (!hasRequestInfoImport) {
230
205
  needsRequestInfoImportRef.value = true;
231
206
  }
232
- hasModifications = true;
233
207
  }
234
- // Transform href if this is a preload link
235
208
  if (tagName === "link" &&
236
209
  isPreload &&
237
210
  hrefValue &&
238
211
  hrefValue.startsWith("/") &&
239
212
  manifest[hrefValue.slice(1)]) {
240
- const path = hrefValue.slice(1); // Remove leading slash
213
+ const path = hrefValue.slice(1);
241
214
  for (const prop of properties) {
242
215
  if (Node.isPropertyAssignment(prop) &&
243
216
  prop.getName() === "href") {
@@ -250,17 +223,21 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
250
223
  const quote = isTemplateLiteral
251
224
  ? "`"
252
225
  : originalText.charAt(0);
253
- // Preserve the original quote style
226
+ let replacementText;
254
227
  if (isTemplateLiteral) {
255
- initializer.replaceWithText(`\`/${transformedHref}\``);
228
+ replacementText = `\`/${transformedHref}\``;
256
229
  }
257
230
  else if (quote === '"') {
258
- initializer.replaceWithText(`"/${transformedHref}"`);
231
+ replacementText = `"/${transformedHref}"`;
259
232
  }
260
233
  else {
261
- initializer.replaceWithText(`'/${transformedHref}'`);
234
+ replacementText = `'/${transformedHref}'`;
262
235
  }
263
- hasModifications = true;
236
+ modifications.push({
237
+ type: "replaceText",
238
+ node: initializer,
239
+ text: replacementText,
240
+ });
264
241
  }
265
242
  }
266
243
  }
@@ -274,51 +251,87 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
274
251
  .join(",\n");
275
252
  const leadingCommentRanges = callExpr.getLeadingCommentRanges();
276
253
  const pureComment = leadingCommentRanges.find((r) => r.getText().includes("@__PURE__"));
277
- const callExprText = callExpr.getText();
278
- if (pureComment) {
279
- const pureCommentText = pureComment.getText();
280
- const newText = `(
281
- ${sideEffects},
282
- ${pureCommentText} ${callExprText}
283
- )`;
284
- const fullText = callExpr.getFullText();
285
- const leadingTriviaText = fullText.substring(0, fullText.length - callExprText.length);
286
- const newLeadingTriviaText = leadingTriviaText.replace(pureCommentText, "");
287
- // By replacing from `getFullStart`, we remove the original node and all its leading trivia
288
- // and replace it with our manually reconstructed string.
289
- // This should correctly move the pure comment and preserve other comments and whitespace.
290
- callExpr
291
- .getSourceFile()
292
- .replaceText([callExpr.getFullStart(), callExpr.getEnd()], newLeadingTriviaText + newText);
293
- }
294
- else {
295
- callExpr.replaceWithText(`(
296
- ${sideEffects},
297
- ${callExprText}
298
- )`);
254
+ const wrapInfo = {
255
+ callExpr: callExpr,
256
+ sideEffects: sideEffects,
257
+ pureCommentText: pureComment?.getText(),
258
+ };
259
+ if (!entryPointsPerCallExpr.has(callExpr)) {
260
+ entryPointsPerCallExpr.set(callExpr, wrapInfo);
299
261
  }
300
262
  needsRequestInfoImportRef.value = true;
301
- hasModifications = true;
302
263
  }
303
264
  });
304
- // Add requestInfo import if needed and not already imported
305
- if (needsRequestInfoImportRef.value && hasModifications) {
306
- if (sdkWorkerImportDecl) {
307
- // Module is imported but need to add requestInfo
308
- if (!hasRequestInfoImport) {
309
- sdkWorkerImportDecl.addNamedImport("requestInfo");
265
+ if (modifications.length > 0 || entryPointsPerCallExpr.size > 0) {
266
+ for (const mod of modifications) {
267
+ if (mod.type === "literalValue") {
268
+ mod.node.setLiteralValue(mod.value);
269
+ }
270
+ else if (mod.type === "replaceText") {
271
+ mod.node.replaceWithText(mod.text);
272
+ }
273
+ else if (mod.type === "addProperty") {
274
+ mod.node.addPropertyAssignment({
275
+ name: mod.name,
276
+ initializer: mod.initializer,
277
+ });
310
278
  }
311
279
  }
312
- else {
313
- // Add new import declaration
314
- sourceFile.addImportDeclaration({
315
- moduleSpecifier: "rwsdk/worker",
316
- namedImports: ["requestInfo"],
280
+ const wrapModifications = [];
281
+ for (const [callExpr, wrapInfo] of entryPointsPerCallExpr) {
282
+ const fullStart = callExpr.getFullStart();
283
+ const end = callExpr.getEnd();
284
+ const callExprText = callExpr.getText();
285
+ const fullText = callExpr.getFullText();
286
+ const leadingWhitespace = fullText.substring(0, fullText.length - callExprText.length);
287
+ let pureCommentText;
288
+ let leadingTriviaText;
289
+ if (wrapInfo.pureCommentText) {
290
+ pureCommentText = wrapInfo.pureCommentText;
291
+ leadingTriviaText = leadingWhitespace;
292
+ }
293
+ wrapModifications.push({
294
+ type: "wrapCallExpr",
295
+ sideEffects: wrapInfo.sideEffects,
296
+ pureCommentText: pureCommentText,
297
+ leadingTriviaText: leadingTriviaText,
298
+ fullStart: fullStart,
299
+ end: end,
300
+ callExprText: callExprText,
301
+ leadingWhitespace: leadingWhitespace,
317
302
  });
318
303
  }
319
- }
320
- // Return the transformed code only if modifications were made
321
- if (hasModifications) {
304
+ wrapModifications.sort((a, b) => b.fullStart - a.fullStart);
305
+ for (const mod of wrapModifications) {
306
+ if (mod.pureCommentText && mod.leadingTriviaText) {
307
+ const newText = `(
308
+ ${mod.sideEffects},
309
+ ${mod.pureCommentText} ${mod.callExprText}
310
+ )`;
311
+ const newLeadingTriviaText = mod.leadingTriviaText.replace(mod.pureCommentText, "");
312
+ sourceFile.replaceText([mod.fullStart, mod.end], newLeadingTriviaText + newText);
313
+ }
314
+ else {
315
+ const leadingNewlines = mod.leadingWhitespace.match(/\n\s*/)?.[0] || "";
316
+ sourceFile.replaceText([mod.fullStart, mod.end], `${leadingNewlines}(
317
+ ${mod.sideEffects},
318
+ ${mod.callExprText}
319
+ )`);
320
+ }
321
+ }
322
+ if (needsRequestInfoImportRef.value) {
323
+ if (sdkWorkerImportDecl) {
324
+ if (!hasRequestInfoImport) {
325
+ sdkWorkerImportDecl.addNamedImport("requestInfo");
326
+ }
327
+ }
328
+ else {
329
+ sourceFile.addImportDeclaration({
330
+ moduleSpecifier: "rwsdk/worker",
331
+ namedImports: ["requestInfo"],
332
+ });
333
+ }
334
+ }
322
335
  return {
323
336
  code: sourceFile.getFullText(),
324
337
  map: null,
@@ -338,6 +351,8 @@ export const transformJsxScriptTagsPlugin = ({ manifestPath, }) => {
338
351
  id.endsWith(".tsx") &&
339
352
  !id.includes("node_modules") &&
340
353
  hasJsxFunctions(code)) {
354
+ log("Transforming JSX script tags in %s", id);
355
+ process.env.VERBOSE && log("Code:\n%s", code);
341
356
  const manifest = isBuild ? await readManifest(manifestPath) : {};
342
357
  const result = await transformJsxScriptTagsCode(code, manifest);
343
358
  if (result) {