rwsdk 1.0.0-beta.29 → 1.0.0-beta.30
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.
|
@@ -3,6 +3,7 @@ import path, { resolve } from "node:path";
|
|
|
3
3
|
import { INTERMEDIATE_SSR_BRIDGE_PATH } from "../lib/constants.mjs";
|
|
4
4
|
import { buildApp } from "./buildApp.mjs";
|
|
5
5
|
import { externalModules } from "./constants.mjs";
|
|
6
|
+
import { ssrBridgeWrapPlugin } from "./ssrBridgeWrapPlugin.mjs";
|
|
6
7
|
export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clientFiles, serverFiles, clientEntryPoints, }) => ({
|
|
7
8
|
name: "rwsdk:config",
|
|
8
9
|
config: async (_, { command }) => {
|
|
@@ -162,22 +163,14 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
|
|
|
162
163
|
// causes a redeclaration error. To solve this, we wrap the SSR
|
|
163
164
|
// bundle in an exporting IIFE. This creates a scope boundary,
|
|
164
165
|
// preventing symbol collisions while producing a valid,
|
|
165
|
-
// tree-shakeable ES module.
|
|
166
|
-
//
|
|
167
|
-
//
|
|
166
|
+
// tree-shakeable ES module.
|
|
167
|
+
//
|
|
168
|
+
// context(justinvdm, 19 Nov 2025): We use a custom plugin
|
|
169
|
+
// (ssrBridgeWrapPlugin) to intelligently inject the IIFE *after*
|
|
170
|
+
// any top-level external imports, ensuring they remain valid.
|
|
168
171
|
inlineDynamicImports: true,
|
|
169
|
-
banner: `export const { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`,
|
|
170
|
-
footer: `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`,
|
|
171
172
|
},
|
|
172
|
-
plugins: [
|
|
173
|
-
{
|
|
174
|
-
name: "rwsdk:ssr-bridge-exports",
|
|
175
|
-
renderChunk(code) {
|
|
176
|
-
// Remove the original export statement as it's now handled by the banner/footer
|
|
177
|
-
return code.replace(/export\s*{[^}]+};?/, "");
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
],
|
|
173
|
+
plugins: [ssrBridgeWrapPlugin()],
|
|
181
174
|
},
|
|
182
175
|
},
|
|
183
176
|
},
|
|
@@ -77,6 +77,15 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
77
77
|
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
|
+
// context(justinvdm, 19 Nov 2025):
|
|
81
|
+
// Ensure platform-specific modules are always treated as external in the
|
|
82
|
+
// SSR environment. This is critical for builds, where we produce a
|
|
83
|
+
// standalone SSR bundle. Without this, Vite might try to bundle these
|
|
84
|
+
// virtual modules or fail to resolve them.
|
|
85
|
+
if (this.environment.name === "ssr" && externalModulesSet.has(id)) {
|
|
86
|
+
log("SSR environment: marking %s as external", id);
|
|
87
|
+
return { id, external: true };
|
|
88
|
+
}
|
|
80
89
|
if (isDev) {
|
|
81
90
|
// context(justinvdm, 27 May 2025): In dev, we need to dynamically load
|
|
82
91
|
// SSR modules, so we return the virtual id so that the dynamic loading
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Lang, parse as sgParse } from "@ast-grep/napi";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import MagicString from "magic-string";
|
|
4
|
+
const log = debug("rwsdk:vite:ssr-bridge-wrap");
|
|
5
|
+
export const ssrBridgeWrapPlugin = () => {
|
|
6
|
+
return {
|
|
7
|
+
name: "rwsdk:ssr-bridge-wrap",
|
|
8
|
+
apply: "build",
|
|
9
|
+
renderChunk(code, chunk) {
|
|
10
|
+
try {
|
|
11
|
+
if (!chunk.fileName.endsWith("ssr_bridge.js")) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const s = new MagicString(code);
|
|
15
|
+
// Use AST parsing to find actual import statements (not in comments)
|
|
16
|
+
const root = sgParse(Lang.JavaScript, code);
|
|
17
|
+
// Find all import statements using AST patterns
|
|
18
|
+
const importPatterns = [
|
|
19
|
+
'import { $$$ } from "$MODULE"',
|
|
20
|
+
"import { $$$ } from '$MODULE'",
|
|
21
|
+
'import $DEFAULT from "$MODULE"',
|
|
22
|
+
"import $DEFAULT from '$MODULE'",
|
|
23
|
+
'import * as $NS from "$MODULE"',
|
|
24
|
+
"import * as $NS from '$MODULE'",
|
|
25
|
+
'import "$MODULE"',
|
|
26
|
+
"import '$MODULE'",
|
|
27
|
+
];
|
|
28
|
+
let lastImportEnd = -1;
|
|
29
|
+
for (const pattern of importPatterns) {
|
|
30
|
+
const matches = root.root().findAll(pattern);
|
|
31
|
+
for (const match of matches) {
|
|
32
|
+
const range = match.range();
|
|
33
|
+
if (range.end.index > lastImportEnd) {
|
|
34
|
+
lastImportEnd = range.end.index;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Find the export statement using AST
|
|
39
|
+
const exportPatterns = [
|
|
40
|
+
"export { $$$ }",
|
|
41
|
+
'export { $$$ } from "$MODULE"',
|
|
42
|
+
"export { $$$ } from '$MODULE'",
|
|
43
|
+
];
|
|
44
|
+
let exportStart = -1;
|
|
45
|
+
let exportEnd = -1;
|
|
46
|
+
for (const pattern of exportPatterns) {
|
|
47
|
+
const matches = root.root().findAll(pattern);
|
|
48
|
+
for (const match of matches) {
|
|
49
|
+
const range = match.range();
|
|
50
|
+
// Check if this export contains our target symbols
|
|
51
|
+
const text = match.text();
|
|
52
|
+
if (text.includes("renderHtmlStream") &&
|
|
53
|
+
text.includes("ssrLoadModule") &&
|
|
54
|
+
text.includes("ssrWebpackRequire")) {
|
|
55
|
+
exportStart = range.start.index;
|
|
56
|
+
exportEnd = range.end.index;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (exportStart !== -1)
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
const banner = `export const { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`;
|
|
64
|
+
const footer = `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`;
|
|
65
|
+
// Insert banner after the last import (or at the beginning if no imports)
|
|
66
|
+
const insertIndex = lastImportEnd === -1 ? 0 : lastImportEnd;
|
|
67
|
+
s.appendLeft(insertIndex, banner + "\n");
|
|
68
|
+
// Append footer at the end
|
|
69
|
+
s.append(footer);
|
|
70
|
+
// Remove the original export statement if found
|
|
71
|
+
if (exportStart !== -1 && exportEnd !== -1) {
|
|
72
|
+
s.remove(exportStart, exportEnd);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
code: s.toString(),
|
|
76
|
+
map: s.generateMap(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.error("Error in ssrBridgeWrapPlugin:", e);
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
};
|