rwsdk 0.1.15 → 0.1.17
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/compileTsModule.d.mts +1 -0
- package/dist/lib/compileTsModule.mjs +27 -0
- package/dist/runtime/entries/navigation.d.ts +1 -0
- package/dist/runtime/entries/navigation.js +1 -0
- package/dist/runtime/imports/client.js +1 -1
- package/dist/runtime/imports/ssr.js +1 -1
- package/dist/runtime/imports/worker.js +1 -1
- package/dist/runtime/register/ssr.js +1 -1
- package/dist/runtime/render/injectRSCPayload.d.ts +3 -0
- package/dist/runtime/render/injectRSCPayload.js +79 -0
- package/dist/scripts/build-vendor-bundles.d.mts +1 -0
- package/dist/scripts/build-vendor-bundles.mjs +92 -0
- package/dist/vite/aliasByEnvPlugin.d.mts +2 -0
- package/dist/vite/aliasByEnvPlugin.mjs +11 -0
- package/dist/vite/asyncSetupPlugin.d.mts +6 -0
- package/dist/vite/asyncSetupPlugin.mjs +23 -0
- package/dist/vite/copyPrismaWasmPlugin.d.mts +4 -0
- package/dist/vite/copyPrismaWasmPlugin.mjs +32 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +4 -6
- package/dist/vite/customReactBuildPlugin.d.mts +4 -0
- package/dist/vite/customReactBuildPlugin.mjs +61 -0
- package/dist/vite/findSsrSpecifiers.mjs +16 -0
- package/dist/vite/injectHmrPreambleJsxPlugin.d.mts +2 -0
- package/dist/vite/injectHmrPreambleJsxPlugin.mjs +22 -0
- package/dist/vite/miniflareHMRPlugin.mjs +59 -21
- package/dist/vite/miniflarePlugin.d.mts +9 -0
- package/dist/vite/miniflarePlugin.mjs +135 -0
- package/dist/vite/requestUtils.d.mts +6 -0
- package/dist/vite/requestUtils.mjs +35 -0
- package/dist/vite/setupEnvFiles.d.mts +4 -0
- package/dist/vite/setupEnvFiles.mjs +31 -0
- package/dist/vite/ssrBridgePlugin.mjs +18 -5
- package/dist/vite/useClientPlugin.d.mts +8 -0
- package/dist/vite/useClientPlugin.mjs +295 -0
- package/dist/vite/useClientPlugin.test.d.mts +1 -0
- package/dist/vite/useClientPlugin.test.mjs +1204 -0
- package/dist/worker/__ssr_bridge.js +8947 -0
- package/dist/worker/__ssr_bridge.js.map +1 -0
- package/package.json +1 -1
- package/dist/vite/invalidateClientModule.d.mts +0 -2
- package/dist/vite/invalidateClientModule.mjs +0 -8
- package/dist/vite/invalidateModule copy.d.mts +0 -2
- package/dist/vite/invalidateModule copy.mjs +0 -14
- package/dist/vite/invalidateSSRModule.d.mts +0 -2
- package/dist/vite/invalidateSSRModule.mjs +0 -7
- package/dist/vite/isJsFile.d.ts +0 -1
- package/dist/vite/isJsFile.js +0 -3
- package/dist/vite/mode.d.mts +0 -5
- package/dist/vite/mode.mjs +0 -25
- package/dist/vite/modePlugin.d.mts +0 -2
- package/dist/vite/modePlugin.mjs +0 -10
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import colors from "picocolors";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { getShortName } from "../lib/getShortName.mjs";
|
|
6
|
+
import { pathExists } from "fs-extra";
|
|
7
|
+
const hasEntryAsAncestor = (module, entryFile, seen = new Set()) => {
|
|
8
|
+
// Prevent infinite recursion
|
|
9
|
+
if (seen.has(module))
|
|
10
|
+
return false;
|
|
11
|
+
seen.add(module);
|
|
12
|
+
// Check direct importers
|
|
13
|
+
for (const importer of module.importers) {
|
|
14
|
+
if (importer.file === entryFile)
|
|
15
|
+
return true;
|
|
16
|
+
// Recursively check importers
|
|
17
|
+
if (hasEntryAsAncestor(importer, entryFile, seen))
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
};
|
|
22
|
+
// Cache for "use client" status results
|
|
23
|
+
const useClientCache = new Map();
|
|
24
|
+
// Function to invalidate cache for a file
|
|
25
|
+
const invalidateUseClientCache = (file) => {
|
|
26
|
+
useClientCache.delete(file);
|
|
27
|
+
};
|
|
28
|
+
const isUseClientModule = async (ctx, file, seen = new Set()) => {
|
|
29
|
+
// Prevent infinite recursion
|
|
30
|
+
if (seen.has(file))
|
|
31
|
+
return false;
|
|
32
|
+
seen.add(file);
|
|
33
|
+
try {
|
|
34
|
+
// Check cache first
|
|
35
|
+
if (useClientCache.has(file)) {
|
|
36
|
+
return useClientCache.get(file);
|
|
37
|
+
}
|
|
38
|
+
// Read and check the file
|
|
39
|
+
const content = (await pathExists(file))
|
|
40
|
+
? await readFile(file, "utf-8")
|
|
41
|
+
: "";
|
|
42
|
+
const hasUseClient = content.includes("'use client'") || content.includes('"use client"');
|
|
43
|
+
if (hasUseClient) {
|
|
44
|
+
useClientCache.set(file, true);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
// Get the module from the module graph to find importers
|
|
48
|
+
const module = ctx.server.moduleGraph.getModuleById(file);
|
|
49
|
+
if (!module) {
|
|
50
|
+
useClientCache.set(file, false);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Check all importers recursively
|
|
54
|
+
for (const importer of module.importers) {
|
|
55
|
+
if (await isUseClientModule(ctx, importer.url, seen)) {
|
|
56
|
+
useClientCache.set(file, true);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
useClientCache.set(file, false);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
useClientCache.set(file, false);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
export const miniflarePlugin = (givenOptions) => [
|
|
69
|
+
cloudflare(givenOptions),
|
|
70
|
+
{
|
|
71
|
+
name: "rwsdk:miniflare-hmr",
|
|
72
|
+
async hotUpdate(ctx) {
|
|
73
|
+
const environment = givenOptions.viteEnvironment?.name ?? "worker";
|
|
74
|
+
const entry = givenOptions.workerEntryPathname;
|
|
75
|
+
if (!["client", environment].includes(this.environment.name)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// todo(justinvdm, 12 Dec 2024): Skip client references
|
|
79
|
+
const modules = Array.from(ctx.server.environments[environment].moduleGraph.getModulesByFile(ctx.file) ?? []);
|
|
80
|
+
const isWorkerUpdate = ctx.file === entry ||
|
|
81
|
+
modules.some((module) => hasEntryAsAncestor(module, entry));
|
|
82
|
+
// The worker doesnt need an update
|
|
83
|
+
// => Short circuit HMR
|
|
84
|
+
if (!isWorkerUpdate) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
// The worker needs an update, but this is the client environment
|
|
88
|
+
// => Notify for HMR update of any css files imported by in worker, that are also in the client module graph
|
|
89
|
+
// Why: There may have been changes to css classes referenced, which might css modules to change
|
|
90
|
+
if (this.environment.name === "client") {
|
|
91
|
+
const cssModules = [];
|
|
92
|
+
for (const [_, module] of ctx.server.environments[environment]
|
|
93
|
+
.moduleGraph.idToModuleMap) {
|
|
94
|
+
// todo(justinvdm, 13 Dec 2024): We check+update _all_ css files in worker module graph,
|
|
95
|
+
// but it could just be a subset of css files that are actually affected, depending
|
|
96
|
+
// on the importers and imports of the changed file. We should be smarter about this.
|
|
97
|
+
if (module.file && module.file.endsWith(".css")) {
|
|
98
|
+
const clientModules = ctx.server.environments.client.moduleGraph.getModulesByFile(module.file);
|
|
99
|
+
if (clientModules) {
|
|
100
|
+
cssModules.push(...clientModules.values());
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
invalidateUseClientCache(ctx.file);
|
|
105
|
+
return (await isUseClientModule(ctx, ctx.file))
|
|
106
|
+
? [...ctx.modules, ...cssModules]
|
|
107
|
+
: cssModules;
|
|
108
|
+
}
|
|
109
|
+
// The worker needs an update, and the hot check is for the worker environment
|
|
110
|
+
// => Notify for custom RSC-based HMR update, then short circuit HMR
|
|
111
|
+
if (isWorkerUpdate && this.environment.name === environment) {
|
|
112
|
+
const shortName = getShortName(ctx.file, ctx.server.config.root);
|
|
113
|
+
this.environment.logger.info(`${colors.green(`worker update`)} ${colors.dim(shortName)}`, {
|
|
114
|
+
clear: true,
|
|
115
|
+
timestamp: true,
|
|
116
|
+
});
|
|
117
|
+
const m = ctx.server.environments.client.moduleGraph
|
|
118
|
+
.getModulesByFile(resolve(givenOptions.rootDir, "src", "app", "style.css"))
|
|
119
|
+
?.values()
|
|
120
|
+
.next().value;
|
|
121
|
+
if (m) {
|
|
122
|
+
ctx.server.environments.client.moduleGraph.invalidateModule(m, new Set(), ctx.timestamp, true);
|
|
123
|
+
}
|
|
124
|
+
ctx.server.environments.client.hot.send({
|
|
125
|
+
type: "custom",
|
|
126
|
+
event: "rsc:update",
|
|
127
|
+
data: {
|
|
128
|
+
file: ctx.file,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Miniflare, type RequestInit } from "miniflare";
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
type MiniflareResponse = Awaited<ReturnType<typeof Miniflare.prototype.dispatchFetch>>;
|
|
4
|
+
export declare const nodeToWebRequest: (req: IncomingMessage) => Request & RequestInit;
|
|
5
|
+
export declare const webToNodeResponse: (webResponse: Response | MiniflareResponse, nodeResponse: ServerResponse) => Promise<void>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const nodeToWebRequest = (req) => {
|
|
2
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
3
|
+
return new Request(url.href, {
|
|
4
|
+
method: req.method,
|
|
5
|
+
headers: req.headers,
|
|
6
|
+
body: req.method !== "GET" && req.method !== "HEAD"
|
|
7
|
+
? req
|
|
8
|
+
: undefined,
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
duplex: "half",
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export const webToNodeResponse = async (webResponse, nodeResponse) => {
|
|
14
|
+
// Copy status and headers
|
|
15
|
+
nodeResponse.statusCode = webResponse.status;
|
|
16
|
+
webResponse.headers.forEach((value, key) => {
|
|
17
|
+
nodeResponse.setHeader(key, value);
|
|
18
|
+
});
|
|
19
|
+
// Stream the response
|
|
20
|
+
if (webResponse.body) {
|
|
21
|
+
const reader = webResponse.body.getReader();
|
|
22
|
+
try {
|
|
23
|
+
while (true) {
|
|
24
|
+
const { done, value } = await reader.read();
|
|
25
|
+
if (done)
|
|
26
|
+
break;
|
|
27
|
+
nodeResponse.write(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
reader.releaseLock();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
nodeResponse.end();
|
|
35
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { symlink, copyFile } from "node:fs/promises";
|
|
3
|
+
import { pathExists } from "fs-extra";
|
|
4
|
+
export async function setupEnvFiles({ rootDir, }) {
|
|
5
|
+
const envPath = resolve(rootDir, ".env");
|
|
6
|
+
const devVarsPath = resolve(rootDir, ".dev.vars");
|
|
7
|
+
const envExamplePath = resolve(rootDir, ".env.example");
|
|
8
|
+
const envExists = await pathExists(envPath);
|
|
9
|
+
const devVarsExists = await pathExists(devVarsPath);
|
|
10
|
+
const envExampleExists = await pathExists(envExamplePath);
|
|
11
|
+
// If .env.example exists but .env doesn't, copy from example to .env
|
|
12
|
+
if (!envExists && !devVarsExists && envExampleExists) {
|
|
13
|
+
try {
|
|
14
|
+
await copyFile(envExamplePath, envPath);
|
|
15
|
+
console.log("Created .env file from .env.example");
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.warn("Failed to copy .env.example to .env:", error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Create symlink from .env to .dev.vars if needed
|
|
22
|
+
if ((await pathExists(envPath)) && !devVarsExists) {
|
|
23
|
+
try {
|
|
24
|
+
await symlink(envPath, devVarsPath);
|
|
25
|
+
console.log("Created symlink from .env to .dev.vars");
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.warn("Failed to create symlink from .env to .dev.vars:", error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -93,22 +93,35 @@ export const ssrBridgePlugin = ({ clientFiles, serverFiles, }) => {
|
|
|
93
93
|
process.env.VERBOSE &&
|
|
94
94
|
log("Fetch module result: id=%s, result=%O", realId, result);
|
|
95
95
|
const code = "code" in result ? result.code : undefined;
|
|
96
|
+
if (realId.endsWith(".css")) {
|
|
97
|
+
process.env.VERBOSE &&
|
|
98
|
+
log("Not a JS file, returning code: %s", code);
|
|
99
|
+
return code ?? "";
|
|
100
|
+
}
|
|
96
101
|
log("Fetched SSR module code length: %d", code?.length || 0);
|
|
97
102
|
const { imports, dynamicImports } = findSsrImportSpecifiers(realId, code || "", log);
|
|
98
|
-
const allSpecifiers = [
|
|
103
|
+
const allSpecifiers = [
|
|
104
|
+
...new Set([...imports, ...dynamicImports]),
|
|
105
|
+
].map((id) => id.startsWith("/@id/") ? id.slice("/@id/".length) : id);
|
|
99
106
|
const switchCases = allSpecifiers
|
|
100
|
-
.map((specifier) => ` case "${specifier}":
|
|
107
|
+
.map((specifier) => ` case "${specifier}": void import("${VIRTUAL_SSR_PREFIX}${specifier}");`)
|
|
101
108
|
.join("\n");
|
|
102
109
|
const transformedCode = `
|
|
103
110
|
await (async function(__vite_ssr_import__, __vite_ssr_dynamic_import__) {${code}})(
|
|
104
|
-
(
|
|
105
|
-
(
|
|
111
|
+
__ssrImport.bind(null, false),
|
|
112
|
+
__ssrImport.bind(null, true)
|
|
106
113
|
);
|
|
107
114
|
|
|
108
|
-
function
|
|
115
|
+
function __ssrImport(isDynamic, id, ...args) {
|
|
116
|
+
id = id.startsWith('/@id/') ? id.slice('/@id/'.length) : id;
|
|
117
|
+
|
|
109
118
|
switch (id) {
|
|
110
119
|
${switchCases}
|
|
111
120
|
}
|
|
121
|
+
|
|
122
|
+
return isDynamic
|
|
123
|
+
? __vite_ssr_dynamic_import__("/@id/${VIRTUAL_SSR_PREFIX}" + id, ...args)
|
|
124
|
+
: __vite_ssr_import__("/@id/${VIRTUAL_SSR_PREFIX}" + id, ...args);
|
|
112
125
|
}
|
|
113
126
|
`;
|
|
114
127
|
log("Transformed SSR module code length: %d", transformedCode.length);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
interface TransformResult {
|
|
3
|
+
code: string;
|
|
4
|
+
map?: any;
|
|
5
|
+
}
|
|
6
|
+
export declare function transformUseClientCode(code: string, relativeId: string): Promise<TransformResult | undefined>;
|
|
7
|
+
export declare const useClientPlugin: () => Plugin;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { Project, Node, SyntaxKind, } from "ts-morph";
|
|
3
|
+
function isJsxFunction(text) {
|
|
4
|
+
return (text.includes("jsx(") || text.includes("jsxs(") || text.includes("jsxDEV("));
|
|
5
|
+
}
|
|
6
|
+
export async function transformUseClientCode(code, relativeId) {
|
|
7
|
+
const cleanCode = code.trimStart();
|
|
8
|
+
if (!cleanCode.startsWith('"use client"') &&
|
|
9
|
+
!cleanCode.startsWith("'use client'")) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const project = new Project({
|
|
13
|
+
useInMemoryFileSystem: true,
|
|
14
|
+
compilerOptions: {
|
|
15
|
+
sourceMap: true,
|
|
16
|
+
target: 2, // ES6
|
|
17
|
+
module: 1, // CommonJS
|
|
18
|
+
jsx: 2, // React
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const sourceFile = project.createSourceFile("temp.tsx", code);
|
|
22
|
+
// Add import declaration properly through the AST
|
|
23
|
+
sourceFile.addImportDeclaration({
|
|
24
|
+
moduleSpecifier: "rwsdk/worker",
|
|
25
|
+
namedImports: [{ name: "registerClientReference" }],
|
|
26
|
+
});
|
|
27
|
+
const components = new Map();
|
|
28
|
+
let anonymousDefaultCount = 0;
|
|
29
|
+
// Pass 1: Collect all component information
|
|
30
|
+
// Handle function declarations
|
|
31
|
+
sourceFile
|
|
32
|
+
.getDescendantsOfKind(SyntaxKind.FunctionDeclaration)
|
|
33
|
+
.forEach((node) => {
|
|
34
|
+
const name = node.getName() || `DefaultComponent${anonymousDefaultCount++}`;
|
|
35
|
+
if (!name)
|
|
36
|
+
return;
|
|
37
|
+
// Only track if it's a component (has JSX return)
|
|
38
|
+
if (isJsxFunction(node.getText())) {
|
|
39
|
+
const ssrName = `${name}SSR`;
|
|
40
|
+
const isInlineExport = node.hasModifier(SyntaxKind.ExportKeyword);
|
|
41
|
+
// Check if this function is used in a default export
|
|
42
|
+
const isDefault = node.hasModifier(SyntaxKind.DefaultKeyword) ||
|
|
43
|
+
sourceFile
|
|
44
|
+
.getDescendantsOfKind(SyntaxKind.ExportAssignment)
|
|
45
|
+
.some((exp) => exp.getExpression().getText() === name);
|
|
46
|
+
components.set(name, {
|
|
47
|
+
name,
|
|
48
|
+
ssrName,
|
|
49
|
+
isDefault,
|
|
50
|
+
isInlineExport,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// Handle arrow functions and anonymous default exports
|
|
55
|
+
sourceFile
|
|
56
|
+
.getDescendantsOfKind(SyntaxKind.VariableStatement)
|
|
57
|
+
.forEach((statement) => {
|
|
58
|
+
const declarations = statement.getDeclarationList().getDeclarations();
|
|
59
|
+
declarations.forEach((varDecl) => {
|
|
60
|
+
const arrowFunc = varDecl.getFirstDescendantByKind(SyntaxKind.ArrowFunction);
|
|
61
|
+
if (!arrowFunc)
|
|
62
|
+
return;
|
|
63
|
+
// Only track if it's a component (has JSX return)
|
|
64
|
+
if (isJsxFunction(arrowFunc.getText())) {
|
|
65
|
+
const name = varDecl.getName();
|
|
66
|
+
const isDefault = !!statement.getFirstAncestorByKind(SyntaxKind.ExportAssignment);
|
|
67
|
+
const isInlineExport = statement.hasModifier(SyntaxKind.ExportKeyword);
|
|
68
|
+
if (!name &&
|
|
69
|
+
(isDefault || statement.getText().includes("export default"))) {
|
|
70
|
+
// Handle anonymous default export
|
|
71
|
+
const anonName = `DefaultComponent${anonymousDefaultCount++}`;
|
|
72
|
+
components.set(anonName, {
|
|
73
|
+
name: anonName,
|
|
74
|
+
ssrName: anonName,
|
|
75
|
+
isDefault: true,
|
|
76
|
+
isInlineExport: true,
|
|
77
|
+
isAnonymousDefault: true,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else if (name) {
|
|
81
|
+
components.set(name, {
|
|
82
|
+
name,
|
|
83
|
+
ssrName: `${name}SSR`,
|
|
84
|
+
isDefault,
|
|
85
|
+
isInlineExport,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
// Pass 2: handle exports
|
|
92
|
+
// Remove use client directives
|
|
93
|
+
sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral).forEach((node) => {
|
|
94
|
+
if (node.getText() === "'use client'" ||
|
|
95
|
+
node.getText() === '"use client"') {
|
|
96
|
+
const parentExpr = node.getFirstAncestorByKind(SyntaxKind.ExpressionStatement);
|
|
97
|
+
if (parentExpr) {
|
|
98
|
+
parentExpr.remove();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// Create lists of nodes to modify before making any changes
|
|
103
|
+
const functionsToModify = [];
|
|
104
|
+
const variableStatementsToModify = [];
|
|
105
|
+
const exportDeclarationsToModify = [];
|
|
106
|
+
const exportAssignmentsToModify = [];
|
|
107
|
+
// Collect function declarations to modify
|
|
108
|
+
sourceFile
|
|
109
|
+
.getDescendantsOfKind(SyntaxKind.FunctionDeclaration)
|
|
110
|
+
.forEach((node) => {
|
|
111
|
+
const name = node.getName();
|
|
112
|
+
if (!name || !components.has(name))
|
|
113
|
+
return;
|
|
114
|
+
const component = components.get(name);
|
|
115
|
+
if (component.isInlineExport) {
|
|
116
|
+
functionsToModify.push({
|
|
117
|
+
node,
|
|
118
|
+
nodeText: node.getText(),
|
|
119
|
+
component,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Collect variable statements to modify
|
|
124
|
+
sourceFile
|
|
125
|
+
.getDescendantsOfKind(SyntaxKind.VariableStatement)
|
|
126
|
+
.forEach((statement) => {
|
|
127
|
+
if (!statement.hasModifier(SyntaxKind.ExportKeyword))
|
|
128
|
+
return;
|
|
129
|
+
const declarations = statement.getDeclarationList().getDeclarations();
|
|
130
|
+
let hasComponent = false;
|
|
131
|
+
declarations.forEach((varDecl) => {
|
|
132
|
+
const name = varDecl.getName();
|
|
133
|
+
if (name && components.has(name)) {
|
|
134
|
+
hasComponent = true;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
if (hasComponent) {
|
|
138
|
+
variableStatementsToModify.push({
|
|
139
|
+
node: statement,
|
|
140
|
+
stmtText: statement.getText(),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// Collect export declarations to modify
|
|
145
|
+
sourceFile
|
|
146
|
+
.getDescendantsOfKind(SyntaxKind.ExportDeclaration)
|
|
147
|
+
.forEach((node) => {
|
|
148
|
+
const namedExports = node.getNamedExports();
|
|
149
|
+
const nonComponentExports = namedExports.filter((exp) => !components.has(exp.getName()));
|
|
150
|
+
if (nonComponentExports.length !== namedExports.length) {
|
|
151
|
+
exportDeclarationsToModify.push({
|
|
152
|
+
node,
|
|
153
|
+
nonComponentExports,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// Collect export assignments to modify
|
|
158
|
+
sourceFile
|
|
159
|
+
.getDescendantsOfKind(SyntaxKind.ExportAssignment)
|
|
160
|
+
.forEach((node) => {
|
|
161
|
+
const expression = node.getExpression();
|
|
162
|
+
if (Node.isArrowFunction(expression)) {
|
|
163
|
+
exportAssignmentsToModify.push({
|
|
164
|
+
node,
|
|
165
|
+
expression,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
exportAssignmentsToModify.push({
|
|
170
|
+
node,
|
|
171
|
+
expression: null,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// Now apply all modifications in sequence to avoid operating on removed nodes
|
|
176
|
+
// Modify function declarations
|
|
177
|
+
functionsToModify.forEach(({ node, nodeText, component }) => {
|
|
178
|
+
const newText = nodeText.replace(/^export\s+(default\s+)?(async\s+)?function/, "$2function");
|
|
179
|
+
node.replaceWithText(newText);
|
|
180
|
+
});
|
|
181
|
+
// Modify variable statements
|
|
182
|
+
variableStatementsToModify.forEach(({ node, stmtText }) => {
|
|
183
|
+
const newText = stmtText.replace(/^export\s+/, "");
|
|
184
|
+
node.replaceWithText(newText);
|
|
185
|
+
});
|
|
186
|
+
// Modify export declarations
|
|
187
|
+
exportDeclarationsToModify.forEach(({ node, nonComponentExports }) => {
|
|
188
|
+
if (nonComponentExports.length === 0) {
|
|
189
|
+
// If all exports were components, remove the declaration
|
|
190
|
+
node.remove();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// If some exports were components, update the export declaration
|
|
194
|
+
const newExports = nonComponentExports
|
|
195
|
+
.map((exp) => exp.getText())
|
|
196
|
+
.join(", ");
|
|
197
|
+
node.replaceWithText(`export { ${newExports} };`);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// Handle export assignments with arrow functions
|
|
201
|
+
exportAssignmentsToModify.forEach(({ node, expression }) => {
|
|
202
|
+
if (expression && Node.isArrowFunction(expression)) {
|
|
203
|
+
const anonName = `DefaultComponent${anonymousDefaultCount++}`;
|
|
204
|
+
const ssrName = `${anonName}SSR`;
|
|
205
|
+
// First add declarations
|
|
206
|
+
sourceFile.addStatements(`const ${ssrName} = ${expression.getText()}`);
|
|
207
|
+
sourceFile.addStatements(`const ${anonName} = registerClientReference("${relativeId}", "default", ${ssrName});`);
|
|
208
|
+
// Store info for later export
|
|
209
|
+
components.set(anonName, {
|
|
210
|
+
name: anonName,
|
|
211
|
+
ssrName,
|
|
212
|
+
isDefault: true,
|
|
213
|
+
isInlineExport: true,
|
|
214
|
+
isAnonymousDefault: true,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// Remove the original export default node
|
|
218
|
+
node.remove();
|
|
219
|
+
});
|
|
220
|
+
// Pass 4: rename all identifiers to SSR version - collect first
|
|
221
|
+
const identifiersToRename = [];
|
|
222
|
+
components.forEach(({ name, ssrName, isAnonymousDefault }) => {
|
|
223
|
+
if (isAnonymousDefault)
|
|
224
|
+
return;
|
|
225
|
+
// Find function declarations by name
|
|
226
|
+
const funcDecls = sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration);
|
|
227
|
+
const funcNode = funcDecls.find((decl) => decl.getName() === name);
|
|
228
|
+
if (funcNode) {
|
|
229
|
+
identifiersToRename.push({ node: funcNode, newName: ssrName });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Find variable declarations by name
|
|
233
|
+
const varDecls = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
234
|
+
const varNode = varDecls.find((decl) => decl.getName() === name);
|
|
235
|
+
if (varNode) {
|
|
236
|
+
const identifier = varNode.getFirstChildByKind(SyntaxKind.Identifier);
|
|
237
|
+
if (identifier) {
|
|
238
|
+
identifiersToRename.push({ node: identifier, newName: ssrName });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
// Now apply the renames
|
|
243
|
+
identifiersToRename.forEach(({ node, newName }) => {
|
|
244
|
+
node.rename(newName);
|
|
245
|
+
});
|
|
246
|
+
// Pass 5: Add client reference registrations
|
|
247
|
+
// Add all declarations first
|
|
248
|
+
components.forEach(({ name, ssrName, isDefault, isAnonymousDefault }) => {
|
|
249
|
+
if (!isAnonymousDefault) {
|
|
250
|
+
sourceFile.addStatements(`const ${name} = registerClientReference("${relativeId}", "${isDefault ? "default" : name}", ${ssrName});`);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// Pass 6: add new exports
|
|
254
|
+
// Then add all exports after declarations
|
|
255
|
+
components.forEach(({ name, ssrName, isDefault }) => {
|
|
256
|
+
if (isDefault) {
|
|
257
|
+
// Export the registerClientReference version as default
|
|
258
|
+
sourceFile.addStatements(`export { ${name} as default, ${ssrName} };`);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
sourceFile.addStatements(`export { ${ssrName}, ${name} };`);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
// Clean up any remaining export assignments
|
|
265
|
+
sourceFile
|
|
266
|
+
.getDescendantsOfKind(SyntaxKind.ExportAssignment)
|
|
267
|
+
.forEach((node) => {
|
|
268
|
+
// If it's not an arrow function (which we handle separately),
|
|
269
|
+
// just remove the export assignment
|
|
270
|
+
node.remove();
|
|
271
|
+
});
|
|
272
|
+
const emitOutput = sourceFile.getEmitOutput();
|
|
273
|
+
let sourceMap;
|
|
274
|
+
for (const outputFile of emitOutput.getOutputFiles()) {
|
|
275
|
+
if (outputFile.getFilePath().endsWith(".js.map")) {
|
|
276
|
+
sourceMap = JSON.parse(outputFile.getText());
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
code: sourceFile.getFullText(),
|
|
281
|
+
map: sourceMap,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
export const useClientPlugin = () => ({
|
|
285
|
+
name: "rwsdk:use-client",
|
|
286
|
+
async transform(code, id) {
|
|
287
|
+
if (id.includes(".vite/deps") ||
|
|
288
|
+
id.includes("node_modules") ||
|
|
289
|
+
this.environment.name !== "worker") {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const relativeId = `/${relative(this.environment.getTopLevelConfig().root, id)}`;
|
|
293
|
+
return transformUseClientCode(code, relativeId);
|
|
294
|
+
},
|
|
295
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|