@vistagenic/vista 0.1.0-alpha.1
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/bin/vista.js +98 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.js +16 -0
- package/dist/bin/build-rsc.d.ts +17 -0
- package/dist/bin/build-rsc.js +320 -0
- package/dist/bin/build.d.ts +4 -0
- package/dist/bin/build.js +336 -0
- package/dist/bin/file-scanner.d.ts +66 -0
- package/dist/bin/file-scanner.js +399 -0
- package/dist/bin/server-component-plugin.d.ts +17 -0
- package/dist/bin/server-component-plugin.js +133 -0
- package/dist/bin/webpack.config.d.ts +6 -0
- package/dist/bin/webpack.config.js +138 -0
- package/dist/build/manifest.d.ts +95 -0
- package/dist/build/manifest.js +168 -0
- package/dist/build/rsc/client-manifest.d.ts +48 -0
- package/dist/build/rsc/client-manifest.js +191 -0
- package/dist/build/rsc/client-reference-plugin.d.ts +37 -0
- package/dist/build/rsc/client-reference-plugin.js +185 -0
- package/dist/build/rsc/compiler.d.ts +36 -0
- package/dist/build/rsc/compiler.js +311 -0
- package/dist/build/rsc/index.d.ts +16 -0
- package/dist/build/rsc/index.js +32 -0
- package/dist/build/rsc/native-scanner.d.ts +123 -0
- package/dist/build/rsc/native-scanner.js +165 -0
- package/dist/build/rsc/rsc-renderer.d.ts +99 -0
- package/dist/build/rsc/rsc-renderer.js +269 -0
- package/dist/build/rsc/server-component-loader.d.ts +19 -0
- package/dist/build/rsc/server-component-loader.js +147 -0
- package/dist/build/rsc/server-manifest.d.ts +63 -0
- package/dist/build/rsc/server-manifest.js +268 -0
- package/dist/build/webpack/loaders/vista-flight-loader.d.ts +17 -0
- package/dist/build/webpack/loaders/vista-flight-loader.js +93 -0
- package/dist/build/webpack/plugins/vista-flight-plugin.d.ts +36 -0
- package/dist/build/webpack/plugins/vista-flight-plugin.js +133 -0
- package/dist/client/dynamic.d.ts +25 -0
- package/dist/client/dynamic.js +68 -0
- package/dist/client/font.d.ts +98 -0
- package/dist/client/font.js +109 -0
- package/dist/client/head.d.ts +79 -0
- package/dist/client/head.js +261 -0
- package/dist/client/hydration.d.ts +45 -0
- package/dist/client/hydration.js +291 -0
- package/dist/client/link.d.ts +30 -0
- package/dist/client/link.js +188 -0
- package/dist/client/navigation.d.ts +28 -0
- package/dist/client/navigation.js +116 -0
- package/dist/client/router.d.ts +41 -0
- package/dist/client/router.js +190 -0
- package/dist/client/script.d.ts +51 -0
- package/dist/client/script.js +118 -0
- package/dist/components/client-island.d.ts +34 -0
- package/dist/components/client-island.js +75 -0
- package/dist/components/client.d.ts +29 -0
- package/dist/components/client.js +102 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +8 -0
- package/dist/components/link.d.ts +6 -0
- package/dist/components/link.js +13 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +31 -0
- package/dist/dev-error.d.ts +35 -0
- package/dist/dev-error.js +310 -0
- package/dist/image/get-img-props.d.ts +28 -0
- package/dist/image/get-img-props.js +49 -0
- package/dist/image/image-config.d.ts +20 -0
- package/dist/image/image-config.js +20 -0
- package/dist/image/image-loader.d.ts +7 -0
- package/dist/image/image-loader.js +14 -0
- package/dist/image/index.d.ts +6 -0
- package/dist/image/index.js +110 -0
- package/dist/image.d.ts +10 -0
- package/dist/image.js +7 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +53 -0
- package/dist/metadata/generate.d.ts +22 -0
- package/dist/metadata/generate.js +324 -0
- package/dist/metadata/index.d.ts +7 -0
- package/dist/metadata/index.js +26 -0
- package/dist/metadata/types.d.ts +325 -0
- package/dist/metadata/types.js +15 -0
- package/dist/router/context.d.ts +8 -0
- package/dist/router/context.js +13 -0
- package/dist/router/index.d.ts +2 -0
- package/dist/router/index.js +18 -0
- package/dist/router/provider.d.ts +5 -0
- package/dist/router/provider.js +31 -0
- package/dist/server/client-boundary.d.ts +48 -0
- package/dist/server/client-boundary.js +133 -0
- package/dist/server/engine.d.ts +4 -0
- package/dist/server/engine.js +651 -0
- package/dist/server/index.d.ts +95 -0
- package/dist/server/index.js +177 -0
- package/dist/server/rsc-engine.d.ts +20 -0
- package/dist/server/rsc-engine.js +588 -0
- package/dist/server/rsc-module-system.d.ts +33 -0
- package/dist/server/rsc-module-system.js +119 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +2 -0
- package/package.json +103 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vista RSC Native Scanner
|
|
3
|
+
*
|
|
4
|
+
* Uses Rust-powered native bindings for blazing fast component scanning.
|
|
5
|
+
* Falls back to TypeScript implementation if native module unavailable.
|
|
6
|
+
*/
|
|
7
|
+
export interface NapiScannedComponent {
|
|
8
|
+
absolutePath: string;
|
|
9
|
+
relativePath: string;
|
|
10
|
+
isClient: boolean;
|
|
11
|
+
directiveLine: number;
|
|
12
|
+
componentType: string;
|
|
13
|
+
exports: string[];
|
|
14
|
+
clientHooksUsed: string[];
|
|
15
|
+
hasMetadata: boolean;
|
|
16
|
+
hasGenerateMetadata: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface NapiServerComponentError {
|
|
19
|
+
file: string;
|
|
20
|
+
message: string;
|
|
21
|
+
hooks: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface NapiScanResult {
|
|
24
|
+
clientComponents: NapiScannedComponent[];
|
|
25
|
+
serverComponents: NapiScannedComponent[];
|
|
26
|
+
pages: NapiScannedComponent[];
|
|
27
|
+
layouts: NapiScannedComponent[];
|
|
28
|
+
apiRoutes: NapiScannedComponent[];
|
|
29
|
+
errors: NapiServerComponentError[];
|
|
30
|
+
totalFiles: number;
|
|
31
|
+
scanTimeMs: number;
|
|
32
|
+
}
|
|
33
|
+
export interface NapiClientModuleEntry {
|
|
34
|
+
id: string;
|
|
35
|
+
path: string;
|
|
36
|
+
absolutePath: string;
|
|
37
|
+
chunkName: string;
|
|
38
|
+
exports: string[];
|
|
39
|
+
asyncLoad: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface NapiClientManifest {
|
|
42
|
+
buildId: string;
|
|
43
|
+
clientModules: NapiClientModuleEntry[];
|
|
44
|
+
}
|
|
45
|
+
export interface NapiRouteEntry {
|
|
46
|
+
pattern: string;
|
|
47
|
+
pagePath: string;
|
|
48
|
+
layoutPaths: string[];
|
|
49
|
+
loadingPath: string | null;
|
|
50
|
+
errorPath: string | null;
|
|
51
|
+
routeType: string;
|
|
52
|
+
}
|
|
53
|
+
export interface NapiServerModuleEntry {
|
|
54
|
+
id: string;
|
|
55
|
+
path: string;
|
|
56
|
+
absolutePath: string;
|
|
57
|
+
componentType: string;
|
|
58
|
+
hasMetadata: boolean;
|
|
59
|
+
hasGenerateMetadata: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface NapiServerManifest {
|
|
62
|
+
buildId: string;
|
|
63
|
+
serverModules: NapiServerModuleEntry[];
|
|
64
|
+
routes: NapiRouteEntry[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if native module is available
|
|
68
|
+
*/
|
|
69
|
+
export declare function isNativeAvailable(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Scan app directory using Rust native code
|
|
72
|
+
* Returns null if native module is not available
|
|
73
|
+
*/
|
|
74
|
+
export declare function scanAppNative(appDir: string): NapiScanResult | null;
|
|
75
|
+
/**
|
|
76
|
+
* Generate client manifest using Rust native code
|
|
77
|
+
*/
|
|
78
|
+
export declare function generateClientManifestNative(appDir: string, buildId: string): NapiClientManifest | null;
|
|
79
|
+
/**
|
|
80
|
+
* Generate server manifest using Rust native code
|
|
81
|
+
*/
|
|
82
|
+
export declare function generateServerManifestNative(appDir: string, buildId: string): NapiServerManifest | null;
|
|
83
|
+
/**
|
|
84
|
+
* Generate unique mount ID for client component
|
|
85
|
+
*/
|
|
86
|
+
export declare function generateMountIdNative(): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Reset mount ID counter (call at start of each request)
|
|
89
|
+
*/
|
|
90
|
+
export declare function resetMountCounterNative(): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Convert NAPI scan result to internal format
|
|
93
|
+
*/
|
|
94
|
+
export declare function convertScanResult(result: NapiScanResult): {
|
|
95
|
+
clientComponents: {
|
|
96
|
+
absolutePath: string;
|
|
97
|
+
relativePath: string;
|
|
98
|
+
isClient: boolean;
|
|
99
|
+
directiveLine: number;
|
|
100
|
+
componentType: string;
|
|
101
|
+
exports: string[];
|
|
102
|
+
clientHooksUsed: string[];
|
|
103
|
+
hasMetadata: boolean;
|
|
104
|
+
hasGenerateMetadata: boolean;
|
|
105
|
+
}[];
|
|
106
|
+
serverComponents: {
|
|
107
|
+
absolutePath: string;
|
|
108
|
+
relativePath: string;
|
|
109
|
+
isClient: boolean;
|
|
110
|
+
directiveLine: number;
|
|
111
|
+
componentType: string;
|
|
112
|
+
exports: string[];
|
|
113
|
+
clientHooksUsed: any[];
|
|
114
|
+
hasMetadata: boolean;
|
|
115
|
+
hasGenerateMetadata: boolean;
|
|
116
|
+
}[];
|
|
117
|
+
pages: NapiScannedComponent[];
|
|
118
|
+
layouts: NapiScannedComponent[];
|
|
119
|
+
apiRoutes: NapiScannedComponent[];
|
|
120
|
+
errors: NapiServerComponentError[];
|
|
121
|
+
totalFiles: number;
|
|
122
|
+
scanTimeMs: number;
|
|
123
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vista RSC Native Scanner
|
|
4
|
+
*
|
|
5
|
+
* Uses Rust-powered native bindings for blazing fast component scanning.
|
|
6
|
+
* Falls back to TypeScript implementation if native module unavailable.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isNativeAvailable = isNativeAvailable;
|
|
13
|
+
exports.scanAppNative = scanAppNative;
|
|
14
|
+
exports.generateClientManifestNative = generateClientManifestNative;
|
|
15
|
+
exports.generateServerManifestNative = generateServerManifestNative;
|
|
16
|
+
exports.generateMountIdNative = generateMountIdNative;
|
|
17
|
+
exports.resetMountCounterNative = resetMountCounterNative;
|
|
18
|
+
exports.convertScanResult = convertScanResult;
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
20
|
+
let nativeModule = null;
|
|
21
|
+
let nativeLoadError = null;
|
|
22
|
+
/**
|
|
23
|
+
* Try to load the native Rust module
|
|
24
|
+
*/
|
|
25
|
+
function loadNativeModule() {
|
|
26
|
+
if (nativeModule !== null)
|
|
27
|
+
return nativeModule;
|
|
28
|
+
if (nativeLoadError !== null)
|
|
29
|
+
return null;
|
|
30
|
+
// Try multiple paths since we might be running from src or dist
|
|
31
|
+
const possiblePaths = [
|
|
32
|
+
// From compiled dist/build/rsc/native-scanner.js
|
|
33
|
+
path_1.default.resolve(__dirname, '../../../../../crates/vista-napi'),
|
|
34
|
+
// From source src/build/rsc/native-scanner.ts
|
|
35
|
+
path_1.default.resolve(__dirname, '../../../../crates/vista-napi'),
|
|
36
|
+
// From workspace root
|
|
37
|
+
path_1.default.resolve(process.cwd(), '../crates/vista-napi'),
|
|
38
|
+
// Try @aspect-build/vista-napi as npm package
|
|
39
|
+
'@aspect-build/vista-napi',
|
|
40
|
+
];
|
|
41
|
+
for (const modulePath of possiblePaths) {
|
|
42
|
+
try {
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
|
+
nativeModule = require(modulePath);
|
|
45
|
+
console.log('[Vista RSC] Using Rust-powered scanner');
|
|
46
|
+
return nativeModule;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Continue trying other paths
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
nativeLoadError = new Error('Native module not found');
|
|
53
|
+
console.warn('⚠ Native module unavailable, using TypeScript fallback');
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if native module is available
|
|
58
|
+
*/
|
|
59
|
+
function isNativeAvailable() {
|
|
60
|
+
return loadNativeModule() !== null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Scan app directory using Rust native code
|
|
64
|
+
* Returns null if native module is not available
|
|
65
|
+
*/
|
|
66
|
+
function scanAppNative(appDir) {
|
|
67
|
+
const native = loadNativeModule();
|
|
68
|
+
if (!native)
|
|
69
|
+
return null;
|
|
70
|
+
try {
|
|
71
|
+
const startTime = performance.now();
|
|
72
|
+
const result = native.rscScanApp(appDir);
|
|
73
|
+
const scanTime = performance.now() - startTime;
|
|
74
|
+
console.log(`🦀 Native scan completed in ${scanTime.toFixed(2)}ms (${result.totalFiles} files)`);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
console.error('Native scan failed:', e);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate client manifest using Rust native code
|
|
84
|
+
*/
|
|
85
|
+
function generateClientManifestNative(appDir, buildId) {
|
|
86
|
+
const native = loadNativeModule();
|
|
87
|
+
if (!native)
|
|
88
|
+
return null;
|
|
89
|
+
try {
|
|
90
|
+
return native.rscGenerateClientManifest(appDir, buildId);
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
console.error('Native client manifest generation failed:', e);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate server manifest using Rust native code
|
|
99
|
+
*/
|
|
100
|
+
function generateServerManifestNative(appDir, buildId) {
|
|
101
|
+
const native = loadNativeModule();
|
|
102
|
+
if (!native)
|
|
103
|
+
return null;
|
|
104
|
+
try {
|
|
105
|
+
return native.rscGenerateServerManifest(appDir, buildId);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
console.error('Native server manifest generation failed:', e);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Generate unique mount ID for client component
|
|
114
|
+
*/
|
|
115
|
+
function generateMountIdNative() {
|
|
116
|
+
const native = loadNativeModule();
|
|
117
|
+
if (!native)
|
|
118
|
+
return null;
|
|
119
|
+
return native.rscGenerateMountId();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Reset mount ID counter (call at start of each request)
|
|
123
|
+
*/
|
|
124
|
+
function resetMountCounterNative() {
|
|
125
|
+
const native = loadNativeModule();
|
|
126
|
+
if (!native)
|
|
127
|
+
return false;
|
|
128
|
+
native.rscResetMountCounter();
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Convert NAPI scan result to internal format
|
|
133
|
+
*/
|
|
134
|
+
function convertScanResult(result) {
|
|
135
|
+
return {
|
|
136
|
+
clientComponents: result.clientComponents.map((c) => ({
|
|
137
|
+
absolutePath: c.absolutePath,
|
|
138
|
+
relativePath: c.relativePath,
|
|
139
|
+
isClient: c.isClient,
|
|
140
|
+
directiveLine: c.directiveLine,
|
|
141
|
+
componentType: c.componentType,
|
|
142
|
+
exports: c.exports,
|
|
143
|
+
clientHooksUsed: c.clientHooksUsed,
|
|
144
|
+
hasMetadata: c.hasMetadata,
|
|
145
|
+
hasGenerateMetadata: c.hasGenerateMetadata,
|
|
146
|
+
})),
|
|
147
|
+
serverComponents: result.serverComponents.map((c) => ({
|
|
148
|
+
absolutePath: c.absolutePath,
|
|
149
|
+
relativePath: c.relativePath,
|
|
150
|
+
isClient: false,
|
|
151
|
+
directiveLine: 0,
|
|
152
|
+
componentType: c.componentType,
|
|
153
|
+
exports: c.exports,
|
|
154
|
+
clientHooksUsed: [],
|
|
155
|
+
hasMetadata: c.hasMetadata,
|
|
156
|
+
hasGenerateMetadata: c.hasGenerateMetadata,
|
|
157
|
+
})),
|
|
158
|
+
pages: result.pages,
|
|
159
|
+
layouts: result.layouts,
|
|
160
|
+
apiRoutes: result.apiRoutes,
|
|
161
|
+
errors: result.errors,
|
|
162
|
+
totalFiles: result.totalFiles,
|
|
163
|
+
scanTimeMs: result.scanTimeMs,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vista RSC Renderer
|
|
3
|
+
*
|
|
4
|
+
* Renders React Server Components to an RSC Payload format.
|
|
5
|
+
* This is the core of the "Zero Bundle Size Server Components" feature.
|
|
6
|
+
*
|
|
7
|
+
* RSC Payload Format:
|
|
8
|
+
* - Server components render to a special streaming format
|
|
9
|
+
* - Client component references are serialized as "holes" to be filled client-side
|
|
10
|
+
* - The client receives a stream of instructions + HTML
|
|
11
|
+
*/
|
|
12
|
+
import type { ClientManifest } from './client-manifest';
|
|
13
|
+
import type { ServerManifest, RouteEntry } from './server-manifest';
|
|
14
|
+
export interface RSCPayload {
|
|
15
|
+
/** The rendered HTML for server components */
|
|
16
|
+
html: string;
|
|
17
|
+
/** Client component references to hydrate */
|
|
18
|
+
clientReferences: ClientReference[];
|
|
19
|
+
/** Data passed to client for hydration */
|
|
20
|
+
data: Record<string, any>;
|
|
21
|
+
/** Flight data for streaming (if enabled) */
|
|
22
|
+
flight?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ClientReference {
|
|
25
|
+
/** Unique ID matching client manifest */
|
|
26
|
+
id: string;
|
|
27
|
+
/** DOM element ID where component should mount */
|
|
28
|
+
mountId: string;
|
|
29
|
+
/** Props to pass to the client component */
|
|
30
|
+
props: Record<string, any>;
|
|
31
|
+
/** Chunk URL to load */
|
|
32
|
+
chunkUrl: string;
|
|
33
|
+
/** Export name to use (usually 'default') */
|
|
34
|
+
exportName: string;
|
|
35
|
+
}
|
|
36
|
+
export interface RSCRenderContext {
|
|
37
|
+
/** Client manifest for resolving client components */
|
|
38
|
+
clientManifest: ClientManifest;
|
|
39
|
+
/** Server manifest for component metadata */
|
|
40
|
+
serverManifest: ServerManifest;
|
|
41
|
+
/** Current route being rendered */
|
|
42
|
+
route: RouteEntry;
|
|
43
|
+
/** URL parameters */
|
|
44
|
+
params: Record<string, string>;
|
|
45
|
+
/** Search params */
|
|
46
|
+
searchParams: Record<string, string>;
|
|
47
|
+
/** Request metadata */
|
|
48
|
+
request: {
|
|
49
|
+
url: string;
|
|
50
|
+
method: string;
|
|
51
|
+
headers: Record<string, string>;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reset mount ID counter (call at start of each request)
|
|
56
|
+
*/
|
|
57
|
+
export declare function resetMountIdCounter(): void;
|
|
58
|
+
/**
|
|
59
|
+
* RSC Renderer class
|
|
60
|
+
* Handles rendering a route with proper server/client component separation
|
|
61
|
+
*/
|
|
62
|
+
export declare class RSCRenderer {
|
|
63
|
+
private clientManifest;
|
|
64
|
+
private serverManifest;
|
|
65
|
+
private clientReferences;
|
|
66
|
+
private cwd;
|
|
67
|
+
constructor(options: {
|
|
68
|
+
clientManifest: ClientManifest;
|
|
69
|
+
serverManifest: ServerManifest;
|
|
70
|
+
cwd: string;
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Render a route to RSC Payload
|
|
74
|
+
*/
|
|
75
|
+
render(context: RSCRenderContext): Promise<RSCPayload>;
|
|
76
|
+
/**
|
|
77
|
+
* Render a route with nested layouts
|
|
78
|
+
*/
|
|
79
|
+
private renderRoute;
|
|
80
|
+
/**
|
|
81
|
+
* Require a component with cache clearing in dev
|
|
82
|
+
*/
|
|
83
|
+
private requireComponent;
|
|
84
|
+
/**
|
|
85
|
+
* Check if a component is a client component
|
|
86
|
+
*/
|
|
87
|
+
private isClientComponent;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create an RSC renderer instance
|
|
91
|
+
*/
|
|
92
|
+
export declare function createRSCRenderer(cwd: string): RSCRenderer | null;
|
|
93
|
+
/**
|
|
94
|
+
* Generate the client-side hydration script
|
|
95
|
+
*
|
|
96
|
+
* This just sets the data - the actual hydration is handled by rsc-client.tsx
|
|
97
|
+
* which is bundled and has all client components pre-imported.
|
|
98
|
+
*/
|
|
99
|
+
export declare function generateHydrationScript(payload: RSCPayload): string;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vista RSC Renderer
|
|
4
|
+
*
|
|
5
|
+
* Renders React Server Components to an RSC Payload format.
|
|
6
|
+
* This is the core of the "Zero Bundle Size Server Components" feature.
|
|
7
|
+
*
|
|
8
|
+
* RSC Payload Format:
|
|
9
|
+
* - Server components render to a special streaming format
|
|
10
|
+
* - Client component references are serialized as "holes" to be filled client-side
|
|
11
|
+
* - The client receives a stream of instructions + HTML
|
|
12
|
+
*/
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.RSCRenderer = void 0;
|
|
18
|
+
exports.resetMountIdCounter = resetMountIdCounter;
|
|
19
|
+
exports.createRSCRenderer = createRSCRenderer;
|
|
20
|
+
exports.generateHydrationScript = generateHydrationScript;
|
|
21
|
+
const react_1 = __importDefault(require("react"));
|
|
22
|
+
const server_1 = require("react-dom/server");
|
|
23
|
+
const path_1 = __importDefault(require("path"));
|
|
24
|
+
const fs_1 = __importDefault(require("fs"));
|
|
25
|
+
// Import RSC module system for client component interception
|
|
26
|
+
let rscModuleSystem = null;
|
|
27
|
+
try {
|
|
28
|
+
rscModuleSystem = require('../../server/rsc-module-system');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Module system not available (during build)
|
|
32
|
+
}
|
|
33
|
+
// Counter for generating unique mount IDs
|
|
34
|
+
let mountIdCounter = 0;
|
|
35
|
+
/**
|
|
36
|
+
* Reset mount ID counter (call at start of each request)
|
|
37
|
+
*/
|
|
38
|
+
function resetMountIdCounter() {
|
|
39
|
+
mountIdCounter = 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate a unique mount ID for a client component
|
|
43
|
+
*/
|
|
44
|
+
function generateMountId() {
|
|
45
|
+
return `__vista_cc_${++mountIdCounter}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if a component module is a client component
|
|
49
|
+
*/
|
|
50
|
+
function isClientComponent(modulePath, manifest) {
|
|
51
|
+
return modulePath in manifest.pathToId;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a client component placeholder
|
|
55
|
+
* This renders a marker div that will be hydrated on the client
|
|
56
|
+
*/
|
|
57
|
+
function createClientComponentPlaceholder(entry, props, children) {
|
|
58
|
+
const mountId = generateMountId();
|
|
59
|
+
// Create the reference for hydration
|
|
60
|
+
const reference = {
|
|
61
|
+
id: entry.id,
|
|
62
|
+
mountId,
|
|
63
|
+
props: serializeProps(props),
|
|
64
|
+
chunkUrl: `/_vista/static/chunks/${entry.chunkName}.js`,
|
|
65
|
+
exportName: 'default',
|
|
66
|
+
};
|
|
67
|
+
// Create placeholder element
|
|
68
|
+
// Children are rendered server-side if they're server components
|
|
69
|
+
const element = react_1.default.createElement('div', {
|
|
70
|
+
'data-vista-cc': entry.id,
|
|
71
|
+
'data-vista-mount': mountId,
|
|
72
|
+
id: mountId,
|
|
73
|
+
suppressHydrationWarning: true,
|
|
74
|
+
}, children);
|
|
75
|
+
return { element, reference };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Serialize props for client-side hydration
|
|
79
|
+
* Handles Date, undefined, functions, etc.
|
|
80
|
+
*/
|
|
81
|
+
function serializeProps(props) {
|
|
82
|
+
const serialized = {};
|
|
83
|
+
for (const [key, value] of Object.entries(props)) {
|
|
84
|
+
if (key === 'children') {
|
|
85
|
+
// Children are handled separately
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (typeof value === 'function') {
|
|
89
|
+
// Functions can't be serialized - skip or warn
|
|
90
|
+
console.warn(`[Vista RSC] Cannot serialize function prop "${key}" to client`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (value instanceof Date) {
|
|
94
|
+
serialized[key] = { __type: 'Date', value: value.toISOString() };
|
|
95
|
+
}
|
|
96
|
+
else if (value === undefined) {
|
|
97
|
+
serialized[key] = { __type: 'undefined' };
|
|
98
|
+
}
|
|
99
|
+
else if (react_1.default.isValidElement(value)) {
|
|
100
|
+
// React elements need special handling
|
|
101
|
+
serialized[key] = { __type: 'ReactElement', rendered: true };
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
serialized[key] = value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return serialized;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* RSC Renderer class
|
|
111
|
+
* Handles rendering a route with proper server/client component separation
|
|
112
|
+
*/
|
|
113
|
+
class RSCRenderer {
|
|
114
|
+
clientManifest;
|
|
115
|
+
serverManifest;
|
|
116
|
+
clientReferences = [];
|
|
117
|
+
cwd;
|
|
118
|
+
constructor(options) {
|
|
119
|
+
this.clientManifest = options.clientManifest;
|
|
120
|
+
this.serverManifest = options.serverManifest;
|
|
121
|
+
this.cwd = options.cwd;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Render a route to RSC Payload
|
|
125
|
+
*/
|
|
126
|
+
async render(context) {
|
|
127
|
+
// Reset counters
|
|
128
|
+
resetMountIdCounter();
|
|
129
|
+
this.clientReferences = [];
|
|
130
|
+
// Reset RSC module system state (for client component interception)
|
|
131
|
+
if (rscModuleSystem) {
|
|
132
|
+
rscModuleSystem.resetRSCState();
|
|
133
|
+
}
|
|
134
|
+
const { route, params, searchParams } = context;
|
|
135
|
+
// Load and render layouts + page
|
|
136
|
+
const element = await this.renderRoute(route, params, searchParams);
|
|
137
|
+
// Render to HTML string
|
|
138
|
+
const html = (0, server_1.renderToString)(element);
|
|
139
|
+
// Collect client references from module system (intercepted requires)
|
|
140
|
+
let collectedReferences = this.clientReferences;
|
|
141
|
+
if (rscModuleSystem) {
|
|
142
|
+
const moduleReferences = rscModuleSystem.getClientReferences();
|
|
143
|
+
collectedReferences = [...collectedReferences, ...moduleReferences];
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
html,
|
|
147
|
+
clientReferences: collectedReferences,
|
|
148
|
+
data: {
|
|
149
|
+
params,
|
|
150
|
+
searchParams,
|
|
151
|
+
route: route.pattern,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Render a route with nested layouts
|
|
157
|
+
*/
|
|
158
|
+
async renderRoute(route, params, searchParams) {
|
|
159
|
+
// Load the page component
|
|
160
|
+
const PageModule = this.requireComponent(route.pagePath);
|
|
161
|
+
const PageComponent = PageModule.default;
|
|
162
|
+
// Fetch page metadata if exists
|
|
163
|
+
let metadata = {};
|
|
164
|
+
if (PageModule.metadata) {
|
|
165
|
+
metadata = { ...metadata, ...PageModule.metadata };
|
|
166
|
+
}
|
|
167
|
+
if (typeof PageModule.generateMetadata === 'function') {
|
|
168
|
+
try {
|
|
169
|
+
const dynamicMeta = await PageModule.generateMetadata({ params, searchParams });
|
|
170
|
+
metadata = { ...metadata, ...dynamicMeta };
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
console.error('[Vista RSC] generateMetadata error:', e);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Create page element
|
|
177
|
+
let pageElement = react_1.default.createElement(PageComponent, {
|
|
178
|
+
params,
|
|
179
|
+
searchParams,
|
|
180
|
+
});
|
|
181
|
+
// Check if page is a client component
|
|
182
|
+
if (this.isClientComponent(route.pagePath)) {
|
|
183
|
+
const entry = this.clientManifest.clientModules[this.clientManifest.pathToId[route.pagePath]];
|
|
184
|
+
const { element, reference } = createClientComponentPlaceholder(entry, {
|
|
185
|
+
params,
|
|
186
|
+
searchParams,
|
|
187
|
+
});
|
|
188
|
+
this.clientReferences.push(reference);
|
|
189
|
+
pageElement = element;
|
|
190
|
+
}
|
|
191
|
+
// Wrap in layouts (innermost to outermost)
|
|
192
|
+
let wrappedElement = pageElement;
|
|
193
|
+
for (let i = route.layoutPaths.length - 1; i >= 0; i--) {
|
|
194
|
+
const layoutPath = route.layoutPaths[i];
|
|
195
|
+
const LayoutModule = this.requireComponent(layoutPath);
|
|
196
|
+
const LayoutComponent = LayoutModule.default;
|
|
197
|
+
if (this.isClientComponent(layoutPath)) {
|
|
198
|
+
// Client component layout
|
|
199
|
+
const entry = this.clientManifest.clientModules[this.clientManifest.pathToId[layoutPath]];
|
|
200
|
+
const { element, reference } = createClientComponentPlaceholder(entry, { params, searchParams }, wrappedElement);
|
|
201
|
+
this.clientReferences.push(reference);
|
|
202
|
+
wrappedElement = element;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Server component layout
|
|
206
|
+
wrappedElement = react_1.default.createElement(LayoutComponent, { params, searchParams }, wrappedElement);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return wrappedElement;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Require a component with cache clearing in dev
|
|
213
|
+
*/
|
|
214
|
+
requireComponent(componentPath) {
|
|
215
|
+
// Clear cache in development
|
|
216
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
217
|
+
delete require.cache[require.resolve(componentPath)];
|
|
218
|
+
}
|
|
219
|
+
return require(componentPath);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Check if a component is a client component
|
|
223
|
+
*/
|
|
224
|
+
isClientComponent(componentPath) {
|
|
225
|
+
return componentPath in this.clientManifest.pathToId;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.RSCRenderer = RSCRenderer;
|
|
229
|
+
/**
|
|
230
|
+
* Create an RSC renderer instance
|
|
231
|
+
*/
|
|
232
|
+
function createRSCRenderer(cwd) {
|
|
233
|
+
const clientManifestPath = path_1.default.join(cwd, '.vista', 'client-manifest.json');
|
|
234
|
+
const serverManifestPath = path_1.default.join(cwd, '.vista', 'server', 'server-manifest.json');
|
|
235
|
+
if (!fs_1.default.existsSync(clientManifestPath) || !fs_1.default.existsSync(serverManifestPath)) {
|
|
236
|
+
console.warn('[Vista RSC] Manifests not found. Run build first.');
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const clientManifest = JSON.parse(fs_1.default.readFileSync(clientManifestPath, 'utf-8'));
|
|
241
|
+
const serverManifest = JSON.parse(fs_1.default.readFileSync(serverManifestPath, 'utf-8'));
|
|
242
|
+
return new RSCRenderer({
|
|
243
|
+
clientManifest,
|
|
244
|
+
serverManifest,
|
|
245
|
+
cwd,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
console.error('[Vista RSC] Failed to load manifests:', e);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Generate the client-side hydration script
|
|
255
|
+
*
|
|
256
|
+
* This just sets the data - the actual hydration is handled by rsc-client.tsx
|
|
257
|
+
* which is bundled and has all client components pre-imported.
|
|
258
|
+
*/
|
|
259
|
+
function generateHydrationScript(payload) {
|
|
260
|
+
const references = JSON.stringify(payload.clientReferences);
|
|
261
|
+
const data = JSON.stringify(payload.data);
|
|
262
|
+
return `
|
|
263
|
+
<script>
|
|
264
|
+
window.__VISTA_RSC_DATA__ = ${data};
|
|
265
|
+
window.__VISTA_CLIENT_REFERENCES__ = ${references};
|
|
266
|
+
console.log('[Vista RSC] Client references:', ${references.length > 2 ? references.length : 0});
|
|
267
|
+
</script>
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vista Server Component Loader
|
|
3
|
+
*
|
|
4
|
+
* Webpack loader that transforms server component imports in client bundles.
|
|
5
|
+
*
|
|
6
|
+
* When a server component is imported in a client bundle:
|
|
7
|
+
* 1. The loader detects if the file has 'client load' directive
|
|
8
|
+
* 2. If NOT a client component, replace the module with a proxy
|
|
9
|
+
* 3. The proxy provides helpful error messages when misused
|
|
10
|
+
*/
|
|
11
|
+
import type { LoaderContext } from 'webpack';
|
|
12
|
+
export interface ServerComponentLoaderOptions {
|
|
13
|
+
appDir: string;
|
|
14
|
+
clientManifestPath: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Server Component Loader
|
|
18
|
+
*/
|
|
19
|
+
export default function serverComponentLoader(this: LoaderContext<ServerComponentLoaderOptions>, source: string): string;
|