rwsdk 0.1.26 → 0.2.0-alpha.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/lib/smokeTests/browser.d.mts +4 -2
- package/dist/lib/smokeTests/browser.mjs +159 -7
- package/dist/lib/smokeTests/codeUpdates.d.mts +1 -0
- package/dist/lib/smokeTests/codeUpdates.mjs +47 -0
- package/dist/lib/smokeTests/development.d.mts +1 -1
- package/dist/lib/smokeTests/development.mjs +10 -3
- package/dist/lib/smokeTests/environment.mjs +1 -14
- package/dist/lib/smokeTests/release.d.mts +1 -1
- package/dist/lib/smokeTests/release.mjs +3 -2
- package/dist/lib/smokeTests/reporting.mjs +30 -2
- package/dist/lib/smokeTests/runSmokeTests.mjs +2 -2
- package/dist/lib/smokeTests/state.d.mts +8 -0
- package/dist/lib/smokeTests/state.mjs +10 -0
- package/dist/lib/smokeTests/templates/SmokeTestClient.template.js +17 -2
- package/dist/lib/smokeTests/templates/smokeTestClientStyles.module.css.template.d.ts +1 -0
- package/dist/lib/smokeTests/templates/smokeTestClientStyles.module.css.template.js +9 -0
- package/dist/lib/smokeTests/templates/smokeTestUrlStyles.css.template.d.ts +1 -0
- package/dist/lib/smokeTests/templates/smokeTestUrlStyles.css.template.js +9 -0
- package/dist/lib/smokeTests/types.d.mts +1 -0
- package/dist/runtime/clientNavigation.d.ts +6 -3
- package/dist/runtime/clientNavigation.js +72 -8
- package/dist/runtime/entries/types/client.d.ts +5 -0
- package/dist/runtime/lib/manifest.d.ts +2 -0
- package/dist/runtime/lib/manifest.js +17 -0
- package/dist/runtime/lib/router.d.ts +1 -0
- package/dist/runtime/register/worker.js +17 -5
- package/dist/runtime/render/renderRscThenableToHtmlStream.d.ts +3 -3
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +7 -3
- package/dist/runtime/render/stylesheets.d.ts +9 -0
- package/dist/runtime/render/stylesheets.js +45 -0
- package/dist/runtime/worker.js +1 -0
- package/dist/scripts/debug-sync.mjs +125 -13
- package/dist/scripts/ensure-deploy-env.mjs +2 -2
- package/dist/scripts/smoke-test.mjs +6 -0
- package/dist/vite/manifestPlugin.d.mts +4 -0
- package/dist/vite/manifestPlugin.mjs +151 -0
- package/dist/vite/redwoodPlugin.mjs +4 -0
- package/dist/vite/ssrBridgePlugin.mjs +17 -8
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +74 -33
- package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +43 -15
- package/package.json +1 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
|
|
4
|
+
const log = debug("rwsdk:vite:manifest-plugin");
|
|
5
|
+
const virtualModuleId = "virtual:rwsdk:manifest.js";
|
|
6
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
7
|
+
const getCssForModule = (server, moduleId, css) => {
|
|
8
|
+
const stack = [moduleId];
|
|
9
|
+
const visited = new Set();
|
|
10
|
+
while (stack.length > 0) {
|
|
11
|
+
const currentModuleId = stack.pop();
|
|
12
|
+
if (visited.has(currentModuleId)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
visited.add(currentModuleId);
|
|
16
|
+
const moduleNode = server.environments.client.moduleGraph.getModuleById(currentModuleId);
|
|
17
|
+
if (!moduleNode) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
for (const importedModule of moduleNode.importedModules) {
|
|
21
|
+
if (importedModule.url.endsWith(".css")) {
|
|
22
|
+
const absolutePath = importedModule.file;
|
|
23
|
+
css.add({
|
|
24
|
+
url: importedModule.url,
|
|
25
|
+
// The `ssrTransformResult` has the CSS content, because the default
|
|
26
|
+
// transform for CSS is to a string of the CSS content.
|
|
27
|
+
content: importedModule.ssrTransformResult?.code ?? "",
|
|
28
|
+
absolutePath,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (importedModule.id) {
|
|
32
|
+
stack.push(importedModule.id);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export const manifestPlugin = ({ manifestPath, }) => {
|
|
38
|
+
let isBuild = false;
|
|
39
|
+
let root;
|
|
40
|
+
return {
|
|
41
|
+
name: "rwsdk:manifest",
|
|
42
|
+
configResolved(config) {
|
|
43
|
+
log("Config resolved, command=%s", config.command);
|
|
44
|
+
isBuild = config.command === "build";
|
|
45
|
+
root = config.root;
|
|
46
|
+
},
|
|
47
|
+
resolveId(id) {
|
|
48
|
+
if (id === virtualModuleId) {
|
|
49
|
+
process.env.VERBOSE && log("Resolving virtual module id=%s", id);
|
|
50
|
+
return resolvedVirtualModuleId;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
async load(id) {
|
|
54
|
+
if (id === resolvedVirtualModuleId) {
|
|
55
|
+
process.env.VERBOSE && log("Loading virtual module id=%s", id);
|
|
56
|
+
if (!isBuild) {
|
|
57
|
+
process.env.VERBOSE && log("Not a build, returning empty manifest");
|
|
58
|
+
return `export default {}`;
|
|
59
|
+
}
|
|
60
|
+
log("Reading manifest from %s", manifestPath);
|
|
61
|
+
const manifestContent = await readFile(manifestPath, "utf-8");
|
|
62
|
+
const manifest = JSON.parse(manifestContent);
|
|
63
|
+
const normalizedManifest = {};
|
|
64
|
+
for (const key in manifest) {
|
|
65
|
+
const normalizedKey = normalizeModulePath(key, root, {
|
|
66
|
+
isViteStyle: false,
|
|
67
|
+
});
|
|
68
|
+
const entry = manifest[key];
|
|
69
|
+
delete manifest[key];
|
|
70
|
+
normalizedManifest[normalizedKey] = entry;
|
|
71
|
+
entry.file = normalizeModulePath(entry.file, root, {
|
|
72
|
+
isViteStyle: false,
|
|
73
|
+
});
|
|
74
|
+
const normalizedCss = [];
|
|
75
|
+
if (entry.css) {
|
|
76
|
+
for (const css of entry.css) {
|
|
77
|
+
normalizedCss.push(normalizeModulePath(css, root, {
|
|
78
|
+
isViteStyle: false,
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
entry.css = normalizedCss;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return `export default ${JSON.stringify(normalizedManifest)}`;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
configEnvironment(name, config) {
|
|
88
|
+
if (name !== "worker" && name !== "ssr") {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
log("Configuring environment: name=%s", name);
|
|
92
|
+
config.optimizeDeps ??= {};
|
|
93
|
+
config.optimizeDeps.esbuildOptions ??= {};
|
|
94
|
+
config.optimizeDeps.esbuildOptions.plugins ??= [];
|
|
95
|
+
config.optimizeDeps.esbuildOptions.plugins.push({
|
|
96
|
+
name: "rwsdk:manifest:esbuild",
|
|
97
|
+
setup(build) {
|
|
98
|
+
log("Setting up esbuild plugin for environment: %s", name);
|
|
99
|
+
build.onResolve({ filter: /^virtual:rwsdk:manifest\.js$/ }, () => {
|
|
100
|
+
log("Resolving virtual manifest module in esbuild");
|
|
101
|
+
return {
|
|
102
|
+
path: "virtual:rwsdk:manifest.js",
|
|
103
|
+
external: true,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
configureServer(server) {
|
|
110
|
+
log("Configuring server middleware for manifest");
|
|
111
|
+
server.middlewares.use("/__rwsdk_manifest", async (req, res, next) => {
|
|
112
|
+
log("Manifest request received: %s", req.url);
|
|
113
|
+
try {
|
|
114
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
115
|
+
const scripts = JSON.parse(url.searchParams.get("scripts") || "[]");
|
|
116
|
+
process.env.VERBOSE && log("Transforming scripts: %o", scripts);
|
|
117
|
+
for (const script of scripts) {
|
|
118
|
+
await server.environments.client.transformRequest(script);
|
|
119
|
+
}
|
|
120
|
+
const manifest = {};
|
|
121
|
+
log("Building manifest from module graph");
|
|
122
|
+
for (const file of server.environments.client.moduleGraph.fileToModulesMap.keys()) {
|
|
123
|
+
const modules = server.environments.client.moduleGraph.getModulesByFile(file);
|
|
124
|
+
if (!modules) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
for (const module of modules) {
|
|
128
|
+
if (module.file) {
|
|
129
|
+
const css = new Set();
|
|
130
|
+
getCssForModule(server, module.id, css);
|
|
131
|
+
manifest[normalizeModulePath(module.file, server.config.root)] =
|
|
132
|
+
{
|
|
133
|
+
file: module.url,
|
|
134
|
+
css: Array.from(css),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
log("Manifest built successfully");
|
|
140
|
+
process.env.VERBOSE && log("Manifest: %o", manifest);
|
|
141
|
+
res.setHeader("Content-Type", "application/json");
|
|
142
|
+
res.end(JSON.stringify(manifest));
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
log("Error building manifest: %o", e);
|
|
146
|
+
next(e);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
};
|
|
@@ -22,6 +22,7 @@ import { prismaPlugin } from "./prismaPlugin.mjs";
|
|
|
22
22
|
import { ssrBridgePlugin } from "./ssrBridgePlugin.mjs";
|
|
23
23
|
import { hasPkgScript } from "../lib/hasPkgScript.mjs";
|
|
24
24
|
import { devServerTimingPlugin } from "./devServerTimingPlugin.mjs";
|
|
25
|
+
import { manifestPlugin } from "./manifestPlugin.mjs";
|
|
25
26
|
const determineWorkerEntryPathname = async (projectRootDir, workerConfigPath, options) => {
|
|
26
27
|
if (options.entry?.worker) {
|
|
27
28
|
return resolve(projectRootDir, options.entry.worker);
|
|
@@ -101,6 +102,9 @@ export const redwoodPlugin = async (options = {}) => {
|
|
|
101
102
|
transformJsxScriptTagsPlugin({
|
|
102
103
|
manifestPath: resolve(projectRootDir, "dist", "client", ".vite", "manifest.json"),
|
|
103
104
|
}),
|
|
105
|
+
manifestPlugin({
|
|
106
|
+
manifestPath: resolve(projectRootDir, "dist", "client", ".vite", "manifest.json"),
|
|
107
|
+
}),
|
|
104
108
|
moveStaticAssetsPlugin({ rootDir: projectRootDir }),
|
|
105
109
|
prismaPlugin({ projectRootDir }),
|
|
106
110
|
];
|
|
@@ -55,6 +55,11 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
55
55
|
// SSR modules, so we return the virtual id so that the dynamic loading
|
|
56
56
|
// can happen in load()
|
|
57
57
|
if (id.startsWith(VIRTUAL_SSR_PREFIX)) {
|
|
58
|
+
if (id.endsWith(".css")) {
|
|
59
|
+
const newId = id + ".js";
|
|
60
|
+
log("Virtual CSS module, adding .js suffix. old: %s, new: %s", id, newId);
|
|
61
|
+
return newId;
|
|
62
|
+
}
|
|
58
63
|
log("Returning virtual SSR id for dev: %s", id);
|
|
59
64
|
return id;
|
|
60
65
|
}
|
|
@@ -86,20 +91,24 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
86
91
|
if (id.startsWith(VIRTUAL_SSR_PREFIX) &&
|
|
87
92
|
this.environment.name === "worker") {
|
|
88
93
|
const realId = id.slice(VIRTUAL_SSR_PREFIX.length);
|
|
89
|
-
|
|
94
|
+
const idForFetch = realId.endsWith(".css.js")
|
|
95
|
+
? realId.slice(0, -3)
|
|
96
|
+
: realId;
|
|
97
|
+
log("Virtual SSR module load: id=%s, realId=%s, idForFetch=%s", id, realId, idForFetch);
|
|
90
98
|
if (isDev) {
|
|
91
|
-
log("Dev mode: fetching SSR module for realPath=%s",
|
|
92
|
-
const result = await devServer?.environments.ssr.fetchModule(
|
|
99
|
+
log("Dev mode: fetching SSR module for realPath=%s", idForFetch);
|
|
100
|
+
const result = await devServer?.environments.ssr.fetchModule(idForFetch);
|
|
93
101
|
process.env.VERBOSE &&
|
|
94
|
-
log("Fetch module result: id=%s, result=%O",
|
|
102
|
+
log("Fetch module result: id=%s, result=%O", idForFetch, result);
|
|
95
103
|
const code = "code" in result ? result.code : undefined;
|
|
96
|
-
if (
|
|
104
|
+
if (idForFetch.endsWith(".css") &&
|
|
105
|
+
!idForFetch.endsWith(".module.css")) {
|
|
97
106
|
process.env.VERBOSE &&
|
|
98
|
-
log("
|
|
99
|
-
return
|
|
107
|
+
log("Plain CSS file, returning empty module for %s", idForFetch);
|
|
108
|
+
return "export default {};";
|
|
100
109
|
}
|
|
101
110
|
log("Fetched SSR module code length: %d", code?.length || 0);
|
|
102
|
-
const { imports, dynamicImports } = findSsrImportSpecifiers(
|
|
111
|
+
const { imports, dynamicImports } = findSsrImportSpecifiers(idForFetch, code || "", log);
|
|
103
112
|
const allSpecifiers = [
|
|
104
113
|
...new Set([...imports, ...dynamicImports]),
|
|
105
114
|
].map((id) => id.startsWith("/@id/") ? id.slice("/@id/".length) : id);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Project, Node, SyntaxKind } from "ts-morph";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { pathExists } from "fs-extra";
|
|
4
|
+
import debug from "debug";
|
|
5
|
+
const log = debug("rwsdk:vite:transform-jsx-script-tags");
|
|
4
6
|
let manifestCache;
|
|
5
7
|
const readManifest = async (manifestPath) => {
|
|
6
8
|
if (manifestCache === undefined) {
|
|
@@ -34,6 +36,7 @@ function transformScriptImports(scriptContent, manifest) {
|
|
|
34
36
|
const wrappedContent = `function __wrapper() {${scriptContent}}`;
|
|
35
37
|
const scriptFile = scriptProject.createSourceFile("script.js", wrappedContent);
|
|
36
38
|
let hasChanges = false;
|
|
39
|
+
const entryPoints = [];
|
|
37
40
|
// Find all CallExpressions that look like import("path")
|
|
38
41
|
scriptFile
|
|
39
42
|
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
@@ -49,10 +52,12 @@ function transformScriptImports(scriptContent, manifest) {
|
|
|
49
52
|
if (args.length > 0 && Node.isStringLiteral(args[0])) {
|
|
50
53
|
const importPath = args[0].getLiteralValue();
|
|
51
54
|
if (importPath.startsWith("/")) {
|
|
55
|
+
log("Found dynamic import with root-relative path: %s", importPath);
|
|
56
|
+
entryPoints.push(importPath);
|
|
52
57
|
const path = importPath.slice(1); // Remove leading slash
|
|
53
58
|
if (manifest[path]) {
|
|
54
|
-
const
|
|
55
|
-
args[0].
|
|
59
|
+
const transformedSrc = `/${manifest[path].file}`;
|
|
60
|
+
args[0].setLiteralValue(transformedSrc);
|
|
56
61
|
hasChanges = true;
|
|
57
62
|
}
|
|
58
63
|
}
|
|
@@ -66,15 +71,15 @@ function transformScriptImports(scriptContent, manifest) {
|
|
|
66
71
|
const startPos = fullText.indexOf("{") + 1;
|
|
67
72
|
const endPos = fullText.lastIndexOf("}");
|
|
68
73
|
const transformedContent = fullText.substring(startPos, endPos);
|
|
69
|
-
return { content: transformedContent, hasChanges: true };
|
|
74
|
+
return { content: transformedContent, hasChanges: true, entryPoints };
|
|
70
75
|
}
|
|
71
76
|
// Return the original content when no changes are made
|
|
72
|
-
return { content: scriptContent, hasChanges: false };
|
|
77
|
+
return { content: scriptContent, hasChanges: false, entryPoints };
|
|
73
78
|
}
|
|
74
79
|
catch (error) {
|
|
75
80
|
// If parsing fails, fall back to the original content
|
|
76
81
|
console.warn("Failed to parse inline script content:", error);
|
|
77
|
-
return { content: undefined, hasChanges: false };
|
|
82
|
+
return { content: undefined, hasChanges: false, entryPoints: [] };
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
@@ -86,7 +91,7 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
|
86
91
|
const project = new Project({ useInMemoryFileSystem: true });
|
|
87
92
|
const sourceFile = project.createSourceFile("temp.tsx", code);
|
|
88
93
|
let hasModifications = false;
|
|
89
|
-
|
|
94
|
+
const needsRequestInfoImportRef = { value: false };
|
|
90
95
|
// Check for existing imports up front
|
|
91
96
|
let hasRequestInfoImport = false;
|
|
92
97
|
let sdkWorkerImportDecl;
|
|
@@ -124,6 +129,7 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
|
124
129
|
if (!Node.isStringLiteral(elementType))
|
|
125
130
|
return;
|
|
126
131
|
const tagName = elementType.getLiteralValue();
|
|
132
|
+
const entryPoints = [];
|
|
127
133
|
// Process script and link tags
|
|
128
134
|
if (tagName === "script" || tagName === "link") {
|
|
129
135
|
// Second argument should be the props object
|
|
@@ -158,25 +164,14 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
|
158
164
|
if (Node.isStringLiteral(initializer) ||
|
|
159
165
|
Node.isNoSubstitutionTemplateLiteral(initializer)) {
|
|
160
166
|
const srcValue = initializer.getLiteralValue();
|
|
161
|
-
if (srcValue.startsWith("/")
|
|
167
|
+
if (srcValue.startsWith("/")) {
|
|
168
|
+
entryPoints.push(srcValue);
|
|
162
169
|
const path = srcValue.slice(1); // Remove leading slash
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
? "`"
|
|
168
|
-
: originalText.charAt(0);
|
|
169
|
-
// Preserve the original quote style
|
|
170
|
-
if (isTemplateLiteral) {
|
|
171
|
-
initializer.replaceWithText(`\`/${transformedSrc}\``);
|
|
170
|
+
if (manifest[path]) {
|
|
171
|
+
const transformedSrc = `/${manifest[path].file}`;
|
|
172
|
+
initializer.setLiteralValue(transformedSrc);
|
|
173
|
+
hasModifications = true;
|
|
172
174
|
}
|
|
173
|
-
else if (quote === '"') {
|
|
174
|
-
initializer.replaceWithText(`"/${transformedSrc}"`);
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
initializer.replaceWithText(`'/${transformedSrc}'`);
|
|
178
|
-
}
|
|
179
|
-
hasModifications = true;
|
|
180
175
|
}
|
|
181
176
|
}
|
|
182
177
|
}
|
|
@@ -188,8 +183,9 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
|
188
183
|
hasStringLiteralChildren = true;
|
|
189
184
|
const scriptContent = initializer.getLiteralValue();
|
|
190
185
|
// Transform import statements in script content using ts-morph
|
|
191
|
-
const { content: transformedContent, hasChanges } = transformScriptImports(scriptContent, manifest);
|
|
192
|
-
|
|
186
|
+
const { content: transformedContent, hasChanges: contentHasChanges, entryPoints: dynamicEntryPoints, } = transformScriptImports(scriptContent, manifest);
|
|
187
|
+
entryPoints.push(...dynamicEntryPoints);
|
|
188
|
+
if (contentHasChanges && transformedContent) {
|
|
193
189
|
// Get the raw text with quotes to determine the exact format
|
|
194
190
|
const isTemplateLiteral = Node.isNoSubstitutionTemplateLiteral(initializer);
|
|
195
191
|
if (isTemplateLiteral) {
|
|
@@ -231,7 +227,7 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
|
231
227
|
initializer: "requestInfo.rw.nonce",
|
|
232
228
|
});
|
|
233
229
|
if (!hasRequestInfoImport) {
|
|
234
|
-
|
|
230
|
+
needsRequestInfoImportRef.value = true;
|
|
235
231
|
}
|
|
236
232
|
hasModifications = true;
|
|
237
233
|
}
|
|
@@ -271,9 +267,42 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
|
271
267
|
}
|
|
272
268
|
}
|
|
273
269
|
}
|
|
270
|
+
if (entryPoints.length > 0) {
|
|
271
|
+
log("Found %d script entry points, adding to scripts to be loaded: %o", entryPoints.length, entryPoints);
|
|
272
|
+
const sideEffects = entryPoints
|
|
273
|
+
.map((p) => `(requestInfo.rw.scriptsToBeLoaded.add("${p}"))`)
|
|
274
|
+
.join(",\n");
|
|
275
|
+
const leadingCommentRanges = callExpr.getLeadingCommentRanges();
|
|
276
|
+
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
|
+
)`);
|
|
299
|
+
}
|
|
300
|
+
needsRequestInfoImportRef.value = true;
|
|
301
|
+
hasModifications = true;
|
|
302
|
+
}
|
|
274
303
|
});
|
|
275
304
|
// Add requestInfo import if needed and not already imported
|
|
276
|
-
if (
|
|
305
|
+
if (needsRequestInfoImportRef.value && hasModifications) {
|
|
277
306
|
if (sdkWorkerImportDecl) {
|
|
278
307
|
// Module is imported but need to add requestInfo
|
|
279
308
|
if (!hasRequestInfoImport) {
|
|
@@ -300,16 +329,28 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
|
300
329
|
export const transformJsxScriptTagsPlugin = ({ manifestPath, }) => {
|
|
301
330
|
let isBuild = false;
|
|
302
331
|
return {
|
|
303
|
-
name: "rwsdk:transform-jsx-script-tags",
|
|
332
|
+
name: "rwsdk:vite:transform-jsx-script-tags",
|
|
304
333
|
configResolved(config) {
|
|
305
334
|
isBuild = config.command === "build";
|
|
306
335
|
},
|
|
307
|
-
async transform(code) {
|
|
308
|
-
if (this.environment
|
|
309
|
-
|
|
336
|
+
async transform(code, id) {
|
|
337
|
+
if (this.environment?.name === "worker" &&
|
|
338
|
+
id.endsWith(".tsx") &&
|
|
339
|
+
!id.includes("node_modules") &&
|
|
340
|
+
hasJsxFunctions(code)) {
|
|
341
|
+
const manifest = isBuild ? await readManifest(manifestPath) : {};
|
|
342
|
+
const result = await transformJsxScriptTagsCode(code, manifest);
|
|
343
|
+
if (result) {
|
|
344
|
+
log("Transformed JSX script tags in %s", id);
|
|
345
|
+
process.env.VERBOSE &&
|
|
346
|
+
log("New Document code for %s:\n%s", id, result.code);
|
|
347
|
+
return {
|
|
348
|
+
code: result.code,
|
|
349
|
+
map: null,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
310
352
|
}
|
|
311
|
-
|
|
312
|
-
return transformJsxScriptTagsCode(code, manifest);
|
|
353
|
+
return null;
|
|
313
354
|
},
|
|
314
355
|
};
|
|
315
356
|
};
|
|
@@ -16,11 +16,14 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
16
16
|
const result = await transformJsxScriptTagsCode(code, mockManifest);
|
|
17
17
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
(
|
|
20
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
|
|
21
|
+
jsx("script", {
|
|
20
22
|
src: "/assets/client-a1b2c3d4.js",
|
|
21
23
|
type: "module",
|
|
22
24
|
nonce: requestInfo.rw.nonce
|
|
23
25
|
})
|
|
26
|
+
)
|
|
24
27
|
`);
|
|
25
28
|
});
|
|
26
29
|
it("transforms inline scripts with dynamic imports", async () => {
|
|
@@ -33,11 +36,14 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
33
36
|
const result = await transformJsxScriptTagsCode(code, mockManifest);
|
|
34
37
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
(
|
|
40
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
|
|
41
|
+
jsx("script", {
|
|
37
42
|
type: "module",
|
|
38
|
-
children: "import(
|
|
43
|
+
children: "import('/assets/client-a1b2c3d4.js').then(module => { console.log(module); })",
|
|
39
44
|
nonce: requestInfo.rw.nonce
|
|
40
45
|
})
|
|
46
|
+
)
|
|
41
47
|
`);
|
|
42
48
|
});
|
|
43
49
|
it("transforms inline scripts with type=module", async () => {
|
|
@@ -47,9 +53,12 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
47
53
|
const result = await transformJsxScriptTagsCode(code, mockManifest);
|
|
48
54
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
(
|
|
57
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
|
|
58
|
+
jsx("script", { type: "module", children: "import('/assets/client-a1b2c3d4.js')",
|
|
51
59
|
nonce: requestInfo.rw.nonce
|
|
52
60
|
})
|
|
61
|
+
)
|
|
53
62
|
`);
|
|
54
63
|
});
|
|
55
64
|
it("transforms inline scripts with multiline content", async () => {
|
|
@@ -69,18 +78,21 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
69
78
|
const result = await transformJsxScriptTagsCode(code, mockManifest);
|
|
70
79
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
(
|
|
82
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/entry.js")),
|
|
83
|
+
jsx("script", {
|
|
73
84
|
type: "module",
|
|
74
85
|
children: \`
|
|
75
86
|
// Some comments here
|
|
76
87
|
const init = async () => {
|
|
77
|
-
await import(
|
|
88
|
+
await import('/assets/entry-e5f6g7h8.js');
|
|
78
89
|
console.log('initialized');
|
|
79
90
|
};
|
|
80
91
|
init();
|
|
81
92
|
\`,
|
|
82
93
|
nonce: requestInfo.rw.nonce
|
|
83
94
|
})
|
|
95
|
+
)
|
|
84
96
|
`);
|
|
85
97
|
});
|
|
86
98
|
it("transforms multiple imports in the same inline script", async () => {
|
|
@@ -96,14 +108,18 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
96
108
|
const result = await transformJsxScriptTagsCode(code, mockManifest);
|
|
97
109
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
(
|
|
112
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
|
|
113
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/entry.js")),
|
|
114
|
+
jsx("script", {
|
|
100
115
|
type: "module",
|
|
101
116
|
children: \`
|
|
102
|
-
import(
|
|
103
|
-
import(
|
|
117
|
+
import('/assets/client-a1b2c3d4.js');
|
|
118
|
+
import('/assets/entry-e5f6g7h8.js');
|
|
104
119
|
\`,
|
|
105
120
|
nonce: requestInfo.rw.nonce
|
|
106
121
|
})
|
|
122
|
+
)
|
|
107
123
|
`);
|
|
108
124
|
});
|
|
109
125
|
it("transforms link href attributes with preload rel", async () => {
|
|
@@ -177,9 +193,12 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
177
193
|
jsx("body", {
|
|
178
194
|
children: [
|
|
179
195
|
jsx("div", { id: "root", children: props.children }),
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
196
|
+
(
|
|
197
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
|
|
198
|
+
jsx("script", { children: "import(\\"\/assets\/client-a1b2c3d4.js\\")",
|
|
199
|
+
nonce: requestInfo.rw.nonce
|
|
200
|
+
})
|
|
201
|
+
)
|
|
183
202
|
]
|
|
184
203
|
})
|
|
185
204
|
]
|
|
@@ -203,11 +222,14 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
203
222
|
const result = await transformJsxScriptTagsCode(code, mockManifest);
|
|
204
223
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
205
224
|
|
|
206
|
-
|
|
225
|
+
(
|
|
226
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/non-existent.js")),
|
|
227
|
+
jsx("script", {
|
|
207
228
|
src: "/src/non-existent.js",
|
|
208
229
|
type: "module",
|
|
209
230
|
nonce: requestInfo.rw.nonce
|
|
210
231
|
})
|
|
232
|
+
)
|
|
211
233
|
`);
|
|
212
234
|
});
|
|
213
235
|
it("adds nonce to script tags with src attribute and imports requestInfo", async () => {
|
|
@@ -220,11 +242,14 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
220
242
|
const result = await transformJsxScriptTagsCode(code, mockManifest);
|
|
221
243
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
222
244
|
|
|
223
|
-
|
|
245
|
+
(
|
|
246
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
|
|
247
|
+
jsx("script", {
|
|
224
248
|
src: "/assets/client-a1b2c3d4.js",
|
|
225
249
|
type: "module",
|
|
226
250
|
nonce: requestInfo.rw.nonce
|
|
227
251
|
})
|
|
252
|
+
)
|
|
228
253
|
`);
|
|
229
254
|
});
|
|
230
255
|
it("adds nonce to script tags with string literal children", async () => {
|
|
@@ -324,11 +349,14 @@ describe("transformJsxScriptTagsCode", () => {
|
|
|
324
349
|
const result = await transformJsxScriptTagsCode(code);
|
|
325
350
|
expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
|
|
326
351
|
|
|
327
|
-
|
|
352
|
+
(
|
|
353
|
+
(requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
|
|
354
|
+
jsx("script", {
|
|
328
355
|
src: "/src/client.tsx",
|
|
329
356
|
type: "module",
|
|
330
357
|
nonce: requestInfo.rw.nonce
|
|
331
358
|
})
|
|
359
|
+
)
|
|
332
360
|
`);
|
|
333
361
|
});
|
|
334
362
|
});
|