vista-core-js 0.0.2

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.
Files changed (53) hide show
  1. package/dist/ErrorOverlay.d.ts +7 -0
  2. package/dist/ErrorOverlay.js +68 -0
  3. package/dist/app.d.ts +21 -0
  4. package/dist/app.js +119 -0
  5. package/dist/client/link.d.ts +23 -0
  6. package/dist/client/link.js +42 -0
  7. package/dist/client.d.ts +2 -0
  8. package/dist/client.js +290 -0
  9. package/dist/components/PixelBlast.d.ts +28 -0
  10. package/dist/components/PixelBlast.js +584 -0
  11. package/dist/entry-client.d.ts +1 -0
  12. package/dist/entry-client.js +56 -0
  13. package/dist/entry-server.d.ts +9 -0
  14. package/dist/entry-server.js +33 -0
  15. package/dist/error-overlay.d.ts +1 -0
  16. package/dist/error-overlay.js +166 -0
  17. package/dist/font/google/index.d.ts +1923 -0
  18. package/dist/font/google/index.js +1948 -0
  19. package/dist/image/get-img-props.d.ts +20 -0
  20. package/dist/image/get-img-props.js +46 -0
  21. package/dist/image/image-config.d.ts +20 -0
  22. package/dist/image/image-config.js +17 -0
  23. package/dist/image/image-loader.d.ts +7 -0
  24. package/dist/image/image-loader.js +10 -0
  25. package/dist/image/index.d.ts +12 -0
  26. package/dist/image/index.js +12 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.js +4 -0
  29. package/dist/plugin.d.ts +5 -0
  30. package/dist/plugin.js +88 -0
  31. package/dist/router.d.ts +18 -0
  32. package/dist/router.js +55 -0
  33. package/package.json +47 -0
  34. package/src/ErrorOverlay.tsx +194 -0
  35. package/src/app.tsx +138 -0
  36. package/src/assets/vista.gif +0 -0
  37. package/src/client/link.tsx +85 -0
  38. package/src/client.tsx +368 -0
  39. package/src/entry-client.tsx +70 -0
  40. package/src/entry-server.tsx +58 -0
  41. package/src/error-overlay.ts +187 -0
  42. package/src/font/google/index.d.ts +19011 -0
  43. package/src/font/google/index.ts +1968 -0
  44. package/src/font/types.d.ts +13 -0
  45. package/src/image/get-img-props.ts +100 -0
  46. package/src/image/image-config.ts +22 -0
  47. package/src/image/image-loader.ts +23 -0
  48. package/src/image/index.tsx +21 -0
  49. package/src/index.ts +7 -0
  50. package/src/plugin.ts +100 -0
  51. package/src/router-loader.ts +51 -0
  52. package/src/router.tsx +80 -0
  53. package/tsconfig.json +23 -0
@@ -0,0 +1,13 @@
1
+ export type CssVariable = `--${string}`;
2
+ export type Display = 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
3
+ export type NextFont = {
4
+ className: string;
5
+ style: {
6
+ fontFamily: string;
7
+ fontWeight?: number;
8
+ fontStyle?: string;
9
+ };
10
+ };
11
+ export type NextFontWithVariable = NextFont & {
12
+ variable: string;
13
+ };
@@ -0,0 +1,100 @@
1
+
2
+ import { ImageConfigComplete, imageConfigDefault } from './image-config';
3
+ import { ImageLoader } from './image-loader';
4
+
5
+ export type ImageProps = React.ImgHTMLAttributes<HTMLImageElement> & {
6
+ src: string;
7
+ alt: string;
8
+ width?: number | string;
9
+ height?: number | string;
10
+ fill?: boolean;
11
+ loader?: ImageLoader;
12
+ quality?: number | string;
13
+ priority?: boolean;
14
+ unoptimized?: boolean;
15
+ };
16
+
17
+ type ImgProps = React.ImgHTMLAttributes<HTMLImageElement> & {
18
+ width?: number;
19
+ height?: number;
20
+ srcSet?: string;
21
+ };
22
+
23
+ // Helper: Generate srcSet
24
+ function generateSrcSet(
25
+ src: string,
26
+ width: number | undefined,
27
+ loader: ImageLoader,
28
+ config: ImageConfigComplete,
29
+ unoptimized: boolean
30
+ ): string | undefined {
31
+ if (unoptimized) return undefined;
32
+
33
+ const { deviceSizes, imageSizes } = config;
34
+ const sizes = [...deviceSizes, ...imageSizes].sort((a, b) => a - b);
35
+
36
+ // If width is known, only generate sizes up to that width (plus maybe 2x/3x for density)
37
+ // For simplicity MVP, we'll generate device sizes.
38
+
39
+ return sizes
40
+ .map((size) => {
41
+ const url = loader({ src, width: size });
42
+ return `${url} ${size}w`;
43
+ })
44
+ .join(', ');
45
+ }
46
+
47
+ export function getImgProps(
48
+ props: ImageProps,
49
+ config: ImageConfigComplete = imageConfigDefault,
50
+ defaultLoader: ImageLoader
51
+ ): ImgProps {
52
+ const {
53
+ src,
54
+ alt,
55
+ width,
56
+ height,
57
+ fill,
58
+ loader = defaultLoader,
59
+ quality,
60
+ priority,
61
+ unoptimized,
62
+ style,
63
+ sizes,
64
+ className,
65
+ loading,
66
+ ...rest
67
+ } = props;
68
+
69
+ const imgStyle: React.CSSProperties = { ...style };
70
+
71
+ // Handle Fill Mode
72
+ if (fill) {
73
+ imgStyle.position = 'absolute';
74
+ imgStyle.height = '100%';
75
+ imgStyle.width = '100%';
76
+ imgStyle.inset = 0;
77
+ imgStyle.objectFit = 'cover'; // Default to cover for bg images
78
+ }
79
+
80
+ // Handle Dimensions
81
+ let widthInt = width ? Number(width) : undefined;
82
+ let heightInt = height ? Number(height) : undefined;
83
+
84
+ // Generate SrcSet
85
+ const srcSet = generateSrcSet(src, widthInt, loader, config, !!unoptimized);
86
+
87
+ return {
88
+ ...rest,
89
+ src,
90
+ alt,
91
+ width: widthInt,
92
+ height: heightInt,
93
+ loading: priority ? 'eager' : (loading || 'lazy'),
94
+ // fetchPriority: priority ? 'high' : undefined, // React types might strict on this
95
+ style: imgStyle,
96
+ sizes: sizes || (fill ? '100vw' : undefined),
97
+ srcSet,
98
+ className,
99
+ };
100
+ }
@@ -0,0 +1,22 @@
1
+
2
+ export const imageConfigDefault = {
3
+ deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
4
+ imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
5
+ path: '/_next/image',
6
+ loader: 'default' as const,
7
+ loaderFile: '',
8
+ domains: [],
9
+ disableStaticImages: false,
10
+ minimumCacheTTL: 60,
11
+ formats: ['image/webp'] as const,
12
+ dangerouslyAllowSVG: false,
13
+ contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;",
14
+ contentDispositionType: 'inline' as const,
15
+ remotePatterns: [],
16
+ unoptimized: false,
17
+ }
18
+
19
+ export type ImageConfigComplete = typeof imageConfigDefault
20
+ export type ImageConfig = Partial<ImageConfigComplete>
21
+ export const VALID_LOADERS = ['default', 'imgix', 'cloudinary', 'akamai', 'custom'] as const
22
+ export type LoaderValue = typeof VALID_LOADERS[number]
@@ -0,0 +1,23 @@
1
+
2
+ import { ImageConfig } from './image-config';
3
+
4
+ export type ImageLoaderProps = {
5
+ src: string;
6
+ width: number;
7
+ quality?: number;
8
+ };
9
+
10
+ export type ImageLoader = (p: ImageLoaderProps) => string;
11
+
12
+ // Default loader: simple pass-through for now,
13
+ // envisioning a future where this hits an optimization endpoint.
14
+ export const defaultLoader: ImageLoader = ({ src, width, quality }) => {
15
+ // If user wants optimization, they would hook up a real loader here.
16
+ // For static dev, we just return the src.
17
+ // In a real Next.js app, this would be: `/_next/image?url=${encodeURIComponent(src)}&w=${width}&q=${quality || 75}`
18
+
19
+ // NOTE: Emulating optimizing behavior by appending query params for now (if backend supported it)
20
+ // return `${src}?w=${width}&q=${quality || 75}`;
21
+
22
+ return src;
23
+ };
@@ -0,0 +1,21 @@
1
+
2
+ import React, { forwardRef } from 'react';
3
+ import { getImgProps, ImageProps } from './get-img-props';
4
+ import { imageConfigDefault } from './image-config';
5
+ import { defaultLoader } from './image-loader';
6
+
7
+ export const Image = forwardRef<HTMLImageElement, ImageProps>((props, ref) => {
8
+ // In a real implementation, we might consume config from a Context Provider
9
+ // For now, we use the default config.
10
+
11
+ const imgProps = getImgProps(props, imageConfigDefault, defaultLoader);
12
+
13
+ return (
14
+ <img
15
+ {...imgProps}
16
+ ref={ref}
17
+ />
18
+ );
19
+ });
20
+
21
+ Image.displayName = 'Image';
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { useRouter, Link } from './router.js';
2
+ import { mount, mountError } from './client.js';
3
+
4
+ export { Image } from './image/index.js';
5
+ export type { ImageProps } from './image/get-img-props.js';
6
+ export type { ImageLoaderProps } from './image/image-loader.js';
7
+ export { useRouter, mount, mountError, Link };
package/src/plugin.ts ADDED
@@ -0,0 +1,100 @@
1
+ import type { Plugin } from 'vite';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const clientPath = path.resolve(__dirname, './client.tsx'); // Resolves to src/client.tsx
7
+
8
+ export interface VistaPluginOptions {
9
+ logo?: string;
10
+ }
11
+
12
+ export function vistaPlugin(options: VistaPluginOptions = {}): Plugin {
13
+ const logoPath = options.logo || '/favicon.ico';
14
+
15
+ return {
16
+ name: 'vista-core',
17
+ enforce: 'pre',
18
+ configureServer(server) {
19
+ if (server.config.appType === 'custom') {
20
+ return; // Skip SPA middleware for SSR
21
+ }
22
+ // Disable default Vite overlay
23
+ server.config.server.hmr = {
24
+ ...(typeof server.config.server.hmr === 'object' ? server.config.server.hmr : {}),
25
+ overlay: false
26
+ };
27
+
28
+ server.middlewares.use(async (req, res, next) => {
29
+ const url = req.url || '/';
30
+ const pathname = url.split('?')[0];
31
+
32
+ // SPA Fallback logic:
33
+ // Serve index.html if request is:
34
+ // 1. Root (/)
35
+ // 2. Not an internal Vite request (starts with /@)
36
+ // 3. Not a file with an extension (check pathname, ignore query params)
37
+ const isFile = pathname.includes('.');
38
+ const isInternal = url.startsWith('/@');
39
+
40
+ if (url === '/' || (!isFile && !isInternal)) {
41
+ const html = `
42
+ <!DOCTYPE html>
43
+ <html lang="en">
44
+ <head>
45
+ <meta charset="UTF-8" />
46
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
47
+ <title>Vista App</title>
48
+ <link rel="icon" href="${logoPath}" />
49
+ </head>
50
+ <body>
51
+ <div id="root"></div>
52
+ <script type="module">
53
+ import { mount, mountError } from '@vista/core';
54
+ console.log('[Vista] Bootstrapping...');
55
+ try {
56
+ // use lazy loading (default) to allow catching syntax errors per module
57
+ // APP Folder scanning: Scan app/**/index.tsx (Pages) and app/**/layout.tsx (Layouts)
58
+ const modules = import.meta.glob(['/app/**/index.tsx', '/app/**/layout.tsx', '/app/layout.tsx', '/app/index.tsx']); console.log('[Vista] Modules discovered:', Object.keys(modules));
59
+ mount(modules);
60
+ } catch (e) {
61
+ console.error('[Vista] Module load failed, calling mountError', e);
62
+ if (typeof mountError === 'function') {
63
+ mountError(e);
64
+ } else {
65
+ console.error('[Vista] mountError is NOT a function! Check exports.', mountError);
66
+ document.body.innerHTML = '<div style="color:red; padding:20px;">CRITICAL: mountError missing. Check console.</div>';
67
+ }
68
+ }
69
+ </script>
70
+ </body>
71
+ </html>
72
+ `;
73
+ // Transform the HTML to handle imports/HMR
74
+ const transformedHtml = await server.transformIndexHtml(url, html);
75
+
76
+ res.statusCode = 200;
77
+ res.setHeader('Content-Type', 'text/html');
78
+ res.end(transformedHtml);
79
+ return;
80
+ }
81
+ next();
82
+ });
83
+ },
84
+ resolveId(id) {
85
+ if (id === 'virtual:vista-routes') {
86
+ return '\0virtual:vista-routes';
87
+ }
88
+ },
89
+ load(id) {
90
+ if (id === '\0virtual:vista-routes') {
91
+ return `export const routes = []`;
92
+ }
93
+ },
94
+ transform(code, id) {
95
+ // REMOVED: <head> injection caused Hydration Mismatch because React 18 component tree didn't include it.
96
+ // We will enforce <head> being present in the User's Layout instead.
97
+ return null;
98
+ }
99
+ };
100
+ }
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+
3
+ // We need to define the types for the modules
4
+ type PageModule = { default: React.ComponentType<any> };
5
+ type LayoutModule = { default: React.ComponentType<any> };
6
+
7
+ // Lazy load all pages and layouts at module level so they can be accessed by loadRoute
8
+ // Note: Vite treats absolute paths in glob as relative to the project root
9
+ // FIX: Use eager: true to solve HMR instability with lazy boundaries
10
+ export const pages = import.meta.glob('/app/**/index.tsx', { eager: true });
11
+ export const layouts = import.meta.glob('/app/**/layout.tsx', { eager: true });
12
+ export const rootLayout = import.meta.glob('/app/layout.tsx', { eager: true });
13
+
14
+ // Helper to resolve route path to file key
15
+ export function getPageKey(path: string) {
16
+ const cleanPath = path === '/' ? '/' : path.replace(/\/$/, '');
17
+ return `/app${cleanPath === '/' ? '' : cleanPath}/index.tsx`;
18
+ }
19
+
20
+ // Exported loader for entry-client to preload the initial route
21
+ export async function loadRoute(path: string) {
22
+ const pageKey = getPageKey(path);
23
+ if (pages[pageKey]) {
24
+ try {
25
+ // Eager: pages[pageKey] is the module itself
26
+ const mod = pages[pageKey] as PageModule;
27
+ return mod.default;
28
+ } catch (e) {
29
+ console.error(`Failed to load route: ${path}`, e);
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+
35
+ // Exported loader for entry-client/server to preload the root layout and metadata
36
+ export async function loadRootLayout() {
37
+ const rootLayoutKey = '/app/layout.tsx';
38
+ if (rootLayout[rootLayoutKey]) {
39
+ try {
40
+ // Eager: rootLayout[rootLayoutKey] is the module itself
41
+ const mod = rootLayout[rootLayoutKey] as LayoutModule & { metadata?: any };
42
+ return {
43
+ Component: mod.default,
44
+ metadata: mod.metadata || {}
45
+ };
46
+ } catch (e) {
47
+ console.error('Failed to load root layout', e);
48
+ }
49
+ }
50
+ return { Component: null, metadata: {} };
51
+ }
package/src/router.tsx ADDED
@@ -0,0 +1,80 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+
3
+ type RouterContextType = {
4
+ push: (path: string) => void;
5
+ replace: (path: string) => void;
6
+ back: () => void;
7
+ path: string;
8
+ };
9
+
10
+ // Create Context
11
+ const RouterContext = createContext<RouterContextType | null>(null);
12
+
13
+ // Hook
14
+ export function useRouter() {
15
+ const context = useContext(RouterContext);
16
+ if (!context) {
17
+ throw new Error('useRouter must be used within a RouterProvider. This is handled automatically by Vista.');
18
+ }
19
+ return context;
20
+ }
21
+
22
+ // Internal Provider
23
+ export function RouterProvider({ children, url }: { children: React.ReactNode; url?: string }) {
24
+ const [path, setPath] = useState(() => {
25
+ if (url) return url;
26
+ if (typeof window !== 'undefined') return window.location.pathname;
27
+ return '/';
28
+ });
29
+
30
+ useEffect(() => {
31
+ if (typeof window === 'undefined') return;
32
+
33
+ const onPopState = () => setPath(window.location.pathname);
34
+ window.addEventListener('popstate', onPopState);
35
+ return () => window.removeEventListener('popstate', onPopState);
36
+ }, []);
37
+
38
+ // Import startTransition
39
+ const { startTransition } = React;
40
+
41
+ const push = (newPath: string) => {
42
+ window.history.pushState({}, '', newPath);
43
+ setPath(newPath);
44
+ };
45
+
46
+ const replace = (newPath: string) => {
47
+ window.history.replaceState({}, '', newPath);
48
+ setPath(newPath);
49
+ };
50
+
51
+ const back = () => {
52
+ window.history.back();
53
+ };
54
+
55
+ const value = { push, replace, back, path };
56
+
57
+ return (
58
+ <RouterContext.Provider value={value}>
59
+ {children}
60
+ </RouterContext.Provider>
61
+ );
62
+ }
63
+
64
+ export function Link({ href, children, className }: { href: string; children: React.ReactNode; className?: string }) {
65
+ const { push } = useRouter();
66
+
67
+ const handleClick = (e: React.MouseEvent) => {
68
+ // Allow default behavior for modifier keys (new tab)
69
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
70
+
71
+ e.preventDefault();
72
+ push(href);
73
+ };
74
+
75
+ return (
76
+ <a href={href} onClick={handleClick} className={className}>
77
+ {children}
78
+ </a>
79
+ );
80
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+
2
+ {
3
+ "compilerOptions": {
4
+ "target": "ESNext",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
7
+ "allowJs": false,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "strict": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "module": "ESNext",
14
+ "moduleResolution": "Node",
15
+ "resolveJsonModule": true,
16
+ "isolatedModules": true,
17
+ "jsx": "react-jsx",
18
+ "types": ["vite/client"],
19
+ "outDir": "./dist",
20
+ "declaration": true
21
+ },
22
+ "include": ["src"]
23
+ }