@vistagenic/vista 0.2.7 → 0.2.9

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.
@@ -28,6 +28,7 @@ const static_generator_1 = require("../server/static-generator");
28
28
  const structure_log_1 = require("../server/structure-log");
29
29
  const devtools_indicator_snippet_1 = require("./devtools-indicator-snippet");
30
30
  const dev_error_overlay_snippet_1 = require("./dev-error-overlay-snippet");
31
+ const deploy_output_1 = require("./deploy-output");
31
32
  const _debug = !!process.env.VISTA_DEBUG;
32
33
  /**
33
34
  * Run PostCSS for CSS compilation
@@ -555,6 +556,11 @@ async function buildRSC(watch = false) {
555
556
  const prerenderManifestPath = path_1.default.join(vistaDirs.root, 'prerender-manifest.json');
556
557
  fs_1.default.writeFileSync(prerenderManifestPath, JSON.stringify(ssgResult.manifest, null, 2));
557
558
  console.log('[Vista JS RSC] Wrote prerender-manifest.json');
559
+ (0, deploy_output_1.generateDeploymentOutputs)({
560
+ cwd,
561
+ vistaDir: vistaDirs.root,
562
+ debug: _debug,
563
+ });
558
564
  console.log('');
559
565
  console.log('╔══════════════════════════════════════════════════════════════╗');
560
566
  console.log('║ Build Complete! 🎉 ║');
package/dist/bin/build.js CHANGED
@@ -17,6 +17,7 @@ const structure_validator_1 = require("../server/structure-validator");
17
17
  const structure_log_1 = require("../server/structure-log");
18
18
  const devtools_indicator_snippet_1 = require("./devtools-indicator-snippet");
19
19
  const dev_error_overlay_snippet_1 = require("./dev-error-overlay-snippet");
20
+ const deploy_output_1 = require("./deploy-output");
20
21
  const _debug = !!process.env.VISTA_DEBUG;
21
22
  // Helper to run PostCSS
22
23
  function runPostCSS(cwd, vistaDir) {
@@ -418,6 +419,11 @@ async function buildClient(watch = false, onRebuild) {
418
419
  console.log(stats?.toString('minimal'));
419
420
  // Build CSS
420
421
  runPostCSS(cwd, vistaDir);
422
+ (0, deploy_output_1.generateDeploymentOutputs)({
423
+ cwd,
424
+ vistaDir,
425
+ debug: _debug,
426
+ });
421
427
  compiler.close((closeErr) => {
422
428
  if (closeErr)
423
429
  console.error('Error closing compiler:', closeErr);
@@ -0,0 +1,7 @@
1
+ interface DeployOutputOptions {
2
+ cwd: string;
3
+ vistaDir: string;
4
+ debug?: boolean;
5
+ }
6
+ export declare function generateDeploymentOutputs(options: DeployOutputOptions): void;
7
+ export {};
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateDeploymentOutputs = generateDeploymentOutputs;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function isVercelBuildEnvironment() {
10
+ return process.env.VERCEL === '1' || process.env.NOW_REGION !== undefined;
11
+ }
12
+ function hasUserVercelConfig(cwd) {
13
+ return fs_1.default.existsSync(path_1.default.join(cwd, 'vercel.json'));
14
+ }
15
+ function copyDirectoryRecursive(sourceDir, targetDir) {
16
+ if (!fs_1.default.existsSync(sourceDir))
17
+ return;
18
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
19
+ const entries = fs_1.default.readdirSync(sourceDir, { withFileTypes: true });
20
+ for (const entry of entries) {
21
+ const from = path_1.default.join(sourceDir, entry.name);
22
+ const to = path_1.default.join(targetDir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ copyDirectoryRecursive(from, to);
25
+ }
26
+ else if (entry.isFile()) {
27
+ fs_1.default.copyFileSync(from, to);
28
+ }
29
+ }
30
+ }
31
+ function copyFileIfPresent(sourceFile, targetFile) {
32
+ if (!fs_1.default.existsSync(sourceFile))
33
+ return;
34
+ fs_1.default.mkdirSync(path_1.default.dirname(targetFile), { recursive: true });
35
+ fs_1.default.copyFileSync(sourceFile, targetFile);
36
+ }
37
+ function writeVercelBuildOutput(options) {
38
+ const { cwd, vistaDir, debug } = options;
39
+ if (!isVercelBuildEnvironment()) {
40
+ return;
41
+ }
42
+ if (hasUserVercelConfig(cwd)) {
43
+ if (debug) {
44
+ console.log('[vista:deploy] Found custom vercel.json, skipping internal Vercel output.');
45
+ }
46
+ return;
47
+ }
48
+ const vercelOutputDir = path_1.default.join(cwd, '.vercel', 'output');
49
+ const vercelStaticDir = path_1.default.join(vercelOutputDir, 'static');
50
+ fs_1.default.rmSync(vercelOutputDir, { recursive: true, force: true });
51
+ fs_1.default.mkdirSync(vercelStaticDir, { recursive: true });
52
+ // Public assets: /favicon.ico, /vista.svg, etc.
53
+ copyDirectoryRecursive(path_1.default.join(cwd, 'public'), vercelStaticDir);
54
+ // Vista static artifacts: /static/pages, /static/chunks, etc.
55
+ copyDirectoryRecursive(path_1.default.join(vistaDir, 'static'), path_1.default.join(vercelStaticDir, 'static'));
56
+ // Global CSS alias support (/styles.css and /client.css)
57
+ const clientCssPath = path_1.default.join(vistaDir, 'client.css');
58
+ copyFileIfPresent(clientCssPath, path_1.default.join(vercelStaticDir, 'client.css'));
59
+ copyFileIfPresent(clientCssPath, path_1.default.join(vercelStaticDir, 'styles.css'));
60
+ const config = {
61
+ version: 3,
62
+ routes: [
63
+ { handle: 'filesystem' },
64
+ { src: '^/_vista/(.*)$', dest: '/$1' },
65
+ { src: '^/(?:rsc|_rsc)/?$', dest: '/static/pages/index.rsc' },
66
+ { src: '^/(?:rsc|_rsc)/(.+)$', dest: '/static/pages/$1.rsc' },
67
+ { src: '^/$', dest: '/static/pages/index.html' },
68
+ { src: '^/(.+)$', dest: '/static/pages/$1.html' },
69
+ ],
70
+ };
71
+ fs_1.default.writeFileSync(path_1.default.join(vercelOutputDir, 'config.json'), JSON.stringify(config, null, 2));
72
+ if (debug) {
73
+ console.log('[vista:deploy] Generated internal Vercel Build Output at .vercel/output/');
74
+ }
75
+ }
76
+ function generateDeploymentOutputs(options) {
77
+ writeVercelBuildOutput(options);
78
+ }
@@ -22,7 +22,7 @@ export interface VistaDirs {
22
22
  }
23
23
  /**
24
24
  * Create the .vista directory structure.
25
- * In legacy mode, only creates root + cache (no empty server/static dirs).
25
+ * In legacy mode, only creates root (no empty server/static dirs).
26
26
  * In RSC mode, creates the full structure for server/client bundles.
27
27
  */
28
28
  export declare function createVistaDirectories(cwd: string, mode?: 'legacy' | 'rsc'): VistaDirs;
@@ -54,7 +54,7 @@ function getBuildId(vistaDir, forceNew = false) {
54
54
  }
55
55
  /**
56
56
  * Create the .vista directory structure.
57
- * In legacy mode, only creates root + cache (no empty server/static dirs).
57
+ * In legacy mode, only creates root (no empty server/static dirs).
58
58
  * In RSC mode, creates the full structure for server/client bundles.
59
59
  */
60
60
  function createVistaDirectories(cwd, mode = 'legacy') {
@@ -71,8 +71,10 @@ function createVistaDirectories(cwd, mode = 'legacy') {
71
71
  // Always create root
72
72
  fs_1.default.mkdirSync(root, { recursive: true });
73
73
  if (mode === 'rsc') {
74
- // RSC mode: create full directory tree
75
- Object.values(dirs).forEach((dir) => {
74
+ // RSC mode: create only currently-used directories.
75
+ // Keep css/media paths reserved in `dirs` for future use, but do not
76
+ // create empty folders unless the build actually emits files there.
77
+ [dirs.root, dirs.cache, dirs.server, dirs.static, dirs.chunks].forEach((dir) => {
76
78
  fs_1.default.mkdirSync(dir, { recursive: true });
77
79
  });
78
80
  // Cache subdirectories
@@ -25,14 +25,14 @@ const wrapperStyle = {
25
25
  overflow: 'hidden',
26
26
  };
27
27
  exports.Image = (0, react_1.forwardRef)((props, ref) => {
28
- const { placeholder, blurDataURL, onLoadingComplete, priority, ...restProps } = props;
28
+ const { placeholder, blurDataURL, onLoadingComplete, onError, priority, ...restProps } = props;
29
29
  const [isLoaded, setIsLoaded] = (0, react_1.useState)(false);
30
- const [isInView, setIsInView] = (0, react_1.useState)(priority || false);
31
- const imgRef = (0, react_1.useRef)(null);
32
- const observerRef = (0, react_1.useRef)(null);
30
+ const [useDirectSrc, setUseDirectSrc] = (0, react_1.useState)(false);
33
31
  // Combine refs
34
32
  const setRefs = (0, react_1.useCallback)((node) => {
35
- imgRef.current = node;
33
+ if (node && node.complete && node.naturalWidth > 0) {
34
+ setIsLoaded(true);
35
+ }
36
36
  if (typeof ref === 'function') {
37
37
  ref(node);
38
38
  }
@@ -40,31 +40,6 @@ exports.Image = (0, react_1.forwardRef)((props, ref) => {
40
40
  ref.current = node;
41
41
  }
42
42
  }, [ref]);
43
- // IntersectionObserver for lazy loading
44
- (0, react_1.useEffect)(() => {
45
- if (priority) {
46
- setIsInView(true);
47
- return;
48
- }
49
- const element = imgRef.current;
50
- if (!element)
51
- return;
52
- observerRef.current = new IntersectionObserver((entries) => {
53
- entries.forEach((entry) => {
54
- if (entry.isIntersecting) {
55
- setIsInView(true);
56
- observerRef.current?.disconnect();
57
- }
58
- });
59
- }, {
60
- rootMargin: '200px', // Start loading 200px before viewport
61
- threshold: 0,
62
- });
63
- observerRef.current.observe(element);
64
- return () => {
65
- observerRef.current?.disconnect();
66
- };
67
- }, [priority]);
68
43
  // Handle image load complete
69
44
  const handleLoad = (0, react_1.useCallback)((event) => {
70
45
  const img = event.currentTarget;
@@ -76,21 +51,31 @@ exports.Image = (0, react_1.forwardRef)((props, ref) => {
76
51
  });
77
52
  }
78
53
  }, [onLoadingComplete]);
54
+ const handleError = (0, react_1.useCallback)((event) => {
55
+ if (!useDirectSrc) {
56
+ // If optimizer endpoint fails (common on static hosts), fall back to raw src.
57
+ setUseDirectSrc(true);
58
+ }
59
+ if (typeof onError === 'function') {
60
+ onError(event);
61
+ }
62
+ }, [onError, useDirectSrc]);
79
63
  // Get processed img props
80
- const imgProps = (0, get_img_props_1.getImgProps)({ ...restProps, priority }, image_config_1.imageConfigDefault, image_loader_1.defaultLoader);
64
+ const imgProps = (0, get_img_props_1.getImgProps)({ ...restProps, priority, unoptimized: useDirectSrc || restProps.unoptimized }, image_config_1.imageConfigDefault, image_loader_1.defaultLoader);
65
+ const fallbackSrc = String(restProps.src || '');
66
+ const effectiveSrc = useDirectSrc ? fallbackSrc : imgProps.src;
67
+ const effectiveSrcSet = useDirectSrc ? undefined : imgProps.srcSet;
81
68
  // Determine if we should show blur placeholder
82
69
  const showBlur = placeholder === 'blur' && blurDataURL && !isLoaded;
83
70
  const needsWrapper = showBlur;
84
71
  // Render image element
85
- const imageElement = ((0, jsx_runtime_1.jsx)("img", { ...imgProps, ref: setRefs, onLoad: handleLoad, style: {
72
+ const imageElement = ((0, jsx_runtime_1.jsx)("img", { ...imgProps, ref: setRefs, onLoad: handleLoad, onError: handleError, style: {
86
73
  ...imgProps.style,
87
74
  ...(showBlur ? {
88
75
  opacity: isLoaded ? 1 : 0,
89
76
  transition: 'opacity 0.5s ease-in-out'
90
77
  } : {}),
91
- },
92
- // Only set src when in view (lazy loading)
93
- src: isInView ? imgProps.src : undefined, srcSet: isInView ? imgProps.srcSet : undefined, decoding: priority ? 'sync' : 'async', fetchPriority: priority ? 'high' : undefined }));
78
+ }, src: effectiveSrc, srcSet: effectiveSrcSet, decoding: priority ? 'sync' : 'async', fetchPriority: priority ? 'high' : undefined }));
94
79
  // Wrap with blur placeholder if needed
95
80
  if (needsWrapper) {
96
81
  return ((0, jsx_runtime_1.jsxs)("span", { style: {
@@ -844,12 +844,14 @@ function startRSCServer(options = {}) {
844
844
  }
845
845
  let serverManifest = JSON.parse(fs_1.default.readFileSync(serverManifestPath, 'utf-8'));
846
846
  // ========================================================================
847
- // Load pre-rendered static pages from disk into in-memory cache
847
+ // Load pre-rendered static pages from disk into in-memory cache (production)
848
848
  // ========================================================================
849
849
  const vistaDirRoot = path_1.default.join(cwd, constants_1.BUILD_DIR);
850
- const loadedStaticPages = (0, static_cache_1.loadStaticPagesFromDisk)(vistaDirRoot);
851
- if (loadedStaticPages > 0) {
852
- (0, logger_1.logInfo)(`Loaded ${loadedStaticPages} pre-rendered page(s) from cache`);
850
+ if (!isDev) {
851
+ const loadedStaticPages = (0, static_cache_1.loadStaticPagesFromDisk)(vistaDirRoot);
852
+ if (loadedStaticPages > 0) {
853
+ (0, logger_1.logInfo)(`Loaded ${loadedStaticPages} pre-rendered page(s) from cache`);
854
+ }
853
855
  }
854
856
  const upstreamChild = spawnUpstream(cwd, upstreamPort);
855
857
  upstreamChild.stdout.setEncoding('utf8');
@@ -1254,7 +1256,7 @@ function startRSCServer(options = {}) {
1254
1256
  // For ISR pages whose revalidate window has expired, serve the stale
1255
1257
  // cached version immediately and kick off background revalidation.
1256
1258
  // ==================================================================
1257
- {
1259
+ if (!isDev) {
1258
1260
  const cached = (0, static_cache_1.getCachedPage)(req.path);
1259
1261
  if (cached.page) {
1260
1262
  if (cached.stale) {
@@ -16,6 +16,7 @@ exports.generateStaticPages = generateStaticPages;
16
16
  exports.revalidatePath = revalidatePath;
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const fs_1 = __importDefault(require("fs"));
19
+ const child_process_1 = require("child_process");
19
20
  const constants_1 = require("../constants");
20
21
  const static_cache_1 = require("./static-cache");
21
22
  const CjsModule = require('module');
@@ -325,6 +326,116 @@ function getChunkScripts(cwd) {
325
326
  return '';
326
327
  }
327
328
  }
329
+ function resolveUpstreamScriptPath() {
330
+ const jsPath = path_1.default.join(__dirname, 'rsc-upstream.js');
331
+ if (fs_1.default.existsSync(jsPath)) {
332
+ return jsPath;
333
+ }
334
+ const tsPath = path_1.default.join(__dirname, 'rsc-upstream.ts');
335
+ if (fs_1.default.existsSync(tsPath)) {
336
+ return tsPath;
337
+ }
338
+ return null;
339
+ }
340
+ function waitForUpstreamReady(child, timeoutMs) {
341
+ return new Promise((resolve, reject) => {
342
+ let logs = '';
343
+ const timer = setTimeout(() => {
344
+ cleanup();
345
+ reject(new Error(`[vista:ssg] Timed out waiting for RSC upstream readiness (${timeoutMs}ms)\n${logs}`));
346
+ }, timeoutMs);
347
+ const cleanup = () => {
348
+ clearTimeout(timer);
349
+ child.stdout.removeListener('data', onData);
350
+ child.stderr.removeListener('data', onData);
351
+ child.removeListener('exit', onExit);
352
+ child.removeListener('error', onError);
353
+ };
354
+ const onData = (chunk) => {
355
+ logs += chunk.toString();
356
+ if (logs.includes('Listening on')) {
357
+ cleanup();
358
+ resolve();
359
+ }
360
+ };
361
+ const onExit = (code) => {
362
+ cleanup();
363
+ reject(new Error(`[vista:ssg] RSC upstream exited before readiness (code: ${code ?? 'unknown'})`));
364
+ };
365
+ const onError = (error) => {
366
+ cleanup();
367
+ reject(error);
368
+ };
369
+ child.stdout.on('data', onData);
370
+ child.stderr.on('data', onData);
371
+ child.once('exit', onExit);
372
+ child.once('error', onError);
373
+ });
374
+ }
375
+ async function startStaticFlightUpstream(cwd) {
376
+ const upstreamScript = resolveUpstreamScriptPath();
377
+ if (!upstreamScript) {
378
+ return null;
379
+ }
380
+ const port = Number(process.env.VISTA_STATIC_RSC_PORT || 3181);
381
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
382
+ return null;
383
+ }
384
+ const child = (0, child_process_1.spawn)(process.execPath, ['--conditions', 'react-server', upstreamScript, '--port', String(port)], {
385
+ cwd,
386
+ env: {
387
+ ...process.env,
388
+ NODE_ENV: process.env.NODE_ENV || 'production',
389
+ RSC_UPSTREAM_PORT: String(port),
390
+ },
391
+ stdio: 'pipe',
392
+ });
393
+ await waitForUpstreamReady(child, 12000);
394
+ const close = async () => {
395
+ if (child.killed)
396
+ return;
397
+ await new Promise((resolve) => {
398
+ const timeout = setTimeout(() => {
399
+ try {
400
+ child.kill('SIGKILL');
401
+ }
402
+ catch {
403
+ // ignore force-kill failures
404
+ }
405
+ }, 2500);
406
+ child.once('exit', () => {
407
+ clearTimeout(timeout);
408
+ resolve();
409
+ });
410
+ try {
411
+ child.kill();
412
+ }
413
+ catch {
414
+ clearTimeout(timeout);
415
+ resolve();
416
+ }
417
+ });
418
+ };
419
+ return {
420
+ async fetchFlight(urlPath) {
421
+ const normalizedPath = urlPath === '/' ? '' : urlPath;
422
+ const flightUrl = `http://127.0.0.1:${port}/rsc${normalizedPath}`;
423
+ try {
424
+ const response = await fetch(flightUrl, {
425
+ headers: { Accept: 'text/x-component' },
426
+ });
427
+ if (!response.ok) {
428
+ return undefined;
429
+ }
430
+ return await response.text();
431
+ }
432
+ catch {
433
+ return undefined;
434
+ }
435
+ },
436
+ close,
437
+ };
438
+ }
328
439
  function wrapInDocument(bodyHtml, _urlPath, metadataHtml, cwd) {
329
440
  const headInjection = `\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n ${metadataHtml}\n ${getCSSLinks(cwd)}`;
330
441
  const scripts = getChunkScripts(cwd);
@@ -373,32 +484,26 @@ async function generateStaticPages(options) {
373
484
  }
374
485
  const staticRoutes = manifest.routes.filter((r) => r.renderMode === 'static' || r.renderMode === 'isr');
375
486
  console.log(`[vista:ssg] Found ${staticRoutes.length} routes eligible for static generation`);
376
- for (const route of staticRoutes) {
377
- if (route.type === 'static') {
378
- // Simple static route — single URL
379
- const urlPath = route.pattern;
380
- const page = await prerenderPage(urlPath, route, undefined, cwd);
381
- if (page) {
382
- (0, static_cache_1.setCachedPage)(urlPath, page);
383
- (0, static_cache_1.writeStaticPageToDisk)(vistaDirRoot, urlPath, page);
384
- result.generatedPaths.push(urlPath);
385
- result.pagesGenerated++;
386
- }
387
- else {
388
- result.failedPaths.push({ path: urlPath, error: 'Prerender returned null' });
389
- }
390
- }
391
- else if (route.hasGenerateStaticParams) {
392
- // Dynamic route with generateStaticParams — expand to concrete URLs
393
- const paramSets = await resolveStaticParams(route, cwd);
394
- if (paramSets.length === 0) {
395
- console.log(`[vista:ssg] No static params for ${route.pattern} — will render on demand`);
396
- continue;
397
- }
398
- for (const params of paramSets) {
399
- const urlPath = expandPattern(route.pattern, params);
400
- const page = await prerenderPage(urlPath, route, params, cwd);
487
+ let flightUpstream = null;
488
+ try {
489
+ flightUpstream = await startStaticFlightUpstream(cwd);
490
+ }
491
+ catch (flightError) {
492
+ console.warn(`[vista:ssg] Flight payload pre-generation disabled: ${flightError.message}`);
493
+ }
494
+ try {
495
+ for (const route of staticRoutes) {
496
+ if (route.type === 'static') {
497
+ // Simple static route — single URL
498
+ const urlPath = route.pattern;
499
+ const page = await prerenderPage(urlPath, route, undefined, cwd);
401
500
  if (page) {
501
+ if (flightUpstream) {
502
+ const flightData = await flightUpstream.fetchFlight(urlPath);
503
+ if (flightData) {
504
+ page.flightData = flightData;
505
+ }
506
+ }
402
507
  (0, static_cache_1.setCachedPage)(urlPath, page);
403
508
  (0, static_cache_1.writeStaticPageToDisk)(vistaDirRoot, urlPath, page);
404
509
  result.generatedPaths.push(urlPath);
@@ -408,6 +513,38 @@ async function generateStaticPages(options) {
408
513
  result.failedPaths.push({ path: urlPath, error: 'Prerender returned null' });
409
514
  }
410
515
  }
516
+ else if (route.hasGenerateStaticParams) {
517
+ // Dynamic route with generateStaticParams — expand to concrete URLs
518
+ const paramSets = await resolveStaticParams(route, cwd);
519
+ if (paramSets.length === 0) {
520
+ console.log(`[vista:ssg] No static params for ${route.pattern} — will render on demand`);
521
+ continue;
522
+ }
523
+ for (const params of paramSets) {
524
+ const urlPath = expandPattern(route.pattern, params);
525
+ const page = await prerenderPage(urlPath, route, params, cwd);
526
+ if (page) {
527
+ if (flightUpstream) {
528
+ const flightData = await flightUpstream.fetchFlight(urlPath);
529
+ if (flightData) {
530
+ page.flightData = flightData;
531
+ }
532
+ }
533
+ (0, static_cache_1.setCachedPage)(urlPath, page);
534
+ (0, static_cache_1.writeStaticPageToDisk)(vistaDirRoot, urlPath, page);
535
+ result.generatedPaths.push(urlPath);
536
+ result.pagesGenerated++;
537
+ }
538
+ else {
539
+ result.failedPaths.push({ path: urlPath, error: 'Prerender returned null' });
540
+ }
541
+ }
542
+ }
543
+ }
544
+ }
545
+ finally {
546
+ if (flightUpstream) {
547
+ await flightUpstream.close();
411
548
  }
412
549
  }
413
550
  // Generate prerender manifest
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vistagenic/vista",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "The React Framework for Visionaries - Rust-powered SSR with Server Components",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",