rwsdk 1.0.0-beta.3 → 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.
- 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 +16 -0
- package/dist/lib/e2e/constants.mjs +77 -0
- package/dist/lib/e2e/dev.mjs +37 -49
- package/dist/lib/e2e/environment.d.mts +2 -0
- package/dist/lib/e2e/environment.mjs +202 -65
- 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 +36 -4
- package/dist/lib/e2e/testHarness.mjs +216 -128
- 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/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 +18 -7
- package/dist/runtime/lib/links.js +70 -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 +147 -33
- 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 -1
- 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.js +3 -2
- 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 +26 -0
- package/dist/scripts/debug-sync.mjs +18 -20
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +52 -113
- package/dist/use-synced-state/SyncStateServer.d.mts +20 -0
- package/dist/use-synced-state/SyncStateServer.mjs +124 -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.d.ts +28 -0
- package/dist/use-synced-state/client.js +39 -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/useSyncState.d.ts +20 -0
- package/dist/use-synced-state/useSyncState.js +58 -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 +14 -0
- package/dist/use-synced-state/worker.mjs +73 -0
- package/dist/vite/buildApp.mjs +34 -2
- package/dist/vite/configPlugin.mjs +8 -14
- package/dist/vite/constants.d.mts +1 -0
- package/dist/vite/constants.mjs +1 -0
- package/dist/vite/directiveModulesDevPlugin.mjs +1 -1
- 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 +68 -0
- package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
- package/dist/vite/knownDepsResolverPlugin.mjs +1 -12
- 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 +1 -38
- package/dist/vite/moveStaticAssetsPlugin.mjs +14 -4
- package/dist/vite/redwoodPlugin.mjs +6 -10
- package/dist/vite/runDirectivesScan.mjs +59 -14
- package/dist/vite/ssrBridgePlugin.mjs +122 -34
- 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 +69 -0
- package/dist/vite/statePlugin.d.mts +4 -0
- package/dist/vite/statePlugin.mjs +62 -0
- package/package.json +26 -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
package/dist/vite/buildApp.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { INTERMEDIATES_OUTPUT_DIR } from "../lib/constants.mjs";
|
|
3
6
|
import { runDirectivesScan } from "./runDirectivesScan.mjs";
|
|
4
7
|
const log = debug("rwsdk:vite:build-app");
|
|
5
8
|
/**
|
|
@@ -10,7 +13,36 @@ const log = debug("rwsdk:vite:build-app");
|
|
|
10
13
|
* @see docs/architecture/productionBuildProcess.md
|
|
11
14
|
*/
|
|
12
15
|
export async function buildApp({ builder, clientEntryPoints, clientFiles, serverFiles, projectRootDir, workerEntryPathname, }) {
|
|
16
|
+
await rm(resolve(projectRootDir, "dist"), { recursive: true, force: true });
|
|
13
17
|
const workerEnv = builder.environments.worker;
|
|
18
|
+
// Run a pre-scan build pass to allow plugins to set up and generate code
|
|
19
|
+
// before scanning.
|
|
20
|
+
console.log("Running plugin setup pass...");
|
|
21
|
+
process.env.RWSDK_BUILD_PASS = "plugin-setup";
|
|
22
|
+
const tempEntryPath = resolve(INTERMEDIATES_OUTPUT_DIR, "temp-entry.js");
|
|
23
|
+
try {
|
|
24
|
+
if (!existsSync(dirname(tempEntryPath))) {
|
|
25
|
+
await mkdir(dirname(tempEntryPath), { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
await writeFile(tempEntryPath, "");
|
|
28
|
+
const originalWorkerBuildConfig = workerEnv.config.build;
|
|
29
|
+
workerEnv.config.build = {
|
|
30
|
+
...originalWorkerBuildConfig,
|
|
31
|
+
write: false,
|
|
32
|
+
rollupOptions: {
|
|
33
|
+
...originalWorkerBuildConfig?.rollupOptions,
|
|
34
|
+
input: {
|
|
35
|
+
index: tempEntryPath,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
await builder.build(workerEnv);
|
|
40
|
+
// Restore the original config
|
|
41
|
+
workerEnv.config.build = originalWorkerBuildConfig;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await rm(tempEntryPath, { force: true });
|
|
45
|
+
}
|
|
14
46
|
await runDirectivesScan({
|
|
15
47
|
rootConfig: builder.config,
|
|
16
48
|
environments: builder.environments,
|
|
@@ -18,7 +50,7 @@ export async function buildApp({ builder, clientEntryPoints, clientFiles, server
|
|
|
18
50
|
serverFiles,
|
|
19
51
|
entries: [workerEntryPathname],
|
|
20
52
|
});
|
|
21
|
-
console.log("Building worker
|
|
53
|
+
console.log("Building worker...");
|
|
22
54
|
process.env.RWSDK_BUILD_PASS = "worker";
|
|
23
55
|
await builder.build(workerEnv);
|
|
24
56
|
log("Used client files after worker build & filtering: %O", Array.from(clientFiles));
|
|
@@ -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 }) => {
|
|
@@ -93,6 +94,7 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
|
|
|
93
94
|
"rwsdk/constants",
|
|
94
95
|
"rwsdk/debug",
|
|
95
96
|
"rwsdk/realtime/client",
|
|
97
|
+
"rwsdk/router",
|
|
96
98
|
"rwsdk/turnstile",
|
|
97
99
|
],
|
|
98
100
|
entries: [],
|
|
@@ -161,22 +163,14 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
|
|
|
161
163
|
// causes a redeclaration error. To solve this, we wrap the SSR
|
|
162
164
|
// bundle in an exporting IIFE. This creates a scope boundary,
|
|
163
165
|
// preventing symbol collisions while producing a valid,
|
|
164
|
-
// tree-shakeable ES module.
|
|
165
|
-
//
|
|
166
|
-
//
|
|
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.
|
|
167
171
|
inlineDynamicImports: true,
|
|
168
|
-
banner: `export const { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream } = (function() {`,
|
|
169
|
-
footer: `return { renderHtmlStream, ssrLoadModule, ssrWebpackRequire, ssrGetModuleExport, createThenableFromReadableStream };\n})();`,
|
|
170
172
|
},
|
|
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
|
-
],
|
|
173
|
+
plugins: [ssrBridgeWrapPlugin()],
|
|
180
174
|
},
|
|
181
175
|
},
|
|
182
176
|
},
|
package/dist/vite/constants.mjs
CHANGED
|
@@ -38,7 +38,7 @@ export const directiveModulesDevPlugin = ({ clientFiles, serverFiles, projectRoo
|
|
|
38
38
|
return {
|
|
39
39
|
name: "rwsdk:directive-modules-dev",
|
|
40
40
|
configureServer(server) {
|
|
41
|
-
if (!process.env.VITE_IS_DEV_SERVER
|
|
41
|
+
if (!process.env.VITE_IS_DEV_SERVER) {
|
|
42
42
|
resolveScanPromise();
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
@@ -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,68 @@
|
|
|
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
|
+
// Return a function to ensure our middleware is placed after internal middlewares
|
|
45
|
+
return () => {
|
|
46
|
+
server.middlewares.use(async function rwsdkStaleBundleErrorHandler(err, req, res, next) {
|
|
47
|
+
if (err &&
|
|
48
|
+
typeof err.message === "string" &&
|
|
49
|
+
err.message.includes("new version of the pre-bundle")) {
|
|
50
|
+
log("Caught stale pre-bundle error. Waiting for server to stabilize...");
|
|
51
|
+
startWaitingForStability();
|
|
52
|
+
await stabilityPromise;
|
|
53
|
+
log("Server stabilized. Sending full-reload and redirecting.");
|
|
54
|
+
// Signal the client to do a full page reload.
|
|
55
|
+
server.environments.client.hot.send({
|
|
56
|
+
type: "full-reload",
|
|
57
|
+
});
|
|
58
|
+
// No need to wait further here, the stability promise handled it.
|
|
59
|
+
res.writeHead(307, { Location: req.url });
|
|
60
|
+
res.end();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
next(err);
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -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}/`));
|
|
@@ -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,25 +33,6 @@ 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",
|
|
@@ -93,15 +74,6 @@ export const miniflareHMRPlugin = (givenOptions) => [
|
|
|
93
74
|
}
|
|
94
75
|
if (this.environment.name === "ssr") {
|
|
95
76
|
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
77
|
invalidateModule(ctx.server, "ssr", ctx.file);
|
|
106
78
|
invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX +
|
|
107
79
|
normalizeModulePath(ctx.file, givenOptions.rootDir));
|
|
@@ -166,21 +138,12 @@ export const miniflareHMRPlugin = (givenOptions) => [
|
|
|
166
138
|
}
|
|
167
139
|
}
|
|
168
140
|
}
|
|
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
141
|
return ctx.modules;
|
|
180
142
|
}
|
|
181
143
|
// The worker needs an update, and the hot check is for the worker environment
|
|
182
144
|
// => Notify for custom RSC-based HMR update, then short circuit HMR
|
|
183
145
|
if (isWorkerUpdate && this.environment.name === environment) {
|
|
146
|
+
invalidateModule(ctx.server, environment, ctx.file);
|
|
184
147
|
const shortName = getShortName(ctx.file, ctx.server.config.root);
|
|
185
148
|
this.environment.logger.info(`${colors.green(`worker update`)} ${colors.dim(shortName)}`, {
|
|
186
149
|
clear: true,
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { glob } from "glob";
|
|
2
4
|
export const moveStaticAssetsPlugin = ({ rootDir, }) => ({
|
|
3
5
|
name: "rwsdk:move-static-assets",
|
|
4
6
|
apply: "build",
|
|
5
7
|
async closeBundle() {
|
|
6
8
|
if (this.environment.name === "worker" &&
|
|
7
9
|
process.env.RWSDK_BUILD_PASS === "linker") {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const sourceDir = path.join(rootDir, "dist", "worker", "assets");
|
|
11
|
+
const destDir = path.join(rootDir, "dist", "client", "assets");
|
|
12
|
+
const cssFiles = await glob("*.css", { cwd: sourceDir });
|
|
13
|
+
if (cssFiles.length > 0) {
|
|
14
|
+
await fs.ensureDir(destDir);
|
|
15
|
+
for (const file of cssFiles) {
|
|
16
|
+
const sourceFile = path.join(sourceDir, file);
|
|
17
|
+
const destFile = path.join(destDir, file);
|
|
18
|
+
await fs.move(sourceFile, destFile, { overwrite: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
}
|
|
12
22
|
},
|
|
13
23
|
});
|
|
@@ -18,12 +18,13 @@ import { directivesPlugin } from "./directivesPlugin.mjs";
|
|
|
18
18
|
import { injectVitePreamble } from "./injectVitePreamblePlugin.mjs";
|
|
19
19
|
import { knownDepsResolverPlugin } from "./knownDepsResolverPlugin.mjs";
|
|
20
20
|
import { linkerPlugin } from "./linkerPlugin.mjs";
|
|
21
|
-
import { manifestPlugin } from "./manifestPlugin.mjs";
|
|
22
21
|
import { miniflareHMRPlugin } from "./miniflareHMRPlugin.mjs";
|
|
23
22
|
import { moveStaticAssetsPlugin } from "./moveStaticAssetsPlugin.mjs";
|
|
24
23
|
import { prismaPlugin } from "./prismaPlugin.mjs";
|
|
25
24
|
import { resolveForcedPaths } from "./resolveForcedPaths.mjs";
|
|
26
25
|
import { ssrBridgePlugin } from "./ssrBridgePlugin.mjs";
|
|
26
|
+
import { staleDepRetryPlugin } from "./staleDepRetryPlugin.mjs";
|
|
27
|
+
import { statePlugin } from "./statePlugin.mjs";
|
|
27
28
|
import { transformJsxScriptTagsPlugin } from "./transformJsxScriptTagsPlugin.mjs";
|
|
28
29
|
import { useClientLookupPlugin } from "./useClientLookupPlugin.mjs";
|
|
29
30
|
import { useServerLookupPlugin } from "./useServerLookupPlugin.mjs";
|
|
@@ -74,17 +75,15 @@ export const redwoodPlugin = async (options = {}) => {
|
|
|
74
75
|
// context(justinvdm, 31 Mar 2025): We assume that if there is no .wrangler directory,
|
|
75
76
|
// then this is fresh install, and we run `npm run dev:init` here.
|
|
76
77
|
if (process.env.RWSDK_WORKER_RUN !== "1" &&
|
|
77
|
-
process.env.
|
|
78
|
+
process.env.NODE_ENV !== "production" &&
|
|
78
79
|
!(await pathExists(resolve(projectRootDir, ".wrangler"))) &&
|
|
79
80
|
(await hasPkgScript(projectRootDir, "dev:init"))) {
|
|
80
81
|
console.log("🚀 Project has no .wrangler directory yet, assuming fresh install: running `npm run dev:init`...");
|
|
81
|
-
await $
|
|
82
|
-
// context(justinvdm, 01 Apr 2025): We want to avoid interactive migration y/n prompt, so we ignore stdin
|
|
83
|
-
// as a signal to operate in no-tty mode
|
|
84
|
-
stdio: ["ignore", "inherit", "inherit"],
|
|
85
|
-
}) `npm run dev:init`;
|
|
82
|
+
await $ `npm run dev:init`;
|
|
86
83
|
}
|
|
87
84
|
return [
|
|
85
|
+
staleDepRetryPlugin(),
|
|
86
|
+
statePlugin({ projectRootDir }),
|
|
88
87
|
devServerTimingPlugin(),
|
|
89
88
|
devServerConstantPlugin(),
|
|
90
89
|
directiveModulesDevPlugin({
|
|
@@ -144,9 +143,6 @@ export const redwoodPlugin = async (options = {}) => {
|
|
|
144
143
|
clientEntryPoints,
|
|
145
144
|
projectRootDir,
|
|
146
145
|
}),
|
|
147
|
-
manifestPlugin({
|
|
148
|
-
projectRootDir,
|
|
149
|
-
}),
|
|
150
146
|
moveStaticAssetsPlugin({ rootDir: projectRootDir }),
|
|
151
147
|
prismaPlugin({ projectRootDir }),
|
|
152
148
|
linkerPlugin({ projectRootDir }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
2
|
import { compile } from "@mdx-js/mdx";
|
|
3
3
|
import debug from "debug";
|
|
4
|
+
import { glob } from "glob";
|
|
4
5
|
import fsp from "node:fs/promises";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { INTERMEDIATES_OUTPUT_DIR } from "../lib/constants.mjs";
|
|
@@ -17,6 +18,39 @@ const isObject = (value) => Object.prototype.toString.call(value) === "[object O
|
|
|
17
18
|
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/utils.ts
|
|
18
19
|
const externalRE = /^(https?:)?\/\//;
|
|
19
20
|
const isExternalUrl = (url) => externalRE.test(url);
|
|
21
|
+
async function findDirectiveRoots({ root, readFileWithCache, directiveCheckCache, }) {
|
|
22
|
+
const srcDir = path.resolve(root, "src");
|
|
23
|
+
const files = await glob("**/*.{ts,tsx,js,jsx,mjs,mts,cjs,cts,mdx}", {
|
|
24
|
+
cwd: srcDir,
|
|
25
|
+
absolute: true,
|
|
26
|
+
});
|
|
27
|
+
const directiveFiles = new Set();
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
if (directiveCheckCache.has(file)) {
|
|
30
|
+
if (directiveCheckCache.get(file)) {
|
|
31
|
+
directiveFiles.add(file);
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const content = await readFileWithCache(file);
|
|
37
|
+
const hasClient = hasDirective(content, "use client");
|
|
38
|
+
const hasServer = hasDirective(content, "use server");
|
|
39
|
+
const hasAnyDirective = hasClient || hasServer;
|
|
40
|
+
directiveCheckCache.set(file, hasAnyDirective);
|
|
41
|
+
if (hasAnyDirective) {
|
|
42
|
+
directiveFiles.add(file);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
log("Could not read file during pre-scan, skipping:", file);
|
|
47
|
+
// Cache the failure to avoid re-reading a problematic file
|
|
48
|
+
directiveCheckCache.set(file, false);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
log("Pre-scan found directive files:", Array.from(directiveFiles));
|
|
52
|
+
return directiveFiles;
|
|
53
|
+
}
|
|
20
54
|
export async function resolveModuleWithEnvironment({ path, importer, importerEnv, clientResolver, workerResolver, }) {
|
|
21
55
|
const resolver = importerEnv === "client" ? clientResolver : workerResolver;
|
|
22
56
|
return new Promise((resolvePromise) => {
|
|
@@ -56,6 +90,16 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
56
90
|
// Set environment variable to indicate scanning is in progress
|
|
57
91
|
process.env.RWSDK_DIRECTIVE_SCAN_ACTIVE = "true";
|
|
58
92
|
try {
|
|
93
|
+
const fileContentCache = new Map();
|
|
94
|
+
const directiveCheckCache = new Map();
|
|
95
|
+
const readFileWithCache = async (path) => {
|
|
96
|
+
if (fileContentCache.has(path)) {
|
|
97
|
+
return fileContentCache.get(path);
|
|
98
|
+
}
|
|
99
|
+
const contents = await fsp.readFile(path, "utf-8");
|
|
100
|
+
fileContentCache.set(path, contents);
|
|
101
|
+
return contents;
|
|
102
|
+
};
|
|
59
103
|
const esbuild = await getViteEsbuild(rootConfig.root);
|
|
60
104
|
const input = initialEntries ?? environments.worker.config.build.rollupOptions?.input;
|
|
61
105
|
let entries;
|
|
@@ -78,19 +122,19 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
78
122
|
// Filter out virtual modules since they can't be scanned by esbuild
|
|
79
123
|
const realEntries = entries.filter((entry) => !entry.includes("virtual:"));
|
|
80
124
|
const absoluteEntries = realEntries.map((entry) => path.resolve(rootConfig.root, entry));
|
|
81
|
-
|
|
125
|
+
const applicationDirectiveFiles = await findDirectiveRoots({
|
|
126
|
+
root: rootConfig.root,
|
|
127
|
+
readFileWithCache,
|
|
128
|
+
directiveCheckCache,
|
|
129
|
+
});
|
|
130
|
+
const combinedEntries = new Set([
|
|
131
|
+
...absoluteEntries,
|
|
132
|
+
...applicationDirectiveFiles,
|
|
133
|
+
]);
|
|
134
|
+
log("Starting directives scan with combined entries:", Array.from(combinedEntries));
|
|
82
135
|
const workerResolver = createViteAwareResolver(rootConfig, environments.worker);
|
|
83
136
|
const clientResolver = createViteAwareResolver(rootConfig, environments.client);
|
|
84
137
|
const moduleEnvironments = new Map();
|
|
85
|
-
const fileContentCache = new Map();
|
|
86
|
-
const readFileWithCache = async (path) => {
|
|
87
|
-
if (fileContentCache.has(path)) {
|
|
88
|
-
return fileContentCache.get(path);
|
|
89
|
-
}
|
|
90
|
-
const contents = await fsp.readFile(path, "utf-8");
|
|
91
|
-
fileContentCache.set(path, contents);
|
|
92
|
-
return contents;
|
|
93
|
-
};
|
|
94
138
|
const esbuildScanPlugin = {
|
|
95
139
|
name: "rwsdk:esbuild-scan-plugin",
|
|
96
140
|
setup(build) {
|
|
@@ -166,11 +210,11 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
166
210
|
});
|
|
167
211
|
build.onLoad({ filter: /\.(m|c)?[jt]sx?$|\.mdx$/ }, async (args) => {
|
|
168
212
|
log("onLoad called for:", args.path);
|
|
169
|
-
if (!args.path
|
|
213
|
+
if (!path.isAbsolute(args.path) ||
|
|
170
214
|
args.path.includes("virtual:") ||
|
|
171
215
|
isExternalUrl(args.path)) {
|
|
172
216
|
log("Skipping file due to filter:", args.path, {
|
|
173
|
-
|
|
217
|
+
isAbsolute: path.isAbsolute(args.path),
|
|
174
218
|
hasVirtual: args.path.includes("virtual:"),
|
|
175
219
|
isExternal: isExternalUrl(args.path),
|
|
176
220
|
});
|
|
@@ -232,7 +276,7 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
232
276
|
},
|
|
233
277
|
};
|
|
234
278
|
await esbuild.build({
|
|
235
|
-
entryPoints:
|
|
279
|
+
entryPoints: Array.from(combinedEntries),
|
|
236
280
|
bundle: true,
|
|
237
281
|
write: false,
|
|
238
282
|
outdir: path.join(INTERMEDIATES_OUTPUT_DIR, "directive-scan"),
|
|
@@ -254,7 +298,8 @@ export const runDirectivesScan = async ({ rootConfig, environments, clientFiles,
|
|
|
254
298
|
}
|
|
255
299
|
};
|
|
256
300
|
const deferredLog = (message) => {
|
|
301
|
+
const doLog = process.env.RWSDK_WORKER_RUN ? log : console.log;
|
|
257
302
|
setTimeout(() => {
|
|
258
|
-
|
|
303
|
+
doLog(message);
|
|
259
304
|
}, 500);
|
|
260
305
|
};
|