@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,588 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vista RSC Engine
|
|
4
|
+
*
|
|
5
|
+
* React Server Components aware rendering engine.
|
|
6
|
+
*
|
|
7
|
+
* This engine implements the "True RSC Architecture":
|
|
8
|
+
* 1. Server components render on the server only, contribute 0kb to client
|
|
9
|
+
* 2. Client components are sent as references, hydrated on demand
|
|
10
|
+
* 3. Strict separation ensures server secrets never leak
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.startRSCServer = startRSCServer;
|
|
17
|
+
exports.default = startRSCServer;
|
|
18
|
+
const express_1 = __importDefault(require("express"));
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const server_1 = require("react-dom/server");
|
|
22
|
+
const react_1 = __importDefault(require("react"));
|
|
23
|
+
const webpack_dev_middleware_1 = __importDefault(require("webpack-dev-middleware"));
|
|
24
|
+
const webpack_hot_middleware_1 = __importDefault(require("webpack-hot-middleware"));
|
|
25
|
+
const config_1 = require("../config");
|
|
26
|
+
const dev_error_1 = require("../dev-error");
|
|
27
|
+
const rsc_renderer_1 = require("../build/rsc/rsc-renderer");
|
|
28
|
+
const rsc_module_system_1 = require("./rsc-module-system");
|
|
29
|
+
// Support CSS imports (ignore them on server)
|
|
30
|
+
require.extensions['.css'] = () => { };
|
|
31
|
+
// IMPORTANT: Initialize RSC module system BEFORE TypeScript compiler
|
|
32
|
+
// This must happen first so we can intercept client component requires
|
|
33
|
+
function earlyInitializeRSC(cwd) {
|
|
34
|
+
try {
|
|
35
|
+
const clientManifestPath = path_1.default.join(cwd, '.vista', 'client-manifest.json');
|
|
36
|
+
if (fs_1.default.existsSync(clientManifestPath)) {
|
|
37
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(clientManifestPath, 'utf-8'));
|
|
38
|
+
(0, rsc_module_system_1.initializeRSCModuleSystem)(manifest);
|
|
39
|
+
return manifest;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
// Will initialize later
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// Early init before TypeScript compilation
|
|
48
|
+
const earlyManifest = earlyInitializeRSC(process.cwd());
|
|
49
|
+
// Use SWC for faster server-side TypeScript compilation
|
|
50
|
+
try {
|
|
51
|
+
require('@swc-node/register');
|
|
52
|
+
console.log('[Vista RSC] Using SWC for server-side TypeScript compilation');
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
try {
|
|
56
|
+
require('ts-node').register({
|
|
57
|
+
transpileOnly: true,
|
|
58
|
+
compilerOptions: {
|
|
59
|
+
module: 'commonjs',
|
|
60
|
+
jsx: 'react-jsx',
|
|
61
|
+
moduleResolution: 'node',
|
|
62
|
+
esModuleInterop: true,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
console.log('[Vista RSC] Using ts-node for server-side TypeScript compilation');
|
|
66
|
+
}
|
|
67
|
+
catch (e2) {
|
|
68
|
+
console.warn('[Vista RSC] No TypeScript compiler found for SSR.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Start the RSC-aware Vista server
|
|
73
|
+
*/
|
|
74
|
+
function startRSCServer(options = {}) {
|
|
75
|
+
const app = (0, express_1.default)();
|
|
76
|
+
const cwd = process.cwd();
|
|
77
|
+
const vistaConfig = (0, config_1.loadConfig)(cwd);
|
|
78
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
79
|
+
const port = options.port || vistaConfig.server?.port || 3003;
|
|
80
|
+
console.log('[Vista RSC] Starting React Server Components Engine...');
|
|
81
|
+
console.log('[Vista RSC] App Directory:', cwd);
|
|
82
|
+
// Initialize RSC renderer
|
|
83
|
+
let rscRenderer = null;
|
|
84
|
+
let clientManifest = null;
|
|
85
|
+
let serverManifest = null;
|
|
86
|
+
function loadManifests() {
|
|
87
|
+
try {
|
|
88
|
+
const clientManifestPath = path_1.default.join(cwd, '.vista', 'client-manifest.json');
|
|
89
|
+
const serverManifestPath = path_1.default.join(cwd, '.vista', 'server', 'server-manifest.json');
|
|
90
|
+
if (fs_1.default.existsSync(clientManifestPath)) {
|
|
91
|
+
clientManifest = JSON.parse(fs_1.default.readFileSync(clientManifestPath, 'utf-8'));
|
|
92
|
+
}
|
|
93
|
+
if (fs_1.default.existsSync(serverManifestPath)) {
|
|
94
|
+
serverManifest = JSON.parse(fs_1.default.readFileSync(serverManifestPath, 'utf-8'));
|
|
95
|
+
}
|
|
96
|
+
if (clientManifest && serverManifest) {
|
|
97
|
+
// Initialize RSC module system to intercept client component requires
|
|
98
|
+
(0, rsc_module_system_1.initializeRSCModuleSystem)(clientManifest);
|
|
99
|
+
rscRenderer = new rsc_renderer_1.RSCRenderer({
|
|
100
|
+
clientManifest,
|
|
101
|
+
serverManifest,
|
|
102
|
+
cwd,
|
|
103
|
+
});
|
|
104
|
+
console.log('[Vista RSC] Manifests loaded successfully');
|
|
105
|
+
console.log('[Vista RSC] Module interception enabled for client components');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
console.warn('[Vista RSC] Could not load manifests:', e);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
loadManifests();
|
|
113
|
+
// SSE clients for live reload and error overlay
|
|
114
|
+
const sseClients = new Set();
|
|
115
|
+
// Webpack Dev + Hot Middleware (only in dev mode)
|
|
116
|
+
if (isDev && options.compiler) {
|
|
117
|
+
console.log('[Vista RSC] Enabling Webpack HMR...');
|
|
118
|
+
app.use((0, webpack_dev_middleware_1.default)(options.compiler, {
|
|
119
|
+
publicPath: '/',
|
|
120
|
+
stats: 'minimal',
|
|
121
|
+
writeToDisk: (filePath) => filePath.endsWith('.css'),
|
|
122
|
+
}));
|
|
123
|
+
app.use((0, webpack_hot_middleware_1.default)(options.compiler, {
|
|
124
|
+
log: console.log,
|
|
125
|
+
path: '/__webpack_hmr',
|
|
126
|
+
heartbeat: 2000,
|
|
127
|
+
}));
|
|
128
|
+
// SSE endpoint for live reload and compile errors
|
|
129
|
+
app.get('/__vista_reload', (req, res) => {
|
|
130
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
131
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
132
|
+
res.setHeader('Connection', 'keep-alive');
|
|
133
|
+
res.flushHeaders();
|
|
134
|
+
sseClients.add(res);
|
|
135
|
+
res.write('data: connected\n\n');
|
|
136
|
+
req.on('close', () => {
|
|
137
|
+
sseClients.delete(res);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
// Push compile errors to browser via SSE
|
|
141
|
+
const pushCompileError = (errorMessage) => {
|
|
142
|
+
const errorData = JSON.stringify({
|
|
143
|
+
type: 'error',
|
|
144
|
+
message: errorMessage,
|
|
145
|
+
});
|
|
146
|
+
sseClients.forEach((client) => {
|
|
147
|
+
client.write(`data: ${errorData}\n\n`);
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
// Push build success to browser (clears error overlay)
|
|
151
|
+
const pushBuildSuccess = () => {
|
|
152
|
+
const data = JSON.stringify({ type: 'ok' });
|
|
153
|
+
sseClients.forEach((client) => {
|
|
154
|
+
client.write(`data: ${data}\n\n`);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
// Listen to webpack compilation - push errors or success to browser
|
|
158
|
+
options.compiler.hooks.done.tap('VistaRSCErrorOverlay', (stats) => {
|
|
159
|
+
if (stats.hasErrors()) {
|
|
160
|
+
const errors = stats.toJson().errors || [];
|
|
161
|
+
const errorMessage = errors.map((e) => (typeof e === 'string' ? e : e.message)).join('\n');
|
|
162
|
+
console.log('[Vista RSC] Build error detected, pushing to browser...');
|
|
163
|
+
pushCompileError(errorMessage);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
pushBuildSuccess();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// Reload manifests on rebuild
|
|
170
|
+
options.compiler.hooks.afterEmit.tap('VistaRSCEngine', () => {
|
|
171
|
+
loadManifests();
|
|
172
|
+
});
|
|
173
|
+
// Watch server files and trigger reload for server component changes
|
|
174
|
+
let debounceTimer = null;
|
|
175
|
+
const triggerReload = () => {
|
|
176
|
+
if (debounceTimer)
|
|
177
|
+
clearTimeout(debounceTimer);
|
|
178
|
+
debounceTimer = setTimeout(() => {
|
|
179
|
+
console.log('[Vista RSC] Server file changed, triggering reload...');
|
|
180
|
+
sseClients.forEach((client) => {
|
|
181
|
+
client.write('data: reload\n\n');
|
|
182
|
+
});
|
|
183
|
+
}, 100);
|
|
184
|
+
};
|
|
185
|
+
// Watch app directory for server component changes
|
|
186
|
+
const appDir = path_1.default.join(cwd, 'app');
|
|
187
|
+
fs_1.default.watch(appDir, { recursive: true }, (event, filename) => {
|
|
188
|
+
if (filename && (filename.endsWith('.tsx') || filename.endsWith('.ts'))) {
|
|
189
|
+
const filePath = path_1.default.join(appDir, filename);
|
|
190
|
+
try {
|
|
191
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
192
|
+
// Server files don't have 'client load' directive - trigger reload
|
|
193
|
+
if (!content.includes("'client load'") && !content.includes('"client load"')) {
|
|
194
|
+
triggerReload();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
// File might be deleted or being written
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// Explicit CSS route - serve .vista/client.css as /styles.css
|
|
204
|
+
app.get('/styles.css', (req, res) => {
|
|
205
|
+
const cssPath = path_1.default.join(cwd, '.vista', 'client.css');
|
|
206
|
+
if (fs_1.default.existsSync(cssPath)) {
|
|
207
|
+
res.setHeader('Content-Type', 'text/css');
|
|
208
|
+
res.sendFile(cssPath);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Fallback to globals.css if client.css doesn't exist
|
|
212
|
+
const globalsPath = path_1.default.join(cwd, 'app', 'globals.css');
|
|
213
|
+
if (fs_1.default.existsSync(globalsPath)) {
|
|
214
|
+
res.setHeader('Content-Type', 'text/css');
|
|
215
|
+
res.sendFile(globalsPath);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
res.status(404).send('/* CSS not found */');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
// Static file serving
|
|
223
|
+
app.use(express_1.default.static(path_1.default.join(cwd, 'public')));
|
|
224
|
+
app.use('/_vista/static', express_1.default.static(path_1.default.join(cwd, '.vista', 'static')));
|
|
225
|
+
app.use('/_vista', express_1.default.static(path_1.default.join(cwd, '.vista')));
|
|
226
|
+
app.use(express_1.default.static(path_1.default.join(cwd, '.vista')));
|
|
227
|
+
// RSC Payload endpoint (for client-side navigation)
|
|
228
|
+
app.get('/_rsc/*', async (req, res) => {
|
|
229
|
+
const pathname = req.path.replace(/^\/_rsc/, '') || '/';
|
|
230
|
+
if (!rscRenderer || !serverManifest) {
|
|
231
|
+
return res.status(500).json({ error: 'RSC not initialized' });
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const route = matchRoute(pathname, serverManifest.routes);
|
|
235
|
+
if (!route) {
|
|
236
|
+
return res.status(404).json({ error: 'Route not found' });
|
|
237
|
+
}
|
|
238
|
+
const params = extractParams(pathname, route);
|
|
239
|
+
const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
|
|
240
|
+
const payload = await rscRenderer.render({
|
|
241
|
+
clientManifest: clientManifest,
|
|
242
|
+
serverManifest: serverManifest,
|
|
243
|
+
route,
|
|
244
|
+
params,
|
|
245
|
+
searchParams,
|
|
246
|
+
request: {
|
|
247
|
+
url: req.url,
|
|
248
|
+
method: req.method,
|
|
249
|
+
headers: req.headers,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
res.json(payload);
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
console.error('[Vista RSC] Error rendering RSC payload:', e);
|
|
256
|
+
res.status(500).json({ error: e.message });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
// Main request handler
|
|
260
|
+
app.use(async (req, res, next) => {
|
|
261
|
+
// Skip static files and HMR
|
|
262
|
+
if (req.path.startsWith('/styles.css') ||
|
|
263
|
+
req.path.startsWith('/__webpack_hmr') ||
|
|
264
|
+
req.path.startsWith('/_vista')) {
|
|
265
|
+
return next();
|
|
266
|
+
}
|
|
267
|
+
// Handle API routes (unchanged)
|
|
268
|
+
if (req.path.startsWith('/api/')) {
|
|
269
|
+
return handleApiRoute(req, res, cwd, isDev);
|
|
270
|
+
}
|
|
271
|
+
// Render page with RSC
|
|
272
|
+
try {
|
|
273
|
+
await renderRSCPage(req, res, {
|
|
274
|
+
cwd,
|
|
275
|
+
isDev,
|
|
276
|
+
vistaConfig,
|
|
277
|
+
rscRenderer,
|
|
278
|
+
clientManifest,
|
|
279
|
+
serverManifest,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
console.error('[Vista RSC] Render error:', err);
|
|
284
|
+
if (isDev) {
|
|
285
|
+
const errorInfo = {
|
|
286
|
+
type: 'runtime',
|
|
287
|
+
message: err.message || 'Unknown Server Error',
|
|
288
|
+
stack: err.stack,
|
|
289
|
+
};
|
|
290
|
+
const errorHtml = (0, server_1.renderToString)(react_1.default.createElement(dev_error_1.ErrorOverlay, { errors: [errorInfo] }));
|
|
291
|
+
res
|
|
292
|
+
.status(500)
|
|
293
|
+
.send(`<!DOCTYPE html><html><body style="margin:0">${errorHtml}</body></html>`);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
res.status(500).send('<h1>Internal Server Error</h1>');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
app.listen(port, () => {
|
|
301
|
+
console.log(`[Vista RSC] Server running at http://localhost:${port}`);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Match a pathname to a route
|
|
306
|
+
*/
|
|
307
|
+
function matchRoute(pathname, routes) {
|
|
308
|
+
for (const route of routes) {
|
|
309
|
+
if (matchPattern(pathname, route.pattern)) {
|
|
310
|
+
return route;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Simple pattern matching for routes
|
|
317
|
+
*/
|
|
318
|
+
function matchPattern(pathname, pattern) {
|
|
319
|
+
const patternParts = pattern.split('/').filter(Boolean);
|
|
320
|
+
const pathParts = pathname.split('/').filter(Boolean);
|
|
321
|
+
// Root route
|
|
322
|
+
if (patternParts.length === 0 && pathParts.length === 0) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
// Check each segment
|
|
326
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
327
|
+
const patternPart = patternParts[i];
|
|
328
|
+
const pathPart = pathParts[i];
|
|
329
|
+
// Catch-all
|
|
330
|
+
if (patternPart.endsWith('*')) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
// Dynamic segment
|
|
334
|
+
if (patternPart.startsWith(':')) {
|
|
335
|
+
if (!pathPart)
|
|
336
|
+
return false;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
// Static segment
|
|
340
|
+
if (patternPart !== pathPart) {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return patternParts.length === pathParts.length;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Extract params from pathname based on route pattern
|
|
348
|
+
*/
|
|
349
|
+
function extractParams(pathname, route) {
|
|
350
|
+
const params = {};
|
|
351
|
+
const patternParts = route.pattern.split('/').filter(Boolean);
|
|
352
|
+
const pathParts = pathname.split('/').filter(Boolean);
|
|
353
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
354
|
+
const patternPart = patternParts[i];
|
|
355
|
+
if (patternPart.startsWith(':')) {
|
|
356
|
+
const paramName = patternPart.slice(1).replace('*', '');
|
|
357
|
+
if (patternPart.endsWith('*')) {
|
|
358
|
+
// Catch-all: grab rest of path
|
|
359
|
+
params[paramName] = pathParts.slice(i).join('/');
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
params[paramName] = pathParts[i] || '';
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return params;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Handle API routes
|
|
370
|
+
*/
|
|
371
|
+
async function handleApiRoute(req, res, cwd, isDev) {
|
|
372
|
+
const apiRoute = req.path.substring(5); // Remove '/api/'
|
|
373
|
+
const routeTsPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.ts');
|
|
374
|
+
const routeTsxPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute, 'route.tsx');
|
|
375
|
+
const legacyPath = path_1.default.resolve(cwd, 'app', 'api', apiRoute + '.ts');
|
|
376
|
+
let apiPath = null;
|
|
377
|
+
if (fs_1.default.existsSync(routeTsPath))
|
|
378
|
+
apiPath = routeTsPath;
|
|
379
|
+
else if (fs_1.default.existsSync(routeTsxPath))
|
|
380
|
+
apiPath = routeTsxPath;
|
|
381
|
+
else if (fs_1.default.existsSync(legacyPath))
|
|
382
|
+
apiPath = legacyPath;
|
|
383
|
+
if (!apiPath) {
|
|
384
|
+
res.status(404).json({ error: 'API Route Not Found' });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
if (isDev)
|
|
389
|
+
delete require.cache[require.resolve(apiPath)];
|
|
390
|
+
const apiModule = require(apiPath);
|
|
391
|
+
const method = req.method?.toUpperCase() || 'GET';
|
|
392
|
+
const methodHandler = apiModule[method];
|
|
393
|
+
if (typeof methodHandler === 'function') {
|
|
394
|
+
const request = {
|
|
395
|
+
url: req.protocol + '://' + req.get('host') + req.originalUrl,
|
|
396
|
+
method: req.method,
|
|
397
|
+
headers: new Map(Object.entries(req.headers)),
|
|
398
|
+
json: async () => req.body,
|
|
399
|
+
text: async () => JSON.stringify(req.body),
|
|
400
|
+
nextUrl: {
|
|
401
|
+
pathname: req.path,
|
|
402
|
+
searchParams: new URLSearchParams(req.query),
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
const result = await methodHandler(request, { params: {} });
|
|
406
|
+
if (result && typeof result.json === 'function') {
|
|
407
|
+
const json = await result.json();
|
|
408
|
+
res.status(result.status || 200).json(json);
|
|
409
|
+
}
|
|
410
|
+
else if (result) {
|
|
411
|
+
res.status(200).json(result);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
res.status(204).end();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else if (apiModule.default) {
|
|
418
|
+
apiModule.default(req, res);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
res.status(405).json({ error: `Method ${method} not allowed` });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
console.error('[Vista RSC] API Route Error:', err);
|
|
426
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Render a page using RSC
|
|
431
|
+
*/
|
|
432
|
+
async function renderRSCPage(req, res, context) {
|
|
433
|
+
const { cwd, isDev, vistaConfig, rscRenderer, clientManifest, serverManifest } = context;
|
|
434
|
+
// If RSC is not initialized, fall back to legacy rendering
|
|
435
|
+
if (!rscRenderer || !serverManifest) {
|
|
436
|
+
return legacyRender(req, res, cwd, isDev, vistaConfig);
|
|
437
|
+
}
|
|
438
|
+
// Match the route
|
|
439
|
+
const route = matchRoute(req.path, serverManifest.routes);
|
|
440
|
+
if (!route) {
|
|
441
|
+
// Try not-found page
|
|
442
|
+
const notFoundPath = path_1.default.resolve(cwd, 'app', 'not-found.tsx');
|
|
443
|
+
if (fs_1.default.existsSync(notFoundPath)) {
|
|
444
|
+
// Render not-found page
|
|
445
|
+
res.status(404);
|
|
446
|
+
return legacyRender(req, res, cwd, isDev, vistaConfig, notFoundPath);
|
|
447
|
+
}
|
|
448
|
+
res.status(404).send('<h1>404 - Page Not Found</h1>');
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
// Extract params and search params
|
|
452
|
+
const params = extractParams(req.path, route);
|
|
453
|
+
const searchParams = Object.fromEntries(new URLSearchParams(req.query).entries());
|
|
454
|
+
// Render using RSC renderer
|
|
455
|
+
const payload = await rscRenderer.render({
|
|
456
|
+
clientManifest: clientManifest,
|
|
457
|
+
serverManifest: serverManifest,
|
|
458
|
+
route,
|
|
459
|
+
params,
|
|
460
|
+
searchParams,
|
|
461
|
+
request: {
|
|
462
|
+
url: req.url,
|
|
463
|
+
method: req.method,
|
|
464
|
+
headers: req.headers,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
// Generate the full HTML document
|
|
468
|
+
const html = generateFullHtml(payload, vistaConfig, isDev, cwd);
|
|
469
|
+
res.setHeader('Content-Type', 'text/html');
|
|
470
|
+
res.send(html);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Find all JS chunk files in the static directory
|
|
474
|
+
* Returns them sorted by size (smallest first = runtime chunks first)
|
|
475
|
+
*/
|
|
476
|
+
function findAllChunkFiles(cwd) {
|
|
477
|
+
const chunksDir = path_1.default.join(cwd, '.vista', 'static', 'chunks');
|
|
478
|
+
const jsFiles = [];
|
|
479
|
+
if (!fs_1.default.existsSync(chunksDir)) {
|
|
480
|
+
return [];
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const entries = fs_1.default.readdirSync(chunksDir);
|
|
484
|
+
for (const entry of entries) {
|
|
485
|
+
if (entry.endsWith('.js') && !entry.endsWith('.map')) {
|
|
486
|
+
const stat = fs_1.default.statSync(path_1.default.join(chunksDir, entry));
|
|
487
|
+
jsFiles.push({ name: entry, size: stat.size });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
// Ignore
|
|
493
|
+
}
|
|
494
|
+
// Sort by size (smallest first - runtime/webpack chunks are usually smallest)
|
|
495
|
+
jsFiles.sort((a, b) => a.size - b.size);
|
|
496
|
+
return jsFiles.map((f) => f.name);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Generate full HTML document from RSC payload
|
|
500
|
+
*/
|
|
501
|
+
function generateFullHtml(payload, config, isDev, cwd = process.cwd()) {
|
|
502
|
+
const hydrationScript = (0, rsc_renderer_1.generateHydrationScript)(payload);
|
|
503
|
+
const chunkFiles = findAllChunkFiles(cwd);
|
|
504
|
+
// Build script tags for all chunks
|
|
505
|
+
const scriptTags = chunkFiles.map((chunk) => `<script src="/_vista/static/chunks/${chunk}"></script>`);
|
|
506
|
+
return `<!DOCTYPE html>
|
|
507
|
+
<html lang="en">
|
|
508
|
+
<head>
|
|
509
|
+
<meta charset="UTF-8">
|
|
510
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
511
|
+
<link rel="preload" href="/styles.css" as="style">
|
|
512
|
+
<link rel="stylesheet" href="/styles.css">
|
|
513
|
+
<style>
|
|
514
|
+
/* Critical styles - prevents FOUC */
|
|
515
|
+
:root {
|
|
516
|
+
--background: #ffffff;
|
|
517
|
+
--foreground: #171717;
|
|
518
|
+
}
|
|
519
|
+
@media (prefers-color-scheme: dark) {
|
|
520
|
+
:root {
|
|
521
|
+
--background: #0a0a0a;
|
|
522
|
+
--foreground: #ededed;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
526
|
+
html, body { height: 100%; }
|
|
527
|
+
body {
|
|
528
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
529
|
+
background: var(--background);
|
|
530
|
+
color: var(--foreground);
|
|
531
|
+
-webkit-font-smoothing: antialiased;
|
|
532
|
+
-moz-osx-font-smoothing: grayscale;
|
|
533
|
+
}
|
|
534
|
+
#root { min-height: 100%; }
|
|
535
|
+
/* Tailwind critical classes */
|
|
536
|
+
.flex { display: flex; }
|
|
537
|
+
.min-h-screen { min-height: 100vh; }
|
|
538
|
+
.flex-col { flex-direction: column; }
|
|
539
|
+
.items-center { align-items: center; }
|
|
540
|
+
.justify-center { justify-content: center; }
|
|
541
|
+
.text-center { text-align: center; }
|
|
542
|
+
.text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
|
|
543
|
+
.font-semibold { font-weight: 600; }
|
|
544
|
+
.relative { position: relative; }
|
|
545
|
+
.absolute { position: absolute; }
|
|
546
|
+
.border { border-width: 1px; }
|
|
547
|
+
.border-dashed { border-style: dashed; }
|
|
548
|
+
.rounded-full { border-radius: 9999px; }
|
|
549
|
+
.p-10 { padding: 2.5rem; }
|
|
550
|
+
.mb-10 { margin-bottom: 2.5rem; }
|
|
551
|
+
.-mt-20 { margin-top: -5rem; }
|
|
552
|
+
.max-w-xs { max-width: 20rem; }
|
|
553
|
+
.tracking-tight { letter-spacing: -0.025em; }
|
|
554
|
+
.leading-10 { line-height: 2.5rem; }
|
|
555
|
+
.bg-white { background-color: #fff; }
|
|
556
|
+
.bg-black { background-color: #000; }
|
|
557
|
+
.text-black { color: #000; }
|
|
558
|
+
.border-gray-300 { border-color: #d1d5db; }
|
|
559
|
+
.transition-colors { transition-property: color, background-color, border-color; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
|
|
560
|
+
.duration-200 { transition-duration: 200ms; }
|
|
561
|
+
@media (prefers-color-scheme: dark) {
|
|
562
|
+
.dark\\:bg-black { background-color: #000; }
|
|
563
|
+
.dark\\:text-zinc-50 { color: #fafafa; }
|
|
564
|
+
.dark\\:border-neutral-700 { border-color: #404040; }
|
|
565
|
+
.dark\\:invert { filter: invert(1); }
|
|
566
|
+
}
|
|
567
|
+
@media (min-width: 640px) {
|
|
568
|
+
.sm\\:max-w-none { max-width: none; }
|
|
569
|
+
}
|
|
570
|
+
[data-vista-cc] { display: contents; }
|
|
571
|
+
</style>
|
|
572
|
+
</head>
|
|
573
|
+
<body>
|
|
574
|
+
<div id="root">${payload.html}</div>
|
|
575
|
+
${hydrationScript}
|
|
576
|
+
${scriptTags.join('\n ')}
|
|
577
|
+
</body>
|
|
578
|
+
</html>`;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Legacy render for fallback or when RSC is not available
|
|
582
|
+
*/
|
|
583
|
+
function legacyRender(req, res, cwd, isDev, config, customPage) {
|
|
584
|
+
// Import the original engine for legacy rendering
|
|
585
|
+
const { startServer } = require('./engine');
|
|
586
|
+
// This is a simplified fallback - in practice, you'd handle this better
|
|
587
|
+
res.status(500).send('RSC not initialized. Please run build first.');
|
|
588
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vista RSC Module System
|
|
3
|
+
*
|
|
4
|
+
* Intercepts require() calls for client components during SSR.
|
|
5
|
+
* Client components are replaced with placeholder divs that will be
|
|
6
|
+
* hydrated on the client side.
|
|
7
|
+
*/
|
|
8
|
+
export interface ClientReference {
|
|
9
|
+
id: string;
|
|
10
|
+
mountId: string;
|
|
11
|
+
props: Record<string, any>;
|
|
12
|
+
chunkUrl: string;
|
|
13
|
+
exportName: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the RSC module system
|
|
17
|
+
* Must be called BEFORE any app modules are loaded
|
|
18
|
+
*/
|
|
19
|
+
export declare function initializeRSCModuleSystem(manifest: any, cwd?: string): void;
|
|
20
|
+
export declare function _getMountId(): number;
|
|
21
|
+
export declare function _addReference(ref: ClientReference): void;
|
|
22
|
+
/**
|
|
23
|
+
* Reset state for new render
|
|
24
|
+
*/
|
|
25
|
+
export declare function resetRSCState(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Get collected client references
|
|
28
|
+
*/
|
|
29
|
+
export declare function getClientReferences(): ClientReference[];
|
|
30
|
+
/**
|
|
31
|
+
* Shutdown the RSC module system
|
|
32
|
+
*/
|
|
33
|
+
export declare function shutdownRSCModuleSystem(): void;
|