rwsdk 1.0.0-beta.4 → 1.0.0-beta.41
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/constants.d.mts +1 -0
- package/dist/lib/constants.mjs +7 -4
- package/dist/lib/e2e/browser.mjs +6 -2
- package/dist/lib/e2e/constants.d.mts +4 -0
- package/dist/lib/e2e/constants.mjs +49 -12
- package/dist/lib/e2e/dev.mjs +37 -49
- package/dist/lib/e2e/environment.d.mts +2 -0
- package/dist/lib/e2e/environment.mjs +201 -64
- package/dist/lib/e2e/index.d.mts +1 -0
- package/dist/lib/e2e/index.mjs +1 -0
- package/dist/lib/e2e/poll.d.mts +1 -1
- package/dist/lib/e2e/release.d.mts +1 -0
- package/dist/lib/e2e/release.mjs +16 -32
- package/dist/lib/e2e/tarball.mjs +2 -34
- package/dist/lib/e2e/testHarness.d.mts +34 -3
- package/dist/lib/e2e/testHarness.mjs +219 -90
- package/dist/lib/e2e/utils.d.mts +1 -0
- package/dist/lib/e2e/utils.mjs +15 -0
- package/dist/runtime/client/client.d.ts +35 -0
- package/dist/runtime/client/client.js +35 -0
- package/dist/runtime/client/navigation.d.ts +49 -0
- package/dist/runtime/client/navigation.js +80 -31
- package/dist/runtime/entries/clientSSR.d.ts +1 -0
- package/dist/runtime/entries/clientSSR.js +3 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
- package/dist/runtime/entries/router.d.ts +1 -0
- package/dist/runtime/entries/routerClient.d.ts +1 -0
- package/dist/runtime/entries/routerClient.js +1 -0
- package/dist/runtime/entries/worker.d.ts +2 -0
- package/dist/runtime/entries/worker.js +2 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
- package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
- package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
- package/dist/runtime/lib/db/createDb.d.ts +1 -2
- package/dist/runtime/lib/db/createDb.js +4 -0
- package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
- package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
- package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
- package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
- package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
- package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
- package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
- package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
- package/dist/runtime/lib/links.d.ts +21 -7
- package/dist/runtime/lib/links.js +82 -24
- package/dist/runtime/lib/links.test.js +20 -0
- package/dist/runtime/lib/manifest.d.ts +1 -1
- package/dist/runtime/lib/manifest.js +7 -4
- package/dist/runtime/lib/realtime/client.js +8 -2
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +153 -36
- package/dist/runtime/lib/router.js +169 -20
- package/dist/runtime/lib/router.test.js +241 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
- package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
- package/dist/runtime/lib/types.js +1 -0
- package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
- package/dist/runtime/render/renderToStream.d.ts +4 -2
- package/dist/runtime/render/renderToStream.js +53 -24
- package/dist/runtime/render/renderToString.d.ts +3 -6
- package/dist/runtime/requestInfo/types.d.ts +4 -1
- package/dist/runtime/requestInfo/utils.d.ts +9 -0
- package/dist/runtime/requestInfo/utils.js +44 -0
- package/dist/runtime/requestInfo/worker.d.ts +0 -1
- package/dist/runtime/requestInfo/worker.js +3 -10
- package/dist/runtime/script.d.ts +1 -3
- package/dist/runtime/script.js +1 -10
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +13 -0
- package/dist/runtime/worker.d.ts +3 -1
- package/dist/runtime/worker.js +32 -0
- package/dist/scripts/debug-sync.mjs +18 -20
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +59 -113
- package/dist/use-synced-state/SyncedStateServer.d.mts +21 -0
- package/dist/use-synced-state/SyncedStateServer.mjs +128 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +109 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
- package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/worker.test.mjs +69 -0
- package/dist/use-synced-state/client-core.d.ts +26 -0
- package/dist/use-synced-state/client-core.js +39 -0
- package/dist/use-synced-state/client.d.ts +3 -0
- package/dist/use-synced-state/client.js +4 -0
- package/dist/use-synced-state/constants.d.mts +1 -0
- package/dist/use-synced-state/constants.mjs +1 -0
- package/dist/use-synced-state/useSyncedState.d.ts +20 -0
- package/dist/use-synced-state/useSyncedState.js +58 -0
- package/dist/use-synced-state/worker.d.mts +13 -0
- package/dist/use-synced-state/worker.mjs +69 -0
- package/dist/vite/buildApp.mjs +34 -2
- package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
- package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
- package/dist/vite/configPlugin.mjs +9 -14
- package/dist/vite/constants.d.mts +1 -0
- package/dist/vite/constants.mjs +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
- package/dist/vite/devServerTimingPlugin.mjs +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
- package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
- package/dist/vite/directivesPlugin.mjs +4 -4
- package/dist/vite/envResolvers.d.mts +11 -0
- package/dist/vite/envResolvers.mjs +20 -0
- package/dist/vite/getViteEsbuild.mjs +2 -1
- package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
- package/dist/vite/hmrStabilityPlugin.mjs +73 -0
- package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
- package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
- package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
- package/dist/vite/linkerPlugin.d.mts +2 -1
- package/dist/vite/linkerPlugin.mjs +11 -3
- package/dist/vite/linkerPlugin.test.mjs +15 -0
- package/dist/vite/miniflareHMRPlugin.mjs +6 -38
- package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
- package/dist/vite/redwoodPlugin.mjs +8 -10
- package/dist/vite/runDirectivesScan.mjs +72 -18
- package/dist/vite/ssrBridgePlugin.mjs +132 -40
- package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
- package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
- package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
- package/dist/vite/staleDepRetryPlugin.mjs +74 -0
- package/dist/vite/statePlugin.d.mts +4 -0
- package/dist/vite/statePlugin.mjs +62 -0
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
- package/dist/vite/virtualPlugin.mjs +6 -7
- package/package.json +27 -10
- package/dist/vite/manifestPlugin.d.mts +0 -4
- package/dist/vite/manifestPlugin.mjs +0 -63
- /package/dist/runtime/lib/{rwContext.js → links.test.d.ts} +0 -0
|
@@ -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 }) => {
|
|
@@ -55,6 +56,7 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
|
|
|
55
56
|
build: {
|
|
56
57
|
outDir: resolve(projectRootDir, "dist", "worker"),
|
|
57
58
|
emitAssets: true,
|
|
59
|
+
ssrManifest: true,
|
|
58
60
|
emptyOutDir: false,
|
|
59
61
|
ssr: true,
|
|
60
62
|
},
|
|
@@ -93,6 +95,7 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
|
|
|
93
95
|
"rwsdk/constants",
|
|
94
96
|
"rwsdk/debug",
|
|
95
97
|
"rwsdk/realtime/client",
|
|
98
|
+
"rwsdk/router",
|
|
96
99
|
"rwsdk/turnstile",
|
|
97
100
|
],
|
|
98
101
|
entries: [],
|
|
@@ -161,22 +164,14 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
|
|
|
161
164
|
// causes a redeclaration error. To solve this, we wrap the SSR
|
|
162
165
|
// bundle in an exporting IIFE. This creates a scope boundary,
|
|
163
166
|
// preventing symbol collisions while producing a valid,
|
|
164
|
-
// tree-shakeable ES module.
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
+
// tree-shakeable ES module.
|
|
168
|
+
//
|
|
169
|
+
// context(justinvdm, 19 Nov 2025): We use a custom plugin
|
|
170
|
+
// (ssrBridgeWrapPlugin) to intelligently inject the IIFE *after*
|
|
171
|
+
// any top-level external imports, ensuring they remain valid.
|
|
167
172
|
inlineDynamicImports: true,
|
|
168
|
-
banner: `export const { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`,
|
|
169
|
-
footer: `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`,
|
|
170
173
|
},
|
|
171
|
-
plugins: [
|
|
172
|
-
{
|
|
173
|
-
name: "rwsdk:ssr-bridge-exports",
|
|
174
|
-
renderChunk(code) {
|
|
175
|
-
// Remove the original export statement as it's now handled by the banner/footer
|
|
176
|
-
return code.replace(/export\s*{[^}]+};?/, "");
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
],
|
|
174
|
+
plugins: [ssrBridgeWrapPlugin()],
|
|
180
175
|
},
|
|
181
176
|
},
|
|
182
177
|
},
|
package/dist/vite/constants.mjs
CHANGED
|
@@ -42,6 +42,10 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
|
|
|
42
42
|
log("Development mode: %s", isDev);
|
|
43
43
|
},
|
|
44
44
|
configureServer(server) {
|
|
45
|
+
// context(justinvdm, 19 Nov 2025): This hook simply saves a reference
|
|
46
|
+
// to the dev server instance for use in other hooks. Unlike plugins that
|
|
47
|
+
// must run before the Cloudflare plugin to prevent startup deadlocks,
|
|
48
|
+
// its execution order is not critical, so `enforce: 'pre'` is not needed.
|
|
45
49
|
devServer = server;
|
|
46
50
|
},
|
|
47
51
|
configEnvironment(env, viteConfig) {
|
|
@@ -97,9 +101,12 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
|
|
|
97
101
|
log("Skipping optimizeDeps and aliasing for environment: %s", env);
|
|
98
102
|
}
|
|
99
103
|
},
|
|
100
|
-
resolveId(source) {
|
|
101
|
-
// Skip during directive scanning to avoid performance issues
|
|
102
|
-
|
|
104
|
+
resolveId(source, _importer, options) {
|
|
105
|
+
// Skip during our directive scanning to avoid performance issues
|
|
106
|
+
// context(justinvdm, 20 Jan 2025): We check options.custom?.rwsdk?.directiveScan to distinguish
|
|
107
|
+
// between our directive scan (which should skip) and external calls like Cloudflare's early
|
|
108
|
+
// dispatch (which should be handled normally).
|
|
109
|
+
if (options?.custom?.rwsdk?.directiveScan === true) {
|
|
103
110
|
return;
|
|
104
111
|
}
|
|
105
112
|
if (source !== `${config.virtualModuleName}.js`) {
|
|
@@ -140,10 +147,6 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
|
|
|
140
147
|
return source;
|
|
141
148
|
},
|
|
142
149
|
async load(id) {
|
|
143
|
-
// Skip during directive scanning to avoid performance issues
|
|
144
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
150
|
if (id === config.virtualModuleName + ".js") {
|
|
148
151
|
log("Loading %s module with %d files", config.virtualModuleName, files.size);
|
|
149
152
|
const environment = this.environment?.name || "client";
|
|
@@ -6,6 +6,10 @@ export const devServerTimingPlugin = () => {
|
|
|
6
6
|
return {
|
|
7
7
|
name: "rwsdk:dev-server-timing",
|
|
8
8
|
configureServer(server) {
|
|
9
|
+
// context(justinvdm, 19 Nov 2025): This hook adds a middleware for
|
|
10
|
+
// logging the time to first response. Unlike other plugins that must
|
|
11
|
+
// run before the Cloudflare plugin to prevent startup deadlocks, its
|
|
12
|
+
// execution order is not critical, so `enforce: 'pre'` is not needed.
|
|
9
13
|
server.middlewares.use((_req, res, next) => {
|
|
10
14
|
if (!hasLoggedFirstResponse) {
|
|
11
15
|
res.on("finish", () => {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const diagnosticAssetGraphPlugin = ({ rootDir, }) => ({
|
|
2
|
+
name: "rwsdk:diagnostic-asset-graph",
|
|
3
|
+
apply: "build",
|
|
4
|
+
async buildEnd() {
|
|
5
|
+
if (this.environment.name === "worker" &&
|
|
6
|
+
process.env.RWSDK_BUILD_PASS === "worker") {
|
|
7
|
+
console.log("\n=== Asset Graph Diagnostic ===");
|
|
8
|
+
const moduleIds = Array.from(this.getModuleIds());
|
|
9
|
+
console.log(`Total modules: ${moduleIds.length}`);
|
|
10
|
+
const urlImportedModules = new Set();
|
|
11
|
+
const publicAssets = new Set();
|
|
12
|
+
for (const moduleId of moduleIds) {
|
|
13
|
+
const moduleInfo = this.getModuleInfo(moduleId);
|
|
14
|
+
if (moduleInfo) {
|
|
15
|
+
const isImportedByUrl = moduleInfo.importers.some((importer) => importer.includes("?url")) || moduleId.includes("?url");
|
|
16
|
+
if (isImportedByUrl) {
|
|
17
|
+
urlImportedModules.add(moduleId);
|
|
18
|
+
console.log(`\nModule imported with ?url: ${moduleId}`);
|
|
19
|
+
console.log(` Importers: ${moduleInfo.importers.join(", ")}`);
|
|
20
|
+
if (moduleInfo.importedIds) {
|
|
21
|
+
console.log(` Imports: ${moduleInfo.importedIds.join(", ")}`);
|
|
22
|
+
for (const importedId of moduleInfo.importedIds) {
|
|
23
|
+
publicAssets.add(importedId);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (moduleInfo.importers.some((importer) => importer.includes("Document.tsx"))) {
|
|
28
|
+
console.log(`\nModule imported by Document.tsx: ${moduleId}`);
|
|
29
|
+
console.log(` Importers: ${moduleInfo.importers.join(", ")}`);
|
|
30
|
+
if (moduleInfo.importedIds) {
|
|
31
|
+
console.log(` Imports: ${moduleInfo.importedIds.join(", ")}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
console.log(`\nTotal modules imported with ?url: ${urlImportedModules.size}`);
|
|
37
|
+
console.log(`Total transitive dependencies: ${publicAssets.size}`);
|
|
38
|
+
console.log("\n=== End Asset Graph Diagnostic ===\n");
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -37,8 +37,16 @@ export const directiveModulesDevPlugin = ({ clientFiles, serverFiles, projectRoo
|
|
|
37
37
|
const APP_SERVER_BARREL_PATH = path.join(tempDir, "app-server-barrel.js");
|
|
38
38
|
return {
|
|
39
39
|
name: "rwsdk:directive-modules-dev",
|
|
40
|
+
enforce: "pre",
|
|
40
41
|
configureServer(server) {
|
|
41
|
-
|
|
42
|
+
// context(justinvdm, 19 Nov 2025): We must run this hook before the
|
|
43
|
+
// Cloudflare plugin's `configureServer` hook. The Cloudflare plugin makes
|
|
44
|
+
// a request back to the dev server to determine worker exports, which
|
|
45
|
+
// triggers Vite's dependency optimizer. Our esbuild plugin for the
|
|
46
|
+
// optimizer blocks on `scanPromise`. By running this first with `enforce: 'pre'`,
|
|
47
|
+
// we ensure our scan is kicked off before the Cloudflare plugin can trigger
|
|
48
|
+
// the optimizer, preventing a deadlock.
|
|
49
|
+
if (!process.env.VITE_IS_DEV_SERVER) {
|
|
42
50
|
resolveScanPromise();
|
|
43
51
|
return;
|
|
44
52
|
}
|
|
@@ -35,6 +35,10 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
|
|
|
35
35
|
isBuild = config.command === "build";
|
|
36
36
|
},
|
|
37
37
|
configureServer(server) {
|
|
38
|
+
// context(justinvdm, 19 Nov 2025): This hook adds a middleware to track
|
|
39
|
+
// when the first server response has finished. Unlike plugins that must
|
|
40
|
+
// run before the Cloudflare plugin to prevent startup deadlocks, its
|
|
41
|
+
// execution order is not critical, so `enforce: 'pre'` is not needed.
|
|
38
42
|
devServer = server;
|
|
39
43
|
devServer.middlewares.use((_req, res, next) => {
|
|
40
44
|
// context(justinvdm, 15 Jun 2025): We want to watch for new client and server modules
|
|
@@ -52,10 +56,6 @@ export const directivesPlugin = ({ projectRootDir, clientFiles, serverFiles, })
|
|
|
52
56
|
});
|
|
53
57
|
},
|
|
54
58
|
async transform(code, id) {
|
|
55
|
-
// Skip during directive scanning to avoid performance issues
|
|
56
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
59
|
if (isBuild &&
|
|
60
60
|
this.environment?.name === "worker" &&
|
|
61
61
|
process.env.RWSDK_BUILD_PASS !== "worker") {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import enhancedResolve from "enhanced-resolve";
|
|
2
|
+
export declare const ENV_RESOLVERS: {
|
|
3
|
+
ssr: enhancedResolve.ResolveFunction;
|
|
4
|
+
worker: enhancedResolve.ResolveFunction;
|
|
5
|
+
client: enhancedResolve.ResolveFunction;
|
|
6
|
+
};
|
|
7
|
+
export declare const maybeResolveEnvImport: ({ id, envName, projectRootDir, }: {
|
|
8
|
+
id: string;
|
|
9
|
+
envName: keyof typeof ENV_RESOLVERS;
|
|
10
|
+
projectRootDir: string;
|
|
11
|
+
}) => string | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import enhancedResolve from "enhanced-resolve";
|
|
2
|
+
export const ENV_RESOLVERS = {
|
|
3
|
+
ssr: enhancedResolve.create.sync({
|
|
4
|
+
conditionNames: ["workerd", "worker", "edge", "default"],
|
|
5
|
+
}),
|
|
6
|
+
worker: enhancedResolve.create.sync({
|
|
7
|
+
conditionNames: ["react-server", "workerd", "worker", "edge", "default"],
|
|
8
|
+
}),
|
|
9
|
+
client: enhancedResolve.create.sync({
|
|
10
|
+
conditionNames: ["browser", "default"],
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
export const maybeResolveEnvImport = ({ id, envName, projectRootDir, }) => {
|
|
14
|
+
try {
|
|
15
|
+
return ENV_RESOLVERS[envName](projectRootDir, id) || undefined;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
3
4
|
const require = createRequire(import.meta.url);
|
|
4
5
|
export async function getViteEsbuild(projectRootDir) {
|
|
5
6
|
const vitePath = require.resolve("vite/package.json", {
|
|
@@ -7,6 +8,6 @@ export async function getViteEsbuild(projectRootDir) {
|
|
|
7
8
|
});
|
|
8
9
|
const viteDir = path.dirname(vitePath);
|
|
9
10
|
const esbuildPath = require.resolve("esbuild", { paths: [viteDir] });
|
|
10
|
-
const esbuildModule = await import(esbuildPath);
|
|
11
|
+
const esbuildModule = await import(pathToFileURL(esbuildPath).href);
|
|
11
12
|
return esbuildModule.default || esbuildModule;
|
|
12
13
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import debug from "debug";
|
|
2
|
+
const log = debug("rws-vite-plugin:hmr-stability");
|
|
3
|
+
let stabilityPromise = null;
|
|
4
|
+
let stabilityResolver = null;
|
|
5
|
+
let debounceTimer = null;
|
|
6
|
+
const DEBOUNCE_MS = 500;
|
|
7
|
+
function startWaitingForStability() {
|
|
8
|
+
if (!stabilityPromise) {
|
|
9
|
+
log("Starting to wait for server stability...");
|
|
10
|
+
stabilityPromise = new Promise((resolve) => {
|
|
11
|
+
stabilityResolver = resolve;
|
|
12
|
+
});
|
|
13
|
+
// Start the timer. If it fires, we're stable.
|
|
14
|
+
debounceTimer = setTimeout(finishWaiting, DEBOUNCE_MS);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function activityDetected() {
|
|
18
|
+
if (stabilityPromise) {
|
|
19
|
+
// If we're waiting for stability, reset the timer.
|
|
20
|
+
log("Activity detected, resetting stability timer.");
|
|
21
|
+
if (debounceTimer)
|
|
22
|
+
clearTimeout(debounceTimer);
|
|
23
|
+
debounceTimer = setTimeout(finishWaiting, DEBOUNCE_MS);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function finishWaiting() {
|
|
27
|
+
if (stabilityResolver) {
|
|
28
|
+
log("Server appears stable. Resolving promise.");
|
|
29
|
+
stabilityResolver();
|
|
30
|
+
}
|
|
31
|
+
stabilityPromise = null;
|
|
32
|
+
stabilityResolver = null;
|
|
33
|
+
debounceTimer = null;
|
|
34
|
+
}
|
|
35
|
+
export function hmrStabilityPlugin() {
|
|
36
|
+
return {
|
|
37
|
+
name: "rws-vite-plugin:hmr-stability",
|
|
38
|
+
// Monitor server activity
|
|
39
|
+
transform() {
|
|
40
|
+
activityDetected();
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
configureServer(server) {
|
|
44
|
+
// context(justinvdm, 19 Nov 2025): This hook adds an error handling
|
|
45
|
+
// middleware for stale dependency errors. It runs in a returned function,
|
|
46
|
+
// which intentionally places it late in the middleware stack. Unlike
|
|
47
|
+
// other plugins that must run before the Cloudflare plugin to prevent
|
|
48
|
+
// startup deadlocks, its timing is not critical, so `enforce: 'pre'`
|
|
49
|
+
// is not needed.
|
|
50
|
+
return () => {
|
|
51
|
+
server.middlewares.use(async function rwsdkStaleBundleErrorHandler(err, req, res, next) {
|
|
52
|
+
if (err &&
|
|
53
|
+
typeof err.message === "string" &&
|
|
54
|
+
err.message.includes("new version of the pre-bundle")) {
|
|
55
|
+
log("Caught stale pre-bundle error. Waiting for server to stabilize...");
|
|
56
|
+
startWaitingForStability();
|
|
57
|
+
await stabilityPromise;
|
|
58
|
+
log("Server stabilized. Sending full-reload and redirecting.");
|
|
59
|
+
// Signal the client to do a full page reload.
|
|
60
|
+
server.environments.client.hot.send({
|
|
61
|
+
type: "full-reload",
|
|
62
|
+
});
|
|
63
|
+
// No need to wait further here, the stability promise handled it.
|
|
64
|
+
res.writeHead(307, { Location: req.url });
|
|
65
|
+
res.end();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
next(err);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -4,10 +4,6 @@ export const injectVitePreamble = ({ clientEntryPoints, projectRootDir, }) => ({
|
|
|
4
4
|
name: "rwsdk:inject-vite-preamble",
|
|
5
5
|
apply: "serve",
|
|
6
6
|
transform(code, id) {
|
|
7
|
-
// Skip during directive scanning to avoid performance issues
|
|
8
|
-
if (process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE) {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
7
|
if (this.environment.name !== "client") {
|
|
12
8
|
return;
|
|
13
9
|
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import enhancedResolve from "enhanced-resolve";
|
|
2
1
|
import { Plugin } from "vite";
|
|
3
2
|
export declare const ENV_PREDEFINED_IMPORTS: {
|
|
4
3
|
worker: string[];
|
|
5
4
|
ssr: string[];
|
|
6
5
|
client: string[];
|
|
7
6
|
};
|
|
8
|
-
export declare const ENV_RESOLVERS: {
|
|
9
|
-
ssr: enhancedResolve.ResolveFunction;
|
|
10
|
-
worker: enhancedResolve.ResolveFunction;
|
|
11
|
-
client: enhancedResolve.ResolveFunction;
|
|
12
|
-
};
|
|
13
7
|
export declare const knownDepsResolverPlugin: ({ projectRootDir, }: {
|
|
14
8
|
projectRootDir: string;
|
|
15
9
|
}) => Plugin[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
|
-
import enhancedResolve from "enhanced-resolve";
|
|
3
2
|
import { ROOT_DIR } from "../lib/constants.mjs";
|
|
4
3
|
import { ensureAliasArray } from "./ensureAliasArray.mjs";
|
|
4
|
+
import { ENV_RESOLVERS } from "./envResolvers.mjs";
|
|
5
5
|
const log = debug("rwsdk:vite:known-deps-resolver-plugin");
|
|
6
6
|
const KNOWN_PREFIXES = [
|
|
7
7
|
"react",
|
|
@@ -36,17 +36,6 @@ export const ENV_PREDEFINED_IMPORTS = {
|
|
|
36
36
|
"react-server-dom-webpack/client.edge",
|
|
37
37
|
],
|
|
38
38
|
};
|
|
39
|
-
export const ENV_RESOLVERS = {
|
|
40
|
-
ssr: enhancedResolve.create.sync({
|
|
41
|
-
conditionNames: ["workerd", "worker", "edge", "default"],
|
|
42
|
-
}),
|
|
43
|
-
worker: enhancedResolve.create.sync({
|
|
44
|
-
conditionNames: ["react-server", "workerd", "worker", "edge", "default"],
|
|
45
|
-
}),
|
|
46
|
-
client: enhancedResolve.create.sync({
|
|
47
|
-
conditionNames: ["browser", "default"],
|
|
48
|
-
}),
|
|
49
|
-
};
|
|
50
39
|
function resolveKnownImport(id, envName, projectRootDir, isPrefixedImport = false) {
|
|
51
40
|
if (!isPrefixedImport) {
|
|
52
41
|
const isKnownImport = KNOWN_PREFIXES.some((prefix) => id === prefix || id.startsWith(`${prefix}/`));
|
|
@@ -106,15 +95,32 @@ export const knownDepsResolverPlugin = ({ projectRootDir, }) => {
|
|
|
106
95
|
if (!mappings) {
|
|
107
96
|
return null;
|
|
108
97
|
}
|
|
98
|
+
// Create reverse mapping from slugified names to original imports
|
|
99
|
+
// Vite converts "react-dom/server.edge" -> "react-dom_server__edge"
|
|
100
|
+
// Pattern: / becomes _, . becomes __
|
|
101
|
+
const slugifiedToOriginal = new Map();
|
|
102
|
+
for (const [original, resolved] of mappings) {
|
|
103
|
+
const slugified = original.replace(/\//g, "_").replace(/\./g, "__");
|
|
104
|
+
slugifiedToOriginal.set(slugified, original);
|
|
105
|
+
}
|
|
109
106
|
return {
|
|
110
107
|
name: `rwsdk:known-dependencies-resolver-esbuild-${envName}`,
|
|
111
108
|
setup(build) {
|
|
112
109
|
build.onResolve({ filter: /.*/ }, (args) => {
|
|
113
110
|
let resolved = mappings.get(args.path);
|
|
111
|
+
// If not found, check if it's a slugified version
|
|
112
|
+
if (!resolved) {
|
|
113
|
+
const originalImport = slugifiedToOriginal.get(args.path);
|
|
114
|
+
if (originalImport) {
|
|
115
|
+
resolved = mappings.get(originalImport);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
114
118
|
if (!resolved) {
|
|
115
119
|
resolved = resolveKnownImport(args.path, envName, projectRootDir);
|
|
116
120
|
}
|
|
117
|
-
|
|
121
|
+
// Resolve for both entry points (importer === '') and regular imports
|
|
122
|
+
// Entry points come from optimizeDeps.include and are critical to intercept
|
|
123
|
+
if (resolved) {
|
|
118
124
|
if (args.path === "react-server-dom-webpack/client.edge") {
|
|
119
125
|
return;
|
|
120
126
|
}
|
|
@@ -129,7 +135,6 @@ export const knownDepsResolverPlugin = ({ projectRootDir, }) => {
|
|
|
129
135
|
return [
|
|
130
136
|
{
|
|
131
137
|
name: "rwsdk:known-dependencies-resolver:config",
|
|
132
|
-
enforce: "post",
|
|
133
138
|
config(config, { command }) {
|
|
134
139
|
isBuild = command === "build";
|
|
135
140
|
log("Configuring plugin for command=%s", command);
|
|
@@ -173,9 +178,12 @@ export const knownDepsResolverPlugin = ({ projectRootDir, }) => {
|
|
|
173
178
|
{
|
|
174
179
|
name: "rwsdk:known-dependencies-resolver:resolveId",
|
|
175
180
|
enforce: "pre",
|
|
176
|
-
async resolveId(id, importer) {
|
|
177
|
-
// Skip during directive scanning to avoid performance issues
|
|
178
|
-
|
|
181
|
+
async resolveId(id, importer, options) {
|
|
182
|
+
// Skip during our directive scanning to avoid performance issues
|
|
183
|
+
// context(justinvdm, 20 Jan 2025): We check options.custom?.rwsdk?.directiveScan to distinguish
|
|
184
|
+
// between our directive scan (which should skip) and external calls like Cloudflare's early
|
|
185
|
+
// dispatch (which should be handled normally).
|
|
186
|
+
if (options?.custom?.rwsdk?.directiveScan === true) {
|
|
179
187
|
return;
|
|
180
188
|
}
|
|
181
189
|
if (!isBuild) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
|
-
export declare function linkWorkerBundle({ code, manifestContent, projectRootDir, }: {
|
|
2
|
+
export declare function linkWorkerBundle({ code, manifestContent, projectRootDir, base, }: {
|
|
3
3
|
code: string;
|
|
4
4
|
manifestContent: string;
|
|
5
5
|
projectRootDir: string;
|
|
6
|
+
base?: string;
|
|
6
7
|
}): {
|
|
7
8
|
code: string;
|
|
8
9
|
map: null;
|
|
@@ -4,19 +4,22 @@ import path from "node:path";
|
|
|
4
4
|
import { CLIENT_MANIFEST_RELATIVE_PATH } from "../lib/constants.mjs";
|
|
5
5
|
import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
|
|
6
6
|
const log = debug("rwsdk:vite:linker-plugin");
|
|
7
|
-
export function linkWorkerBundle({ code, manifestContent, projectRootDir, }) {
|
|
7
|
+
export function linkWorkerBundle({ code, manifestContent, projectRootDir, base, }) {
|
|
8
8
|
let newCode = code;
|
|
9
9
|
const manifest = JSON.parse(manifestContent);
|
|
10
10
|
// 1. Replace the manifest placeholder with the actual manifest content.
|
|
11
11
|
log("Injecting manifest into worker bundle");
|
|
12
|
-
newCode = newCode.replace('"__RWSDK_MANIFEST_PLACEHOLDER__"
|
|
12
|
+
newCode = newCode.replace(/['"]__RWSDK_MANIFEST_PLACEHOLDER__['"]/, manifestContent);
|
|
13
13
|
// 2. Replace asset placeholders with their final hashed paths.
|
|
14
14
|
log("Replacing asset placeholders in final worker bundle");
|
|
15
15
|
for (const [key, value] of Object.entries(manifest)) {
|
|
16
16
|
const normalizedKey = normalizeModulePath(key, projectRootDir, {
|
|
17
17
|
isViteStyle: false,
|
|
18
18
|
});
|
|
19
|
-
|
|
19
|
+
// If base is provided, prepend it with the final hashed path.
|
|
20
|
+
// Base is assumed to have a trailing "/".
|
|
21
|
+
const assetPath = (base ? base : "/") + value.file;
|
|
22
|
+
newCode = newCode.replaceAll(`rwsdk_asset:${normalizedKey}`, assetPath);
|
|
20
23
|
}
|
|
21
24
|
// 3. Deprefix any remaining placeholders that were not in the manifest.
|
|
22
25
|
// This handles public assets that don't go through the bundler.
|
|
@@ -28,8 +31,12 @@ export function linkWorkerBundle({ code, manifestContent, projectRootDir, }) {
|
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
export const linkerPlugin = ({ projectRootDir, }) => {
|
|
34
|
+
let config;
|
|
31
35
|
return {
|
|
32
36
|
name: "rwsdk:linker",
|
|
37
|
+
configResolved(resolvedConfig) {
|
|
38
|
+
config = resolvedConfig;
|
|
39
|
+
},
|
|
33
40
|
async renderChunk(code) {
|
|
34
41
|
if (this.environment.name !== "worker" ||
|
|
35
42
|
process.env.RWSDK_BUILD_PASS !== "linker") {
|
|
@@ -41,6 +48,7 @@ export const linkerPlugin = ({ projectRootDir, }) => {
|
|
|
41
48
|
code,
|
|
42
49
|
manifestContent,
|
|
43
50
|
projectRootDir,
|
|
51
|
+
base: config.base,
|
|
44
52
|
});
|
|
45
53
|
log("Final worker chunk rendered");
|
|
46
54
|
return result;
|
|
@@ -29,6 +29,21 @@ describe("linkWorkerBundle", () => {
|
|
|
29
29
|
expect(result.code).toContain(`const stylesheet = "/assets/styles.123.css";`);
|
|
30
30
|
expect(result.code).toContain(`const logo = "/assets/logo.abc.svg";`);
|
|
31
31
|
});
|
|
32
|
+
it("should replace asset placeholder with a base + hashed paths from the manifest if base is provided", () => {
|
|
33
|
+
const code = `
|
|
34
|
+
const stylesheet = "rwsdk_asset:/src/styles.css";
|
|
35
|
+
const logo = "rwsdk_asset:/src/logo.svg";
|
|
36
|
+
`;
|
|
37
|
+
const base = "/base/";
|
|
38
|
+
const result = linkWorkerBundle({
|
|
39
|
+
code,
|
|
40
|
+
manifestContent,
|
|
41
|
+
projectRootDir,
|
|
42
|
+
base,
|
|
43
|
+
});
|
|
44
|
+
expect(result.code).toContain(`const stylesheet = "/base/assets/styles.123.css";`);
|
|
45
|
+
expect(result.code).toContain(`const logo = "/base/assets/logo.abc.svg";`);
|
|
46
|
+
});
|
|
32
47
|
it("should deprefix remaining asset placeholders not in the manifest", () => {
|
|
33
48
|
const code = `const publicImg = "rwsdk_asset:/images/photo.jpg";`;
|
|
34
49
|
const result = linkWorkerBundle({
|
|
@@ -33,29 +33,15 @@ export const hasEntryAsAncestor = ({ module, entryFile, seen = new Set(), }) =>
|
|
|
33
33
|
}
|
|
34
34
|
return false;
|
|
35
35
|
};
|
|
36
|
-
const isInUseClientGraph = ({ file, clientFiles, server, }) => {
|
|
37
|
-
const id = normalizeModulePath(file, server.config.root);
|
|
38
|
-
if (clientFiles.has(id)) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
const modules = server.environments.client.moduleGraph.getModulesByFile(file);
|
|
42
|
-
if (!modules) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
for (const m of modules) {
|
|
46
|
-
for (const importer of m.importers) {
|
|
47
|
-
if (importer.file &&
|
|
48
|
-
isInUseClientGraph({ file: importer.file, clientFiles, server })) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return false;
|
|
54
|
-
};
|
|
55
36
|
export const miniflareHMRPlugin = (givenOptions) => [
|
|
56
37
|
{
|
|
57
38
|
name: "rwsdk:miniflare-hmr",
|
|
58
39
|
configureServer(server) {
|
|
40
|
+
// context(justinvdm, 19 Nov 2025): This hook sets up an error handler
|
|
41
|
+
// middleware. It runs in a returned function, which intentionally
|
|
42
|
+
// places it late in the middleware stack. Unlike plugins that must
|
|
43
|
+
// run before the Cloudflare plugin to prevent startup deadlocks, its
|
|
44
|
+
// timing is not critical, so `enforce: 'pre'` is not needed.
|
|
59
45
|
return () => {
|
|
60
46
|
server.middlewares.use(function rwsdkDevServerErrorHandler(err, _req, _res, next) {
|
|
61
47
|
if (err) {
|
|
@@ -93,15 +79,6 @@ export const miniflareHMRPlugin = (givenOptions) => [
|
|
|
93
79
|
}
|
|
94
80
|
if (this.environment.name === "ssr") {
|
|
95
81
|
log("SSR update, invalidating recursively", ctx.file);
|
|
96
|
-
const isUseClientUpdate = isInUseClientGraph({
|
|
97
|
-
file: ctx.file,
|
|
98
|
-
clientFiles,
|
|
99
|
-
server: ctx.server,
|
|
100
|
-
});
|
|
101
|
-
if (!isUseClientUpdate) {
|
|
102
|
-
log("hmr: not a use client update, short circuiting");
|
|
103
|
-
return [];
|
|
104
|
-
}
|
|
105
82
|
invalidateModule(ctx.server, "ssr", ctx.file);
|
|
106
83
|
invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX +
|
|
107
84
|
normalizeModulePath(ctx.file, givenOptions.rootDir));
|
|
@@ -166,21 +143,12 @@ export const miniflareHMRPlugin = (givenOptions) => [
|
|
|
166
143
|
}
|
|
167
144
|
}
|
|
168
145
|
}
|
|
169
|
-
const isUseClientUpdate = isInUseClientGraph({
|
|
170
|
-
file: ctx.file,
|
|
171
|
-
clientFiles,
|
|
172
|
-
server: ctx.server,
|
|
173
|
-
});
|
|
174
|
-
if (!isUseClientUpdate && !ctx.file.endsWith(".css")) {
|
|
175
|
-
log("hmr: not a use client update and not css, short circuiting");
|
|
176
|
-
return [];
|
|
177
|
-
}
|
|
178
|
-
log("hmr: returning client modules for hmr");
|
|
179
146
|
return ctx.modules;
|
|
180
147
|
}
|
|
181
148
|
// The worker needs an update, and the hot check is for the worker environment
|
|
182
149
|
// => Notify for custom RSC-based HMR update, then short circuit HMR
|
|
183
150
|
if (isWorkerUpdate && this.environment.name === environment) {
|
|
151
|
+
invalidateModule(ctx.server, environment, ctx.file);
|
|
184
152
|
const shortName = getShortName(ctx.file, ctx.server.config.root);
|
|
185
153
|
this.environment.logger.info(`${colors.green(`worker update`)} ${colors.dim(shortName)}`, {
|
|
186
154
|
clear: true,
|
|
@@ -1,13 +1,44 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
export const moveStaticAssetsPlugin = ({ rootDir, }) => ({
|
|
3
4
|
name: "rwsdk:move-static-assets",
|
|
4
5
|
apply: "build",
|
|
5
6
|
async closeBundle() {
|
|
6
7
|
if (this.environment.name === "worker" &&
|
|
7
8
|
process.env.RWSDK_BUILD_PASS === "linker") {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const sourceDir = path.join(rootDir, "dist", "worker", "assets");
|
|
10
|
+
const destDir = path.join(rootDir, "dist", "client", "assets");
|
|
11
|
+
if (!(await fs.pathExists(sourceDir))) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const ssrManifestPath = path.join(rootDir, "dist", "worker", ".vite", "ssr-manifest.json");
|
|
15
|
+
if (!(await fs.pathExists(ssrManifestPath))) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const manifestContent = await fs.readFile(ssrManifestPath, "utf-8");
|
|
19
|
+
const ssrManifest = JSON.parse(manifestContent);
|
|
20
|
+
const publicAssets = new Set();
|
|
21
|
+
for (const [moduleId, assetPaths] of Object.entries(ssrManifest)) {
|
|
22
|
+
if (moduleId.includes("?url")) {
|
|
23
|
+
for (const assetPath of assetPaths) {
|
|
24
|
+
const assetFileName = path.basename(assetPath);
|
|
25
|
+
publicAssets.add(assetFileName);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const allFiles = await fs.readdir(sourceDir);
|
|
30
|
+
const filesToMove = allFiles.filter((file) => !file.endsWith(".js") &&
|
|
31
|
+
!file.endsWith(".map") &&
|
|
32
|
+
publicAssets.has(file));
|
|
33
|
+
if (filesToMove.length > 0) {
|
|
34
|
+
await fs.ensureDir(destDir);
|
|
35
|
+
for (const file of filesToMove) {
|
|
36
|
+
const sourceFile = path.join(sourceDir, file);
|
|
37
|
+
const destFile = path.join(destDir, file);
|
|
38
|
+
await fs.move(sourceFile, destFile, { overwrite: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await fs.remove(ssrManifestPath);
|
|
11
42
|
}
|
|
12
43
|
},
|
|
13
44
|
});
|