rwsdk 0.1.0-alpha.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/client.d.ts +3 -1
- package/dist/runtime/client.js +16 -13
- package/dist/runtime/clientNavigation.d.ts +3 -0
- package/dist/runtime/clientNavigation.js +43 -0
- package/dist/runtime/entries/client.d.ts +1 -0
- package/dist/runtime/entries/client.js +1 -0
- package/dist/runtime/entries/worker.d.ts +2 -0
- package/dist/runtime/entries/worker.js +2 -0
- package/dist/runtime/imports/ClientOnly.d.ts +3 -0
- package/dist/runtime/imports/ClientOnly.js +8 -0
- package/dist/runtime/imports/NoSSRStub.d.ts +1 -0
- package/dist/runtime/imports/NoSSRStub.js +4 -0
- package/dist/runtime/imports/client.js +15 -2
- package/dist/runtime/imports/worker.d.ts +1 -1
- package/dist/runtime/imports/worker.js +7 -5
- package/dist/runtime/lib/db/DOWorkerDialect.d.ts +29 -0
- package/dist/runtime/lib/db/DOWorkerDialect.js +66 -0
- package/dist/runtime/lib/db/SqliteDurableObject.d.ts +14 -0
- package/dist/runtime/lib/db/SqliteDurableObject.js +42 -0
- package/dist/runtime/lib/db/create.d.ts +3 -0
- package/dist/runtime/lib/db/create.js +36 -0
- package/dist/runtime/lib/db/createDb.d.ts +2 -0
- package/dist/runtime/lib/db/createDb.js +27 -0
- package/dist/runtime/lib/db/index.d.ts +3 -0
- package/dist/runtime/lib/db/index.js +3 -0
- package/dist/runtime/lib/db/logger.d.ts +2 -0
- package/dist/runtime/lib/db/logger.js +41 -0
- package/dist/runtime/lib/db/migrations.d.ts +23 -0
- package/dist/runtime/lib/db/migrations.js +34 -0
- package/dist/runtime/lib/db/types.d.ts +0 -0
- package/dist/runtime/lib/db/types.js +1 -0
- package/dist/runtime/lib/debug.d.ts +2 -0
- package/dist/runtime/lib/debug.js +36 -0
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +31 -26
- package/dist/runtime/lib/router.js +9 -2
- package/dist/runtime/register/ssr.d.ts +2 -0
- package/dist/runtime/register/ssr.js +14 -1
- package/dist/runtime/register/worker.d.ts +1 -1
- package/dist/runtime/register/worker.js +5 -2
- package/dist/runtime/render/renderRscThenableToHtmlStream.d.ts +2 -1
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +17 -3
- package/dist/runtime/render/renderToStream.d.ts +9 -0
- package/dist/runtime/render/renderToStream.js +26 -0
- package/dist/runtime/render/renderToString.d.ts +7 -0
- package/dist/runtime/render/renderToString.js +26 -0
- package/dist/runtime/render/transformRscToHtmlStream.js +1 -0
- package/dist/runtime/worker.d.ts +2 -1
- package/dist/runtime/worker.js +21 -14
- package/dist/scripts/debug-sync.mjs +8 -6
- package/dist/scripts/worker-run.mjs +1 -0
- package/dist/vite/configPlugin.mjs +8 -17
- package/dist/vite/createDirectiveLookupPlugin.d.mts +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +88 -49
- package/dist/vite/devServerTimingPlugin.d.mts +2 -0
- package/dist/vite/devServerTimingPlugin.mjs +24 -0
- package/dist/vite/directivesPlugin.mjs +168 -70
- package/dist/vite/findImportSpecifiers.d.mts +16 -0
- package/dist/vite/findImportSpecifiers.mjs +152 -0
- package/dist/vite/findImportSpecifiers.test.d.mts +1 -0
- package/dist/vite/findImportSpecifiers.test.mjs +73 -0
- package/dist/vite/findSpecifiers.d.mts +31 -0
- package/dist/vite/findSpecifiers.mjs +230 -0
- package/dist/vite/hasDirective.d.mts +7 -0
- package/dist/vite/hasDirective.mjs +54 -0
- package/dist/vite/hasOwnCloudflareVitePlugin.d.mts +3 -0
- package/dist/vite/hasOwnCloudflareVitePlugin.mjs +14 -0
- package/dist/vite/invalidateModule.d.mts +2 -0
- package/dist/vite/invalidateModule.mjs +14 -0
- package/dist/vite/miniflareHMRPlugin.d.mts +8 -0
- package/dist/vite/miniflareHMRPlugin.mjs +133 -0
- package/dist/vite/normalizeModulePath.mjs +12 -1
- package/dist/vite/redwoodPlugin.d.mts +1 -0
- package/dist/vite/redwoodPlugin.mjs +19 -4
- package/dist/vite/resolveModuleId.d.mts +6 -0
- package/dist/vite/resolveModuleId.mjs +14 -0
- package/dist/vite/ssrBridgePlugin.d.mts +5 -1
- package/dist/vite/ssrBridgePlugin.mjs +4 -43
- package/dist/vite/transformClientComponents.d.mts +1 -0
- package/dist/vite/transformClientComponents.mjs +61 -125
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +14 -3
- package/dist/vite/transformServerFunctions.d.mts +11 -3
- package/dist/vite/transformServerFunctions.mjs +256 -171
- package/dist/vite/transformServerFunctions.test.mjs +22 -3
- package/dist/vite/useClientLookupPlugin.mjs +1 -0
- package/dist/vite/useServerLookupPlugin.mjs +1 -0
- package/dist/vite/useServerPlugin.d.mts +1 -1
- package/dist/vite/useServerPlugin.mjs +1 -1
- package/package.json +14 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import colors from "picocolors";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { getShortName } from "../lib/getShortName.mjs";
|
|
5
|
+
import { pathExists } from "fs-extra";
|
|
6
|
+
const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => {
|
|
7
|
+
// Prevent infinite recursion
|
|
8
|
+
if (seen.has(module))
|
|
9
|
+
return false;
|
|
10
|
+
seen.add(module);
|
|
11
|
+
// Check direct importers
|
|
12
|
+
for (const importer of module.importers) {
|
|
13
|
+
if (importer.file === entryFile)
|
|
14
|
+
return true;
|
|
15
|
+
// Recursively check importers
|
|
16
|
+
if (hasEntryAsAncestor(importer, entryFile, seen))
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
// Cache for "use client" status results
|
|
22
|
+
const useClientCache = new Map();
|
|
23
|
+
// Function to invalidate cache for a file
|
|
24
|
+
const invalidateUseClientCache = (file) => {
|
|
25
|
+
useClientCache.delete(file);
|
|
26
|
+
};
|
|
27
|
+
const isUseClientModule = async (ctx, file, seen = new Set()) => {
|
|
28
|
+
// Prevent infinite recursion
|
|
29
|
+
if (seen.has(file))
|
|
30
|
+
return false;
|
|
31
|
+
seen.add(file);
|
|
32
|
+
try {
|
|
33
|
+
// Check cache first
|
|
34
|
+
if (useClientCache.has(file)) {
|
|
35
|
+
return useClientCache.get(file);
|
|
36
|
+
}
|
|
37
|
+
// Read and check the file
|
|
38
|
+
const content = (await pathExists(file))
|
|
39
|
+
? await readFile(file, "utf-8")
|
|
40
|
+
: "";
|
|
41
|
+
const hasUseClient = content.includes("'use client'") || content.includes('"use client"');
|
|
42
|
+
if (hasUseClient) {
|
|
43
|
+
useClientCache.set(file, true);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
// Get the module from the module graph to find importers
|
|
47
|
+
const module = ctx.server.moduleGraph.getModuleById(file);
|
|
48
|
+
if (!module) {
|
|
49
|
+
useClientCache.set(file, false);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Check all importers recursively
|
|
53
|
+
for (const importer of module.importers) {
|
|
54
|
+
if (await isUseClientModule(ctx, importer.url, seen)) {
|
|
55
|
+
useClientCache.set(file, true);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
useClientCache.set(file, false);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
useClientCache.set(file, false);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
export const miniflareHMRPlugin = (givenOptions) => [
|
|
68
|
+
{
|
|
69
|
+
name: "rwsdk:miniflare-hmr",
|
|
70
|
+
async hotUpdate(ctx) {
|
|
71
|
+
const environment = givenOptions.viteEnvironment.name;
|
|
72
|
+
const entry = givenOptions.workerEntryPathname;
|
|
73
|
+
if (!["client", environment].includes(this.environment.name)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// todo(justinvdm, 12 Dec 2024): Skip client references
|
|
77
|
+
const modules = Array.from(ctx.server.environments[environment].moduleGraph.getModulesByFile(ctx.file) ?? []);
|
|
78
|
+
const isWorkerUpdate = ctx.file === entry ||
|
|
79
|
+
modules.some((module) => hasEntryAsAncestor(module, entry));
|
|
80
|
+
// The worker doesnt need an update
|
|
81
|
+
// => Short circuit HMR
|
|
82
|
+
if (!isWorkerUpdate) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
// The worker needs an update, but this is the client environment
|
|
86
|
+
// => Notify for HMR update of any css files imported by in worker, that are also in the client module graph
|
|
87
|
+
// Why: There may have been changes to css classes referenced, which might css modules to change
|
|
88
|
+
if (this.environment.name === "client") {
|
|
89
|
+
const cssModules = [];
|
|
90
|
+
for (const [_, module] of ctx.server.environments[environment]
|
|
91
|
+
.moduleGraph.idToModuleMap) {
|
|
92
|
+
// todo(justinvdm, 13 Dec 2024): We check+update _all_ css files in worker module graph,
|
|
93
|
+
// but it could just be a subset of css files that are actually affected, depending
|
|
94
|
+
// on the importers and imports of the changed file. We should be smarter about this.
|
|
95
|
+
if (module.file && module.file.endsWith(".css")) {
|
|
96
|
+
const clientModules = ctx.server.environments.client.moduleGraph.getModulesByFile(module.file);
|
|
97
|
+
if (clientModules) {
|
|
98
|
+
cssModules.push(...clientModules.values());
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
invalidateUseClientCache(ctx.file);
|
|
103
|
+
return (await isUseClientModule(ctx, ctx.file))
|
|
104
|
+
? [...ctx.modules, ...cssModules]
|
|
105
|
+
: cssModules;
|
|
106
|
+
}
|
|
107
|
+
// The worker needs an update, and the hot check is for the worker environment
|
|
108
|
+
// => Notify for custom RSC-based HMR update, then short circuit HMR
|
|
109
|
+
if (isWorkerUpdate && this.environment.name === environment) {
|
|
110
|
+
const shortName = getShortName(ctx.file, ctx.server.config.root);
|
|
111
|
+
this.environment.logger.info(`${colors.green(`worker update`)} ${colors.dim(shortName)}`, {
|
|
112
|
+
clear: true,
|
|
113
|
+
timestamp: true,
|
|
114
|
+
});
|
|
115
|
+
const m = ctx.server.environments.client.moduleGraph
|
|
116
|
+
.getModulesByFile(resolve(givenOptions.rootDir, "src", "app", "style.css"))
|
|
117
|
+
?.values()
|
|
118
|
+
.next().value;
|
|
119
|
+
if (m) {
|
|
120
|
+
ctx.server.environments.client.moduleGraph.invalidateModule(m, new Set(), ctx.timestamp, true);
|
|
121
|
+
}
|
|
122
|
+
ctx.server.environments.client.hot.send({
|
|
123
|
+
type: "custom",
|
|
124
|
+
event: "rsc:update",
|
|
125
|
+
data: {
|
|
126
|
+
file: ctx.file,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
];
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
export const normalizeModulePath = (projectRootDir, modulePath) =>
|
|
2
|
+
export const normalizeModulePath = (projectRootDir, modulePath) => {
|
|
3
|
+
// /Users/path/to/project/src/foo/bar.ts -> /src/foo/bar.ts
|
|
4
|
+
if (modulePath.startsWith(projectRootDir)) {
|
|
5
|
+
return "/" + path.relative(projectRootDir, modulePath);
|
|
6
|
+
}
|
|
7
|
+
// /src/foo/bar.ts -> /src/foo/bar.ts
|
|
8
|
+
if (modulePath.startsWith("/")) {
|
|
9
|
+
return modulePath;
|
|
10
|
+
}
|
|
11
|
+
// src/foo/bar.ts -> /src/foo/bar.ts
|
|
12
|
+
return "/" + modulePath;
|
|
13
|
+
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
3
|
+
import { hasOwnCloudflareVitePlugin } from "./hasOwnCloudflareVitePlugin.mjs";
|
|
2
4
|
import reactPlugin from "@vitejs/plugin-react";
|
|
3
5
|
import tsconfigPaths from "vite-tsconfig-paths";
|
|
4
6
|
import { transformJsxScriptTagsPlugin } from "./transformJsxScriptTagsPlugin.mjs";
|
|
5
7
|
import { directivesPlugin } from "./directivesPlugin.mjs";
|
|
6
8
|
import { useClientLookupPlugin } from "./useClientLookupPlugin.mjs";
|
|
7
9
|
import { useServerLookupPlugin } from "./useServerLookupPlugin.mjs";
|
|
8
|
-
import {
|
|
10
|
+
import { miniflareHMRPlugin } from "./miniflareHMRPlugin.mjs";
|
|
9
11
|
import { moveStaticAssetsPlugin } from "./moveStaticAssetsPlugin.mjs";
|
|
10
12
|
import { configPlugin } from "./configPlugin.mjs";
|
|
11
13
|
import { $ } from "../lib/$.mjs";
|
|
@@ -17,6 +19,7 @@ import { vitePreamblePlugin } from "./vitePreamblePlugin.mjs";
|
|
|
17
19
|
import { prismaPlugin } from "./prismaPlugin.mjs";
|
|
18
20
|
import { ssrBridgePlugin } from "./ssrBridgePlugin.mjs";
|
|
19
21
|
import { hasPkgScript } from "../lib/hasPkgScript.mjs";
|
|
22
|
+
import { devServerTimingPlugin } from "./devServerTimingPlugin.mjs";
|
|
20
23
|
export const redwoodPlugin = async (options = {}) => {
|
|
21
24
|
const projectRootDir = process.cwd();
|
|
22
25
|
const mode = options.mode ??
|
|
@@ -25,6 +28,8 @@ export const redwoodPlugin = async (options = {}) => {
|
|
|
25
28
|
const workerEntryPathname = resolve(projectRootDir, options?.entry?.worker ?? "src/worker.tsx");
|
|
26
29
|
const clientFiles = new Set();
|
|
27
30
|
const serverFiles = new Set();
|
|
31
|
+
const shouldIncludeCloudflarePlugin = options.includeCloudflarePlugin ??
|
|
32
|
+
!(await hasOwnCloudflareVitePlugin({ rootProjectDir: projectRootDir }));
|
|
28
33
|
// context(justinvdm, 31 Mar 2025): We assume that if there is no .wrangler directory,
|
|
29
34
|
// then this is fresh install, and we run `npm run dev:init` here.
|
|
30
35
|
if (process.env.RWSDK_WORKER_RUN !== "1" &&
|
|
@@ -39,6 +44,7 @@ export const redwoodPlugin = async (options = {}) => {
|
|
|
39
44
|
}) `npm run dev:init`;
|
|
40
45
|
}
|
|
41
46
|
return [
|
|
47
|
+
devServerTimingPlugin(),
|
|
42
48
|
configPlugin({
|
|
43
49
|
mode,
|
|
44
50
|
silent: options.silent ?? false,
|
|
@@ -46,14 +52,23 @@ export const redwoodPlugin = async (options = {}) => {
|
|
|
46
52
|
clientEntryPathname,
|
|
47
53
|
workerEntryPathname,
|
|
48
54
|
}),
|
|
49
|
-
ssrBridgePlugin(
|
|
55
|
+
ssrBridgePlugin({
|
|
56
|
+
clientFiles,
|
|
57
|
+
serverFiles,
|
|
58
|
+
projectRootDir,
|
|
59
|
+
}),
|
|
50
60
|
reactConditionsResolverPlugin(),
|
|
51
61
|
tsconfigPaths({ root: projectRootDir }),
|
|
52
|
-
|
|
62
|
+
shouldIncludeCloudflarePlugin
|
|
63
|
+
? cloudflare({
|
|
64
|
+
viteEnvironment: { name: "worker" },
|
|
65
|
+
configPath: options.configPath ?? (await findWranglerConfig(projectRootDir)),
|
|
66
|
+
})
|
|
67
|
+
: [],
|
|
68
|
+
miniflareHMRPlugin({
|
|
53
69
|
rootDir: projectRootDir,
|
|
54
70
|
viteEnvironment: { name: "worker" },
|
|
55
71
|
workerEntryPathname,
|
|
56
|
-
configPath: options.configPath ?? (await findWranglerConfig(projectRootDir)),
|
|
57
72
|
}),
|
|
58
73
|
reactPlugin(),
|
|
59
74
|
directivesPlugin({
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a module ID consistently across client and server transformations.
|
|
3
|
+
* For node modules, uses the /rwsdk:kind/relativePath format.
|
|
4
|
+
* For regular files, uses the raw ID.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveModuleId(id: string, kind: "client" | "server", projectRootDir: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { normalizeModulePath } from "./normalizeModulePath.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a module ID consistently across client and server transformations.
|
|
4
|
+
* For node modules, uses the /rwsdk:kind/relativePath format.
|
|
5
|
+
* For regular files, uses the raw ID.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveModuleId(id, kind, projectRootDir) {
|
|
8
|
+
const modulePath = normalizeModulePath(projectRootDir, id);
|
|
9
|
+
return modulePath.includes("node_modules")
|
|
10
|
+
? modulePath.includes("__rwsdk_kind")
|
|
11
|
+
? modulePath
|
|
12
|
+
: `${modulePath}?__rwsdk_kind=${kind}`
|
|
13
|
+
: modulePath;
|
|
14
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
export declare const VIRTUAL_SSR_PREFIX = "virtual:rwsdk:ssr:";
|
|
3
|
-
export declare const ssrBridgePlugin: (
|
|
3
|
+
export declare const ssrBridgePlugin: ({ clientFiles, serverFiles, }: {
|
|
4
|
+
clientFiles: Set<string>;
|
|
5
|
+
serverFiles: Set<string>;
|
|
6
|
+
projectRootDir: string;
|
|
7
|
+
}) => Plugin;
|
|
@@ -1,40 +1,13 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
2
|
import { SSR_BRIDGE_PATH } from "../lib/constants.mjs";
|
|
3
|
+
import { invalidateModule } from "./invalidateModule.mjs";
|
|
3
4
|
const log = debug("rwsdk:vite:ssr-bridge-plugin");
|
|
4
5
|
const verboseLog = debug("verbose:rwsdk:vite:ssr-bridge-plugin");
|
|
5
6
|
export const VIRTUAL_SSR_PREFIX = "virtual:rwsdk:ssr:";
|
|
6
|
-
export const ssrBridgePlugin = () => {
|
|
7
|
+
export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
7
8
|
log("Initializing SSR bridge plugin with SSR_BRIDGE_PATH=%s", SSR_BRIDGE_PATH);
|
|
8
9
|
let devServer;
|
|
9
10
|
let isDev = false;
|
|
10
|
-
let promisedSSRWarmup;
|
|
11
|
-
const ensureWarmupSSRModules = async () => {
|
|
12
|
-
if (promisedSSRWarmup) {
|
|
13
|
-
log("SSR warmup already in progress");
|
|
14
|
-
return promisedSSRWarmup;
|
|
15
|
-
}
|
|
16
|
-
promisedSSRWarmup = doWarmupSSRModules();
|
|
17
|
-
return promisedSSRWarmup;
|
|
18
|
-
};
|
|
19
|
-
const doWarmupSSRModules = async () => {
|
|
20
|
-
log("Warming up SSR modules");
|
|
21
|
-
const files = [
|
|
22
|
-
"virtual:use-server-lookup",
|
|
23
|
-
"virtual:use-client-lookup",
|
|
24
|
-
"rwsdk/__ssr",
|
|
25
|
-
"rwsdk/__ssr_bridge",
|
|
26
|
-
];
|
|
27
|
-
for (const file of files) {
|
|
28
|
-
log("Warming up SSR file: %s", file);
|
|
29
|
-
await devServer.environments.ssr.warmupRequest(file);
|
|
30
|
-
log("Waiting for SSR requests to idle");
|
|
31
|
-
await devServer.environments.ssr.waitForRequestsIdle();
|
|
32
|
-
log("Deps optimizer scan processing");
|
|
33
|
-
await devServer.environments.ssr.depsOptimizer?.scanProcessing;
|
|
34
|
-
log("Deps optimizer scan processing complete");
|
|
35
|
-
}
|
|
36
|
-
log("SSR warmup complete");
|
|
37
|
-
};
|
|
38
11
|
const ssrBridgePlugin = {
|
|
39
12
|
name: "rwsdk:ssr-bridge",
|
|
40
13
|
enforce: "pre",
|
|
@@ -112,11 +85,10 @@ export const ssrBridgePlugin = () => {
|
|
|
112
85
|
verboseLog("Loading id=%s, isDev=%s, environment=%s", id, isDev, this.environment.name);
|
|
113
86
|
if (id.startsWith(VIRTUAL_SSR_PREFIX) &&
|
|
114
87
|
this.environment.name === "worker") {
|
|
115
|
-
await ensureWarmupSSRModules();
|
|
116
88
|
const realId = id.slice(VIRTUAL_SSR_PREFIX.length);
|
|
117
89
|
log("Virtual SSR module load: id=%s, realId=%s", id, realId);
|
|
118
90
|
if (isDev) {
|
|
119
|
-
log("Dev mode:
|
|
91
|
+
log("Dev mode: fetching SSR module for realPath=%s", realId);
|
|
120
92
|
const result = await devServer?.environments.ssr.fetchModule(realId);
|
|
121
93
|
verboseLog("Fetch module result: id=%s, result=%O", realId, result);
|
|
122
94
|
if (!result) {
|
|
@@ -129,7 +101,7 @@ export const ssrBridgePlugin = () => {
|
|
|
129
101
|
await (async function(__vite_ssr_import__, __vite_ssr_dynamic_import__) {${code}})((id) => __vite_ssr_import__('/@id/${VIRTUAL_SSR_PREFIX}'+id), (id) => __vite_ssr_dynamic_import__('/@id/${VIRTUAL_SSR_PREFIX}'+id));
|
|
130
102
|
`;
|
|
131
103
|
log("Transformed SSR module code length: %d", transformedCode.length);
|
|
132
|
-
|
|
104
|
+
verboseLog("Transformed SSR module code for realId=%s: %s", realId, transformedCode);
|
|
133
105
|
return transformedCode;
|
|
134
106
|
}
|
|
135
107
|
}
|
|
@@ -138,14 +110,3 @@ await (async function(__vite_ssr_import__, __vite_ssr_dynamic_import__) {${code}
|
|
|
138
110
|
};
|
|
139
111
|
return ssrBridgePlugin;
|
|
140
112
|
};
|
|
141
|
-
const invalidateModule = (devServer, environment, id) => {
|
|
142
|
-
const [rawId, _query] = id.split("?");
|
|
143
|
-
log("Invalidating module: id=%s, environment=%s", id, environment);
|
|
144
|
-
const moduleNode = devServer?.environments[environment]?.moduleGraph.idToModuleMap.get(rawId);
|
|
145
|
-
if (moduleNode) {
|
|
146
|
-
devServer?.environments[environment]?.moduleGraph.invalidateModule(moduleNode);
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
verboseLog("Module not found: id=%s, environment=%s", id, environment);
|
|
150
|
-
}
|
|
151
|
-
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import MagicString from "magic-string";
|
|
2
2
|
import debug from "debug";
|
|
3
|
+
import { hasDirective } from "./hasDirective.mjs";
|
|
4
|
+
import { findExports } from "./findSpecifiers.mjs";
|
|
3
5
|
const logVite = debug("rwsdk:vite:transform-client-components:vite");
|
|
4
6
|
const logEsbuild = debug("rwsdk:vite:transform-client-components:esbuild");
|
|
5
7
|
const verboseLogVite = debug("verbose:rwsdk:vite:transform-client-components:vite");
|
|
@@ -7,170 +9,104 @@ const verboseLogEsbuild = debug("verbose:rwsdk:vite:transform-client-components:
|
|
|
7
9
|
export async function transformClientComponents(code, normalizedId, ctx) {
|
|
8
10
|
const log = ctx.isEsbuild ? logEsbuild : logVite;
|
|
9
11
|
const verboseLog = ctx.isEsbuild ? verboseLogEsbuild : verboseLogVite;
|
|
10
|
-
log("Called transformClientComponents for id: id=%s
|
|
11
|
-
|
|
12
|
-
const emitOutput = sourceFile.getEmitOutput();
|
|
13
|
-
let sourceMap;
|
|
14
|
-
const outputFiles = emitOutput.getOutputFiles();
|
|
15
|
-
log(":isEsbuild=%s: EmitOutput files for %s (%s) - %d files:", !!ctx.isEsbuild, normalizedId, ctx.environmentName, outputFiles.length);
|
|
16
|
-
for (const outputFile of outputFiles) {
|
|
17
|
-
log(":isEsbuild=%s: - %s (%s)", !!ctx.isEsbuild, outputFile.getFilePath(), ctx.environmentName);
|
|
18
|
-
if (outputFile.getFilePath().endsWith(".js.map")) {
|
|
19
|
-
sourceMap = JSON.parse(outputFile.getText());
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return sourceMap;
|
|
23
|
-
}
|
|
24
|
-
// 2. Only transform files that start with 'use client'
|
|
25
|
-
const cleanCode = code.trimStart();
|
|
26
|
-
const hasUseClient = cleanCode.startsWith('"use client"') ||
|
|
27
|
-
cleanCode.startsWith("'use client'");
|
|
28
|
-
if (!hasUseClient) {
|
|
12
|
+
log("Called transformClientComponents for id: id=%s", normalizedId);
|
|
13
|
+
if (!hasDirective(code, "use client")) {
|
|
29
14
|
log("Skipping: no 'use client' directive in id=%s", normalizedId);
|
|
30
15
|
verboseLog(":VERBOSE: Returning code unchanged for id=%s:\n%s", normalizedId, code);
|
|
31
16
|
return;
|
|
32
17
|
}
|
|
33
18
|
log("Processing 'use client' module: id=%s", normalizedId);
|
|
34
|
-
ctx.
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
compilerOptions: {
|
|
39
|
-
sourceMap: true,
|
|
40
|
-
inlineSourceMap: false,
|
|
41
|
-
allowJs: true,
|
|
42
|
-
checkJs: true,
|
|
43
|
-
target: 2, // ES6
|
|
44
|
-
module: 1, // CommonJS
|
|
45
|
-
jsx: 2, // React
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
const sourceFile = project.createSourceFile(normalizedId + ".ts", code);
|
|
49
|
-
const exportInfos = [];
|
|
19
|
+
ctx.addClientModule?.(ctx.environmentName, normalizedId);
|
|
20
|
+
// Parse exports using the findExports helper
|
|
21
|
+
const exportInfos = findExports(normalizedId, code, verboseLog);
|
|
22
|
+
const processedExports = [];
|
|
50
23
|
let defaultExportInfo;
|
|
51
24
|
// Helper to get the computed local name (with alias suffix if present)
|
|
52
25
|
function getComputedLocalName(info) {
|
|
53
26
|
return `${info.local}${info.alias ? `_${info.alias}` : ""}`;
|
|
54
27
|
}
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
if (isDefault) {
|
|
58
|
-
defaultExportInfo = {
|
|
28
|
+
// Convert ExportInfo to ProcessedExportInfo
|
|
29
|
+
exportInfos.forEach((exportInfo, idx) => {
|
|
30
|
+
if (exportInfo.isDefault) {
|
|
31
|
+
defaultExportInfo = {
|
|
32
|
+
local: exportInfo.alias || "default",
|
|
33
|
+
exported: "default",
|
|
34
|
+
isDefault: true,
|
|
35
|
+
statementIdx: idx,
|
|
36
|
+
};
|
|
59
37
|
}
|
|
60
38
|
else {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// export default ... (assignment)
|
|
75
|
-
if (Node.isExportAssignment(stmt)) {
|
|
76
|
-
const expr = stmt.getExpression();
|
|
77
|
-
if (Node.isIdentifier(expr)) {
|
|
78
|
-
addExport(expr.getText(), "default", true, idx);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
addExport("default", "default", true, idx);
|
|
82
|
-
}
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
// export const foo = ...
|
|
86
|
-
if (Node.isVariableStatement(stmt) &&
|
|
87
|
-
stmt.hasModifier(SyntaxKind.ExportKeyword)) {
|
|
88
|
-
stmt
|
|
89
|
-
.getDeclarationList()
|
|
90
|
-
.getDeclarations()
|
|
91
|
-
.forEach((decl) => {
|
|
92
|
-
const name = decl.getName();
|
|
93
|
-
addExport(name, name, false, idx);
|
|
39
|
+
// For aliases like "export { MyComponent as CustomName }", we need:
|
|
40
|
+
// - local: "MyComponent" (the original name)
|
|
41
|
+
// - exported: "CustomName" (the alias name)
|
|
42
|
+
// - alias: "CustomName" (to generate MyComponent_CustomName)
|
|
43
|
+
const hasAlias = exportInfo.alias && exportInfo.originalName;
|
|
44
|
+
processedExports.push({
|
|
45
|
+
local: exportInfo.originalName || exportInfo.name, // Use originalName if available
|
|
46
|
+
exported: exportInfo.name, // The exported name (alias if present)
|
|
47
|
+
isDefault: false,
|
|
48
|
+
statementIdx: idx,
|
|
49
|
+
alias: hasAlias ? exportInfo.alias : undefined,
|
|
94
50
|
});
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
// export function foo() ...
|
|
98
|
-
if (Node.isFunctionDeclaration(stmt) &&
|
|
99
|
-
stmt.hasModifier(SyntaxKind.ExportKeyword)) {
|
|
100
|
-
if (!stmt.hasModifier(SyntaxKind.DefaultKeyword)) {
|
|
101
|
-
const name = stmt.getName();
|
|
102
|
-
if (name) {
|
|
103
|
-
addExport(name, name, false, idx);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
// export { ... } or export { ... } from ...
|
|
109
|
-
if (Node.isExportDeclaration(stmt)) {
|
|
110
|
-
const namedExports = stmt.getNamedExports();
|
|
111
|
-
if (namedExports.length > 0) {
|
|
112
|
-
namedExports.forEach((exp) => {
|
|
113
|
-
const alias = exp.getAliasNode()?.getText();
|
|
114
|
-
const local = alias ? exp.getNameNode().getText() : exp.getName();
|
|
115
|
-
const exported = alias ? alias : exp.getName();
|
|
116
|
-
addExport(local, exported, exported === "default", idx, alias);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return;
|
|
120
51
|
}
|
|
121
52
|
});
|
|
122
53
|
// 3. Client/SSR files: just remove the directive
|
|
123
54
|
if (ctx.environmentName === "ssr" || ctx.environmentName === "client") {
|
|
124
55
|
log(":isEsbuild=%s: Handling SSR virtual module: %s", !!ctx.isEsbuild, normalizedId);
|
|
125
|
-
// Remove 'use client' directive using
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
56
|
+
// Remove 'use client' directive using magic-string
|
|
57
|
+
const s = new MagicString(code);
|
|
58
|
+
// Find and remove "use client" directives
|
|
59
|
+
const directiveRegex = /^(\s*)(['"]use client['"])\s*;?\s*\n?/gm;
|
|
60
|
+
let match;
|
|
61
|
+
while ((match = directiveRegex.exec(code)) !== null) {
|
|
62
|
+
const start = match.index;
|
|
63
|
+
const end = match.index + match[0].length;
|
|
64
|
+
s.remove(start, end);
|
|
65
|
+
verboseLog("Removed 'use client' directive from normalizedId=%s", normalizedId);
|
|
66
|
+
break; // Only remove the first one
|
|
67
|
+
}
|
|
68
|
+
const sourceMap = s.generateMap({
|
|
69
|
+
source: normalizedId,
|
|
70
|
+
includeContent: true,
|
|
71
|
+
hires: true,
|
|
136
72
|
});
|
|
137
|
-
|
|
138
|
-
verboseLog(":VERBOSE: SSR transformed code for %s:\n%s", normalizedId, sourceFile.getFullText());
|
|
73
|
+
verboseLog(":VERBOSE: SSR transformed code for %s:\n%s", normalizedId, s.toString());
|
|
139
74
|
return {
|
|
140
|
-
code:
|
|
75
|
+
code: s.toString(),
|
|
141
76
|
map: sourceMap,
|
|
142
77
|
};
|
|
143
78
|
}
|
|
144
79
|
// 4. Non-SSR files: replace all implementation with registerClientReference logic
|
|
145
|
-
//
|
|
146
|
-
|
|
80
|
+
// Generate completely new code for worker/client environments
|
|
81
|
+
const s = new MagicString("");
|
|
147
82
|
// Add import declaration
|
|
148
|
-
|
|
149
|
-
moduleSpecifier: "rwsdk/worker",
|
|
150
|
-
namedImports: [{ name: "registerClientReference" }],
|
|
151
|
-
});
|
|
83
|
+
s.append('import { registerClientReference } from "rwsdk/worker";\n');
|
|
152
84
|
// Compute unique computed local names first
|
|
153
|
-
const computedLocalNames = new Map(
|
|
85
|
+
const computedLocalNames = new Map(processedExports.map((info) => [getComputedLocalName(info), info]));
|
|
154
86
|
// Add registerClientReference assignments for unique names
|
|
155
87
|
for (const [computedLocalName, correspondingInfo] of computedLocalNames) {
|
|
156
88
|
log(":isEsbuild=%s: Registering client reference for named export: %s as %s", !!ctx.isEsbuild, correspondingInfo.local, correspondingInfo.exported);
|
|
157
|
-
|
|
89
|
+
s.append(`const ${computedLocalName} = registerClientReference("${normalizedId}", "${correspondingInfo.exported}");\n`);
|
|
158
90
|
}
|
|
159
91
|
// Add grouped export statement for named exports (preserving order and alias)
|
|
160
|
-
if (
|
|
92
|
+
if (processedExports.length > 0) {
|
|
161
93
|
const exportNames = Array.from(computedLocalNames.entries()).map(([computedLocalName, correspondingInfo]) => correspondingInfo.local === correspondingInfo.exported
|
|
162
94
|
? computedLocalName
|
|
163
95
|
: `${computedLocalName} as ${correspondingInfo.exported}`);
|
|
164
96
|
log(":isEsbuild=%s: Exporting named exports: %O", !!ctx.isEsbuild, exportNames);
|
|
165
|
-
|
|
97
|
+
s.append(`export { ${exportNames.join(", ")} };\n`);
|
|
166
98
|
}
|
|
167
99
|
// Add default export if present
|
|
168
100
|
if (defaultExportInfo) {
|
|
169
101
|
log(":isEsbuild=%s: Registering client reference for default export: %s", !!ctx.isEsbuild, defaultExportInfo.exported);
|
|
170
|
-
|
|
102
|
+
s.append(`export default registerClientReference("${normalizedId}", "${defaultExportInfo.exported}");\n`);
|
|
171
103
|
}
|
|
172
|
-
const sourceMap =
|
|
173
|
-
|
|
104
|
+
const sourceMap = s.generateMap({
|
|
105
|
+
source: normalizedId,
|
|
106
|
+
includeContent: true,
|
|
107
|
+
hires: true,
|
|
108
|
+
});
|
|
109
|
+
const finalResult = s.toString();
|
|
174
110
|
verboseLog(":VERBOSE: Transformed code (env=%s, normalizedId=%s):\n%s", normalizedId, ctx.environmentName, finalResult);
|
|
175
111
|
return {
|
|
176
112
|
code: finalResult,
|
|
@@ -12,9 +12,19 @@ const readManifest = async (manifestPath) => {
|
|
|
12
12
|
}
|
|
13
13
|
return manifestCache;
|
|
14
14
|
};
|
|
15
|
-
// Check if a string includes any jsx function calls
|
|
16
15
|
function hasJsxFunctions(text) {
|
|
17
|
-
return (text.includes(
|
|
16
|
+
return (text.includes('jsx("script"') ||
|
|
17
|
+
text.includes("jsx('script'") ||
|
|
18
|
+
text.includes('jsx("link"') ||
|
|
19
|
+
text.includes("jsx('link'") ||
|
|
20
|
+
text.includes('jsxs("script"') ||
|
|
21
|
+
text.includes("jsxs('script'") ||
|
|
22
|
+
text.includes('jsxs("link"') ||
|
|
23
|
+
text.includes("jsxs('link'") ||
|
|
24
|
+
text.includes('jsxDEV("script"') ||
|
|
25
|
+
text.includes("jsxDEV('script'") ||
|
|
26
|
+
text.includes('jsxDEV("link"') ||
|
|
27
|
+
text.includes("jsxDEV('link'"));
|
|
18
28
|
}
|
|
19
29
|
// Transform import statements in script content using ts-morph
|
|
20
30
|
function transformScriptImports(scriptContent, manifest) {
|
|
@@ -68,7 +78,8 @@ function transformScriptImports(scriptContent, manifest) {
|
|
|
68
78
|
}
|
|
69
79
|
}
|
|
70
80
|
export async function transformJsxScriptTagsCode(code, manifest = {}) {
|
|
71
|
-
//
|
|
81
|
+
// context(justinvdm, 15 Jun 2025): Optimization to exit early
|
|
82
|
+
// to avoidunnecessary ts-morph parsing
|
|
72
83
|
if (!hasJsxFunctions(code)) {
|
|
73
84
|
return;
|
|
74
85
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { SourceFile } from "ts-morph";
|
|
2
1
|
interface TransformResult {
|
|
3
2
|
code: string;
|
|
4
3
|
map?: any;
|
|
5
4
|
}
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
type ExportInfoCompat = {
|
|
6
|
+
localFunctions: Set<string>;
|
|
7
|
+
reExports: Array<{
|
|
8
|
+
localName: string;
|
|
9
|
+
originalName: string;
|
|
10
|
+
moduleSpecifier: string;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
export declare const findExportedFunctions: (code: string, normalizedId?: string) => Set<string>;
|
|
14
|
+
export declare const findExportInfo: (code: string, normalizedId?: string) => ExportInfoCompat;
|
|
15
|
+
export declare const transformServerFunctions: (code: string, normalizedId: string, environment: "client" | "worker" | "ssr", serverFiles?: Set<string>, addServerModule?: (environment: string, id: string) => void) => TransformResult | undefined;
|
|
8
16
|
export type { TransformResult };
|