@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,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vista Server Component Loader
|
|
4
|
+
*
|
|
5
|
+
* Webpack loader that transforms server component imports in client bundles.
|
|
6
|
+
*
|
|
7
|
+
* When a server component is imported in a client bundle:
|
|
8
|
+
* 1. The loader detects if the file has 'client load' directive
|
|
9
|
+
* 2. If NOT a client component, replace the module with a proxy
|
|
10
|
+
* 3. The proxy provides helpful error messages when misused
|
|
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.default = serverComponentLoader;
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
/**
|
|
19
|
+
* Check if source has 'client load' directive
|
|
20
|
+
*/
|
|
21
|
+
function hasClientDirective(source) {
|
|
22
|
+
const trimmed = source.trim();
|
|
23
|
+
return trimmed.startsWith("'client load'") || trimmed.startsWith('"client load"');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract the component name from exports
|
|
27
|
+
*/
|
|
28
|
+
function extractComponentName(source) {
|
|
29
|
+
// Try to find "export default function ComponentName" or "export default ComponentName"
|
|
30
|
+
const defaultFuncMatch = source.match(/export\s+default\s+function\s+([A-Z][a-zA-Z0-9_]*)/);
|
|
31
|
+
if (defaultFuncMatch)
|
|
32
|
+
return defaultFuncMatch[1];
|
|
33
|
+
const defaultClassMatch = source.match(/export\s+default\s+class\s+([A-Z][a-zA-Z0-9_]*)/);
|
|
34
|
+
if (defaultClassMatch)
|
|
35
|
+
return defaultClassMatch[1];
|
|
36
|
+
const defaultConstMatch = source.match(/export\s+default\s+([A-Z][a-zA-Z0-9_]*)/);
|
|
37
|
+
if (defaultConstMatch)
|
|
38
|
+
return defaultConstMatch[1];
|
|
39
|
+
return 'ServerComponent';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Server Component Loader
|
|
43
|
+
*/
|
|
44
|
+
function serverComponentLoader(source) {
|
|
45
|
+
const options = this.getOptions();
|
|
46
|
+
const resourcePath = this.resourcePath;
|
|
47
|
+
// Only process files in the app directory
|
|
48
|
+
if (!resourcePath.startsWith(options.appDir)) {
|
|
49
|
+
return source;
|
|
50
|
+
}
|
|
51
|
+
// If this IS a client component, pass through unchanged
|
|
52
|
+
if (hasClientDirective(source)) {
|
|
53
|
+
return source;
|
|
54
|
+
}
|
|
55
|
+
// This is a server component - we need to check how it's being used
|
|
56
|
+
//
|
|
57
|
+
// The key insight: we're building the CLIENT bundle here.
|
|
58
|
+
// Server components should NOT be in the client bundle at all.
|
|
59
|
+
//
|
|
60
|
+
// However, there are valid use cases:
|
|
61
|
+
// 1. A client component might import a server component's TYPE only
|
|
62
|
+
// 2. A server component might be passed as children (rendered server-side)
|
|
63
|
+
//
|
|
64
|
+
// For safety, we replace the module with a proxy that:
|
|
65
|
+
// - Exports a function that throws a helpful error
|
|
66
|
+
// - Has a special marker so the RSC renderer can handle it
|
|
67
|
+
const relativePath = path_1.default.relative(options.appDir, resourcePath);
|
|
68
|
+
const componentId = `server:${relativePath.replace(/\\/g, '/').replace(/\.[jt]sx?$/, '')}`;
|
|
69
|
+
const componentName = extractComponentName(source);
|
|
70
|
+
// Generate proxy module
|
|
71
|
+
const proxyModule = `
|
|
72
|
+
// Vista Server Component Proxy
|
|
73
|
+
// This file was transformed because "${relativePath}" is a Server Component
|
|
74
|
+
// Server Components cannot be rendered on the client.
|
|
75
|
+
|
|
76
|
+
import * as React from 'react';
|
|
77
|
+
|
|
78
|
+
const componentId = ${JSON.stringify(componentId)};
|
|
79
|
+
const componentName = ${JSON.stringify(componentName)};
|
|
80
|
+
|
|
81
|
+
function ServerComponentError(props) {
|
|
82
|
+
if (typeof window !== 'undefined') {
|
|
83
|
+
console.error(
|
|
84
|
+
\`[Vista RSC] Attempted to render server component "\${componentName}" on the client.\`,
|
|
85
|
+
\`\\nServer components can only be rendered on the server.\`,
|
|
86
|
+
\`\\nTo fix: add 'client load' at the top of the file if you need client-side interactivity.\`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// In development, show an error UI
|
|
91
|
+
if (process.env.NODE_ENV === 'development') {
|
|
92
|
+
return React.createElement('div', {
|
|
93
|
+
style: {
|
|
94
|
+
padding: '20px',
|
|
95
|
+
background: '#fee2e2',
|
|
96
|
+
border: '2px solid #ef4444',
|
|
97
|
+
borderRadius: '8px',
|
|
98
|
+
color: '#991b1b',
|
|
99
|
+
fontFamily: 'monospace',
|
|
100
|
+
}
|
|
101
|
+
}, [
|
|
102
|
+
React.createElement('h3', { key: 'title', style: { margin: '0 0 10px 0' } }, '⚠️ Server Component Error'),
|
|
103
|
+
React.createElement('p', { key: 'msg', style: { margin: '0 0 10px 0' } },
|
|
104
|
+
\`Component "\${componentName}" is a Server Component and cannot be rendered on the client.\`
|
|
105
|
+
),
|
|
106
|
+
React.createElement('p', { key: 'fix', style: { margin: 0, fontSize: '12px' } },
|
|
107
|
+
"Add 'client load' at the top of the file to make it a Client Component."
|
|
108
|
+
),
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// In production, render nothing (the server should have rendered it)
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Mark as server component reference
|
|
117
|
+
ServerComponentError.$$typeof = Symbol.for('vista.server.reference');
|
|
118
|
+
ServerComponentError.$$id = componentId;
|
|
119
|
+
ServerComponentError.$$name = componentName;
|
|
120
|
+
|
|
121
|
+
// Re-export any named exports as proxies too
|
|
122
|
+
${extractNamedExports(source)
|
|
123
|
+
.map((name) => `
|
|
124
|
+
export const ${name} = ServerComponentError;
|
|
125
|
+
`)
|
|
126
|
+
.join('\n')}
|
|
127
|
+
|
|
128
|
+
export default ServerComponentError;
|
|
129
|
+
`;
|
|
130
|
+
return proxyModule;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Extract named exports from source
|
|
134
|
+
*/
|
|
135
|
+
function extractNamedExports(source) {
|
|
136
|
+
const exports = [];
|
|
137
|
+
// Match: export const/let/var Name, export function Name, export class Name
|
|
138
|
+
const namedRegex = /export\s+(?:const|let|var|function|class)\s+([A-Z][a-zA-Z0-9_]*)/g;
|
|
139
|
+
let match;
|
|
140
|
+
while ((match = namedRegex.exec(source)) !== null) {
|
|
141
|
+
exports.push(match[1]);
|
|
142
|
+
}
|
|
143
|
+
return exports;
|
|
144
|
+
}
|
|
145
|
+
// Also export the raw loader for Webpack
|
|
146
|
+
module.exports = serverComponentLoader;
|
|
147
|
+
module.exports.default = serverComponentLoader;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Component Manifest Generator
|
|
3
|
+
*
|
|
4
|
+
* Scans the app directory and builds a manifest of all Server Components.
|
|
5
|
+
* Server components are all components WITHOUT 'client load' directive.
|
|
6
|
+
*
|
|
7
|
+
* Server components:
|
|
8
|
+
* - Render on the server only
|
|
9
|
+
* - Have access to server resources (DB, file system, env vars)
|
|
10
|
+
* - Contribute 0kb to the client JavaScript bundle
|
|
11
|
+
*/
|
|
12
|
+
export interface ServerComponentEntry {
|
|
13
|
+
/** Unique ID for this component */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Relative path from app directory */
|
|
16
|
+
path: string;
|
|
17
|
+
/** Absolute file path */
|
|
18
|
+
absolutePath: string;
|
|
19
|
+
/** Component type: page, layout, loading, error, component */
|
|
20
|
+
type: 'page' | 'layout' | 'loading' | 'error' | 'not-found' | 'component';
|
|
21
|
+
/** Has static metadata export */
|
|
22
|
+
hasMetadata: boolean;
|
|
23
|
+
/** Has generateMetadata function */
|
|
24
|
+
hasGenerateMetadata: boolean;
|
|
25
|
+
/** List of client components this server component imports */
|
|
26
|
+
clientDependencies: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface ServerManifest {
|
|
29
|
+
/** Build ID */
|
|
30
|
+
buildId: string;
|
|
31
|
+
/** Map of module ID to server component info */
|
|
32
|
+
serverModules: Record<string, ServerComponentEntry>;
|
|
33
|
+
/** Map of path to module ID */
|
|
34
|
+
pathToId: Record<string, string>;
|
|
35
|
+
/** Routes discovered */
|
|
36
|
+
routes: RouteEntry[];
|
|
37
|
+
}
|
|
38
|
+
export interface RouteEntry {
|
|
39
|
+
/** URL path pattern */
|
|
40
|
+
pattern: string;
|
|
41
|
+
/** Page component path */
|
|
42
|
+
pagePath: string;
|
|
43
|
+
/** Layout component paths (from root to this route) */
|
|
44
|
+
layoutPaths: string[];
|
|
45
|
+
/** Loading component path if exists */
|
|
46
|
+
loadingPath?: string;
|
|
47
|
+
/** Error component path if exists */
|
|
48
|
+
errorPath?: string;
|
|
49
|
+
/** Route type */
|
|
50
|
+
type: 'static' | 'dynamic' | 'catch-all';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate the server component manifest
|
|
54
|
+
*/
|
|
55
|
+
export declare function generateServerManifest(cwd: string, appDir: string): ServerManifest;
|
|
56
|
+
/**
|
|
57
|
+
* Get server component by path
|
|
58
|
+
*/
|
|
59
|
+
export declare function getServerComponent(manifest: ServerManifest, filePath: string): ServerComponentEntry | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Check if a path is a server component
|
|
62
|
+
*/
|
|
63
|
+
export declare function isServerComponentPath(manifest: ServerManifest, filePath: string): boolean;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server Component Manifest Generator
|
|
4
|
+
*
|
|
5
|
+
* Scans the app directory and builds a manifest of all Server Components.
|
|
6
|
+
* Server components are all components WITHOUT 'client load' directive.
|
|
7
|
+
*
|
|
8
|
+
* Server components:
|
|
9
|
+
* - Render on the server only
|
|
10
|
+
* - Have access to server resources (DB, file system, env vars)
|
|
11
|
+
* - Contribute 0kb to the client JavaScript bundle
|
|
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.generateServerManifest = generateServerManifest;
|
|
18
|
+
exports.getServerComponent = getServerComponent;
|
|
19
|
+
exports.isServerComponentPath = isServerComponentPath;
|
|
20
|
+
const fs_1 = __importDefault(require("fs"));
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
// Try to load Rust NAPI bindings
|
|
23
|
+
let rustNative = null;
|
|
24
|
+
try {
|
|
25
|
+
const possiblePaths = [
|
|
26
|
+
path_1.default.resolve(__dirname, '../../../../../crates/vista-napi'),
|
|
27
|
+
path_1.default.resolve(__dirname, '../../../../crates/vista-napi'),
|
|
28
|
+
];
|
|
29
|
+
for (const p of possiblePaths) {
|
|
30
|
+
try {
|
|
31
|
+
rustNative = require(p);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
// Try next
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
// Fallback to JS
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if source has 'client load' directive
|
|
44
|
+
*/
|
|
45
|
+
function hasClientDirective(source) {
|
|
46
|
+
if (rustNative?.isClientComponent) {
|
|
47
|
+
return rustNative.isClientComponent(source);
|
|
48
|
+
}
|
|
49
|
+
const trimmed = source.trim();
|
|
50
|
+
return trimmed.startsWith("'client load'") || trimmed.startsWith('"client load"');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check for metadata exports
|
|
54
|
+
*/
|
|
55
|
+
function analyzeMetadata(source) {
|
|
56
|
+
if (rustNative?.analyzeMetadata) {
|
|
57
|
+
const result = rustNative.analyzeMetadata(source);
|
|
58
|
+
return {
|
|
59
|
+
hasMetadata: result.has_static_metadata,
|
|
60
|
+
hasGenerateMetadata: result.has_generate_metadata,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
hasMetadata: /export\s+const\s+metadata\b/.test(source),
|
|
65
|
+
hasGenerateMetadata: /export\s+(async\s+)?function\s+generateMetadata\b/.test(source),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Determine component type from file name
|
|
70
|
+
*/
|
|
71
|
+
function getComponentType(fileName) {
|
|
72
|
+
const base = path_1.default.basename(fileName).replace(/\.[jt]sx?$/, '');
|
|
73
|
+
switch (base) {
|
|
74
|
+
case 'page':
|
|
75
|
+
case 'index':
|
|
76
|
+
return 'page';
|
|
77
|
+
case 'layout':
|
|
78
|
+
case 'root':
|
|
79
|
+
return 'layout';
|
|
80
|
+
case 'loading':
|
|
81
|
+
return 'loading';
|
|
82
|
+
case 'error':
|
|
83
|
+
return 'error';
|
|
84
|
+
case 'not-found':
|
|
85
|
+
return 'not-found';
|
|
86
|
+
default:
|
|
87
|
+
return 'component';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Extract client component imports from source
|
|
92
|
+
*/
|
|
93
|
+
function extractClientImports(source, appDir) {
|
|
94
|
+
const imports = [];
|
|
95
|
+
// Match import statements
|
|
96
|
+
const importRegex = /import\s+(?:[\w\s{},*]+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
97
|
+
let match;
|
|
98
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
99
|
+
const importPath = match[1];
|
|
100
|
+
// Skip node_modules
|
|
101
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('/'))
|
|
102
|
+
continue;
|
|
103
|
+
// This is a relative import - we'd need to resolve and check if it's a client component
|
|
104
|
+
// For now, we'll mark it as a potential dependency
|
|
105
|
+
imports.push(importPath);
|
|
106
|
+
}
|
|
107
|
+
return imports;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generate unique module ID
|
|
111
|
+
*/
|
|
112
|
+
function generateModuleId(relativePath) {
|
|
113
|
+
const normalized = relativePath.replace(/\\/g, '/').replace(/\.[jt]sx?$/, '');
|
|
114
|
+
return `server:${normalized}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Scan directory recursively for server components
|
|
118
|
+
*/
|
|
119
|
+
function scanForServerComponents(dir, appDir, components) {
|
|
120
|
+
if (!fs_1.default.existsSync(dir))
|
|
121
|
+
return;
|
|
122
|
+
const items = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
const fullPath = path_1.default.join(dir, item.name);
|
|
125
|
+
if (item.isDirectory()) {
|
|
126
|
+
if (!item.name.startsWith('.') && item.name !== 'node_modules' && item.name !== 'api') {
|
|
127
|
+
scanForServerComponents(fullPath, appDir, components);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (item.isFile()) {
|
|
131
|
+
const ext = path_1.default.extname(item.name);
|
|
132
|
+
if (!['.tsx', '.ts', '.jsx', '.js'].includes(ext))
|
|
133
|
+
continue;
|
|
134
|
+
try {
|
|
135
|
+
const source = fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
136
|
+
// Only add if NOT a client component
|
|
137
|
+
if (!hasClientDirective(source)) {
|
|
138
|
+
const relativePath = path_1.default.relative(appDir, fullPath);
|
|
139
|
+
const moduleId = generateModuleId(relativePath);
|
|
140
|
+
const metadata = analyzeMetadata(source);
|
|
141
|
+
components.push({
|
|
142
|
+
id: moduleId,
|
|
143
|
+
path: relativePath,
|
|
144
|
+
absolutePath: fullPath,
|
|
145
|
+
type: getComponentType(item.name),
|
|
146
|
+
hasMetadata: metadata.hasMetadata,
|
|
147
|
+
hasGenerateMetadata: metadata.hasGenerateMetadata,
|
|
148
|
+
clientDependencies: extractClientImports(source, appDir),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
console.warn(`[Vista RSC] Failed to read ${fullPath}:`, e);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build route entries from discovered components
|
|
160
|
+
*/
|
|
161
|
+
function buildRoutes(components, appDir) {
|
|
162
|
+
const routes = [];
|
|
163
|
+
const pages = components.filter((c) => c.type === 'page');
|
|
164
|
+
const layouts = components.filter((c) => c.type === 'layout');
|
|
165
|
+
const loadings = components.filter((c) => c.type === 'loading');
|
|
166
|
+
const errors = components.filter((c) => c.type === 'error');
|
|
167
|
+
for (const page of pages) {
|
|
168
|
+
const pageDir = path_1.default.dirname(page.absolutePath);
|
|
169
|
+
const relativePath = path_1.default.relative(appDir, pageDir);
|
|
170
|
+
// Build URL pattern
|
|
171
|
+
let pattern = '/' + relativePath.replace(/\\/g, '/');
|
|
172
|
+
let routeType = 'static';
|
|
173
|
+
// Handle dynamic segments
|
|
174
|
+
pattern = pattern
|
|
175
|
+
.replace(/\[\.\.\.([^\]]+)\]/g, (_, name) => {
|
|
176
|
+
routeType = 'catch-all';
|
|
177
|
+
return `:${name}*`;
|
|
178
|
+
})
|
|
179
|
+
.replace(/\[([^\]]+)\]/g, (_, name) => {
|
|
180
|
+
if (routeType !== 'catch-all')
|
|
181
|
+
routeType = 'dynamic';
|
|
182
|
+
return `:${name}`;
|
|
183
|
+
});
|
|
184
|
+
// Handle route groups - remove (groupname) from URL
|
|
185
|
+
pattern = pattern.replace(/\/\([^)]+\)/g, '');
|
|
186
|
+
// Root page
|
|
187
|
+
if (pattern === '/' || pattern === '') {
|
|
188
|
+
pattern = '/';
|
|
189
|
+
}
|
|
190
|
+
// Find layouts in ancestor directories
|
|
191
|
+
const layoutPaths = [];
|
|
192
|
+
let currentDir = pageDir;
|
|
193
|
+
while (currentDir.startsWith(appDir)) {
|
|
194
|
+
const layout = layouts.find((l) => path_1.default.dirname(l.absolutePath) === currentDir);
|
|
195
|
+
if (layout) {
|
|
196
|
+
layoutPaths.unshift(layout.absolutePath);
|
|
197
|
+
}
|
|
198
|
+
const parent = path_1.default.dirname(currentDir);
|
|
199
|
+
if (parent === currentDir)
|
|
200
|
+
break;
|
|
201
|
+
currentDir = parent;
|
|
202
|
+
}
|
|
203
|
+
// Find loading and error in same directory
|
|
204
|
+
const loading = loadings.find((l) => path_1.default.dirname(l.absolutePath) === pageDir);
|
|
205
|
+
const error = errors.find((e) => path_1.default.dirname(e.absolutePath) === pageDir);
|
|
206
|
+
routes.push({
|
|
207
|
+
pattern,
|
|
208
|
+
pagePath: page.absolutePath,
|
|
209
|
+
layoutPaths,
|
|
210
|
+
loadingPath: loading?.absolutePath,
|
|
211
|
+
errorPath: error?.absolutePath,
|
|
212
|
+
type: routeType,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// Sort routes: static first, then dynamic, then catch-all
|
|
216
|
+
routes.sort((a, b) => {
|
|
217
|
+
const order = { static: 0, dynamic: 1, 'catch-all': 2 };
|
|
218
|
+
return order[a.type] - order[b.type];
|
|
219
|
+
});
|
|
220
|
+
return routes;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Generate the server component manifest
|
|
224
|
+
*/
|
|
225
|
+
function generateServerManifest(cwd, appDir) {
|
|
226
|
+
const components = [];
|
|
227
|
+
scanForServerComponents(appDir, appDir, components);
|
|
228
|
+
const serverModules = {};
|
|
229
|
+
const pathToId = {};
|
|
230
|
+
for (const component of components) {
|
|
231
|
+
serverModules[component.id] = component;
|
|
232
|
+
pathToId[component.path] = component.id;
|
|
233
|
+
pathToId[component.absolutePath] = component.id;
|
|
234
|
+
}
|
|
235
|
+
const routes = buildRoutes(components, appDir);
|
|
236
|
+
// Get or generate build ID
|
|
237
|
+
const buildIdPath = path_1.default.join(cwd, '.vista', 'BUILD_ID');
|
|
238
|
+
let buildId = 'dev';
|
|
239
|
+
try {
|
|
240
|
+
if (fs_1.default.existsSync(buildIdPath)) {
|
|
241
|
+
buildId = fs_1.default.readFileSync(buildIdPath, 'utf-8').trim();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
// Use dev
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
buildId,
|
|
249
|
+
serverModules,
|
|
250
|
+
pathToId,
|
|
251
|
+
routes,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get server component by path
|
|
256
|
+
*/
|
|
257
|
+
function getServerComponent(manifest, filePath) {
|
|
258
|
+
const moduleId = manifest.pathToId[filePath];
|
|
259
|
+
if (!moduleId)
|
|
260
|
+
return undefined;
|
|
261
|
+
return manifest.serverModules[moduleId];
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Check if a path is a server component
|
|
265
|
+
*/
|
|
266
|
+
function isServerComponentPath(manifest, filePath) {
|
|
267
|
+
return filePath in manifest.pathToId;
|
|
268
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vista Flight Loader
|
|
3
|
+
*
|
|
4
|
+
* Rust-powered webpack loader that detects 'client load' directive
|
|
5
|
+
* and marks modules with RSC info for proper bundle separation.
|
|
6
|
+
*
|
|
7
|
+
* This is similar to Next.js's flight-loader but uses Vista's Rust scanner.
|
|
8
|
+
*/
|
|
9
|
+
import type { LoaderContext } from 'webpack';
|
|
10
|
+
/**
|
|
11
|
+
* Vista Flight Loader
|
|
12
|
+
*
|
|
13
|
+
* Marks modules with RSC info based on 'client load' directive.
|
|
14
|
+
* Uses Rust for detection when available, falls back to TypeScript.
|
|
15
|
+
*/
|
|
16
|
+
export default function vistaFlightLoader(this: LoaderContext<{}>, source: string): string;
|
|
17
|
+
export declare const raw = false;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vista Flight Loader
|
|
4
|
+
*
|
|
5
|
+
* Rust-powered webpack loader that detects 'client load' directive
|
|
6
|
+
* and marks modules with RSC info for proper bundle separation.
|
|
7
|
+
*
|
|
8
|
+
* This is similar to Next.js's flight-loader but uses Vista's Rust scanner.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.raw = void 0;
|
|
12
|
+
exports.default = vistaFlightLoader;
|
|
13
|
+
// Try to load Rust native bindings
|
|
14
|
+
let nativeBindings = null;
|
|
15
|
+
try {
|
|
16
|
+
nativeBindings = require('../../../../crates/vista-napi');
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
// Fall back to TypeScript implementation
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Fallback TypeScript implementation of client directive detection
|
|
23
|
+
*/
|
|
24
|
+
function hasClientDirective(source) {
|
|
25
|
+
const lines = source.split('\n');
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
// Skip empty lines and comments
|
|
29
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*')) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// Check for 'client load' directive
|
|
33
|
+
if (trimmed === "'client load';" || trimmed === '"client load";' ||
|
|
34
|
+
trimmed === "'client load'" || trimmed === '"client load"') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// If we hit an import or other statement first, it's not a client component
|
|
38
|
+
if (trimmed.startsWith('import') || trimmed.startsWith('export') ||
|
|
39
|
+
trimmed.startsWith('const') || trimmed.startsWith('function')) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Vista Flight Loader
|
|
47
|
+
*
|
|
48
|
+
* Marks modules with RSC info based on 'client load' directive.
|
|
49
|
+
* Uses Rust for detection when available, falls back to TypeScript.
|
|
50
|
+
*/
|
|
51
|
+
function vistaFlightLoader(source) {
|
|
52
|
+
// Get module's build info
|
|
53
|
+
const buildInfo = this._module.buildInfo;
|
|
54
|
+
const fileName = this.resourcePath.split(/[\\/]/).pop() || '';
|
|
55
|
+
// Only process app directory files
|
|
56
|
+
if (!this.resourcePath.includes('app')) {
|
|
57
|
+
return source;
|
|
58
|
+
}
|
|
59
|
+
if (!buildInfo.rsc) {
|
|
60
|
+
// Detect directive using Rust or fallback
|
|
61
|
+
let isClient = false;
|
|
62
|
+
let directiveLine = 0;
|
|
63
|
+
if (nativeBindings) {
|
|
64
|
+
try {
|
|
65
|
+
const result = nativeBindings.analyzeClientDirective(source);
|
|
66
|
+
isClient = result.isClient;
|
|
67
|
+
directiveLine = result.directiveLine;
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
// Fallback if Rust call fails
|
|
71
|
+
isClient = hasClientDirective(source);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
isClient = hasClientDirective(source);
|
|
76
|
+
}
|
|
77
|
+
// Mark module with RSC info (like Next.js does)
|
|
78
|
+
buildInfo.rsc = {
|
|
79
|
+
isClientRef: isClient,
|
|
80
|
+
type: isClient ? 'client' : 'server',
|
|
81
|
+
directiveLine
|
|
82
|
+
};
|
|
83
|
+
// Debug logging (only when VISTA_DEBUG is set)
|
|
84
|
+
if (process.env.VISTA_DEBUG && isClient) {
|
|
85
|
+
console.log(`[Vista Flight Loader] ${fileName}: isClient=${isClient}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Pass through source unchanged
|
|
89
|
+
// The loader's job is just to mark modules, not transform code
|
|
90
|
+
return source;
|
|
91
|
+
}
|
|
92
|
+
// Allow async loading
|
|
93
|
+
exports.raw = false;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vista Flight Client Entry Plugin
|
|
3
|
+
*
|
|
4
|
+
* Webpack plugin that creates separate client entries for components
|
|
5
|
+
* marked with 'client load' directive. Uses Rust scanner for detection.
|
|
6
|
+
*
|
|
7
|
+
* This is similar to Next.js's FlightClientEntryPlugin.
|
|
8
|
+
*/
|
|
9
|
+
import webpack from 'webpack';
|
|
10
|
+
interface PluginOptions {
|
|
11
|
+
appDir: string;
|
|
12
|
+
dev: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface ClientModuleInfo {
|
|
15
|
+
moduleId: string | number;
|
|
16
|
+
absolutePath: string;
|
|
17
|
+
relativePath: string;
|
|
18
|
+
exports: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare class VistaFlightPlugin {
|
|
21
|
+
private appDir;
|
|
22
|
+
private dev;
|
|
23
|
+
constructor(options: PluginOptions);
|
|
24
|
+
apply(compiler: webpack.Compiler): void;
|
|
25
|
+
/**
|
|
26
|
+
* Collect information about all modules and their RSC status
|
|
27
|
+
*/
|
|
28
|
+
private collectModuleInfo;
|
|
29
|
+
/**
|
|
30
|
+
* Generate client reference manifest for hydration
|
|
31
|
+
*/
|
|
32
|
+
private generateClientManifest;
|
|
33
|
+
}
|
|
34
|
+
export declare function getClientModules(): Map<string, ClientModuleInfo>;
|
|
35
|
+
export declare function isClientModule(resourcePath: string): boolean;
|
|
36
|
+
export default VistaFlightPlugin;
|