rwsdk 0.2.0-alpha.8 → 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.
- package/dist/runtime/client/client.d.ts +10 -0
- package/dist/runtime/{client.js → client/client.js} +13 -10
- package/dist/runtime/client/navigation.d.ts +9 -0
- package/dist/runtime/client/navigation.js +88 -0
- package/dist/runtime/{clientNavigation.test.js → client/navigation.test.js} +1 -1
- package/dist/runtime/client/setWebpackRequire.d.ts +1 -0
- package/dist/runtime/client/setWebpackRequire.js +2 -0
- package/dist/runtime/{client.d.ts → client/types.d.ts} +4 -10
- package/dist/runtime/client/types.js +1 -0
- package/dist/runtime/entries/client.d.ts +2 -2
- package/dist/runtime/entries/client.js +2 -2
- package/dist/runtime/imports/client.d.ts +3 -3
- package/dist/runtime/imports/client.js +7 -6
- package/dist/runtime/imports/ssr.d.ts +3 -3
- package/dist/runtime/imports/ssr.js +3 -3
- package/dist/runtime/imports/worker.d.ts +3 -3
- package/dist/runtime/imports/worker.js +3 -3
- package/dist/runtime/lib/manifest.d.ts +11 -2
- package/dist/runtime/lib/manifest.js +1 -1
- package/dist/runtime/lib/memoizeOnId.d.ts +1 -0
- package/dist/runtime/lib/memoizeOnId.js +11 -0
- package/dist/runtime/lib/realtime/client.d.ts +1 -1
- package/dist/runtime/lib/realtime/client.js +1 -1
- package/dist/runtime/lib/router.d.ts +3 -3
- package/dist/runtime/lib/router.js +77 -33
- package/dist/runtime/register/ssr.d.ts +1 -1
- package/dist/runtime/register/ssr.js +2 -2
- package/dist/runtime/render/preloads.d.ts +6 -0
- package/dist/runtime/render/preloads.js +40 -0
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +2 -1
- package/dist/runtime/render/stylesheets.js +1 -1
- package/dist/runtime/requestInfo/types.d.ts +3 -1
- package/dist/runtime/worker.js +1 -1
- package/dist/scripts/debug-sync.mjs +159 -33
- package/dist/scripts/worker-run.mjs +8 -3
- package/dist/vite/hasOwnReactVitePlugin.d.mts +3 -0
- package/dist/vite/hasOwnReactVitePlugin.mjs +14 -0
- package/dist/vite/miniflareHMRPlugin.mjs +17 -2
- package/dist/vite/reactConditionsResolverPlugin.d.mts +3 -4
- package/dist/vite/reactConditionsResolverPlugin.mjs +71 -48
- package/dist/vite/redwoodPlugin.d.mts +1 -0
- package/dist/vite/redwoodPlugin.mjs +9 -3
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +106 -91
- package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +341 -110
- package/package.json +22 -4
- /package/dist/runtime/{imports → client}/ClientOnly.d.ts +0 -0
- /package/dist/runtime/{imports → client}/ClientOnly.js +0 -0
- /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);
|
|
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
|
-
|
|
84
|
+
const modifications = [];
|
|
94
85
|
const needsRequestInfoImportRef = { value: false };
|
|
95
|
-
|
|
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);
|
|
145
|
+
const path = srcValue.slice(1);
|
|
170
146
|
if (manifest[path]) {
|
|
171
147
|
const transformedSrc = `/${manifest[path].file}`;
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
initializer
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
225
|
-
|
|
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);
|
|
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
|
-
|
|
226
|
+
let replacementText;
|
|
254
227
|
if (isTemplateLiteral) {
|
|
255
|
-
|
|
228
|
+
replacementText = `\`/${transformedHref}\``;
|
|
256
229
|
}
|
|
257
230
|
else if (quote === '"') {
|
|
258
|
-
|
|
231
|
+
replacementText = `"/${transformedHref}"`;
|
|
259
232
|
}
|
|
260
233
|
else {
|
|
261
|
-
|
|
234
|
+
replacementText = `'/${transformedHref}'`;
|
|
262
235
|
}
|
|
263
|
-
|
|
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
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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) {
|