zylaris 1.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.
- package/LICENSE +21 -0
- package/README.md +558 -0
- package/Zylaris.js.png +0 -0
- package/examples/default/index.html +13 -0
- package/examples/default/package.json +23 -0
- package/examples/default/src/app/about/page.tsx +18 -0
- package/examples/default/src/app/counter/page.tsx +22 -0
- package/examples/default/src/app/global.css +225 -0
- package/examples/default/src/app/layout.tsx +33 -0
- package/examples/default/src/app/page.tsx +14 -0
- package/examples/default/src/entry-client.tsx +87 -0
- package/examples/default/src/entry-server.tsx +52 -0
- package/examples/default/src/router.ts +60 -0
- package/examples/default/tsconfig.json +28 -0
- package/examples/default/zylaris.config.ts +24 -0
- package/package.json +34 -0
- package/packages/adapter/package.json +59 -0
- package/packages/adapter/src/adapters/bun.ts +215 -0
- package/packages/adapter/src/adapters/cloudflare.ts +278 -0
- package/packages/adapter/src/adapters/deno.ts +219 -0
- package/packages/adapter/src/adapters/netlify.ts +274 -0
- package/packages/adapter/src/adapters/node.ts +155 -0
- package/packages/adapter/src/adapters/static.ts +134 -0
- package/packages/adapter/src/adapters/vercel.ts +239 -0
- package/packages/adapter/src/index.ts +115 -0
- package/packages/adapter/src/lib/builder.ts +361 -0
- package/packages/adapter/src/types.ts +191 -0
- package/packages/adapter/tsconfig.json +8 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/bin.ts +107 -0
- package/packages/cli/src/commands/build.ts +197 -0
- package/packages/cli/src/commands/create.ts +222 -0
- package/packages/cli/src/commands/deploy.ts +90 -0
- package/packages/cli/src/commands/dev.ts +108 -0
- package/packages/cli/src/index.ts +6 -0
- package/packages/cli/tsconfig.json +9 -0
- package/packages/compiler/package.json +39 -0
- package/packages/compiler/src/index.ts +210 -0
- package/packages/compiler/src/jit.ts +187 -0
- package/packages/compiler/tsconfig.json +9 -0
- package/packages/core/package.json +55 -0
- package/packages/core/src/components.test.ts +125 -0
- package/packages/core/src/components.ts +181 -0
- package/packages/core/src/config.ts +204 -0
- package/packages/core/src/hooks.ts +142 -0
- package/packages/core/src/index.ts +59 -0
- package/packages/core/src/jsx-runtime.ts +46 -0
- package/packages/core/tsconfig.json +16 -0
- package/packages/dev-server/package.json +51 -0
- package/packages/dev-server/src/index.ts +306 -0
- package/packages/dev-server/src/jit-middleware.ts +78 -0
- package/packages/dev-server/tsconfig.json +9 -0
- package/packages/plugins/package.json +44 -0
- package/packages/plugins/src/cdn/loader.ts +275 -0
- package/packages/plugins/src/index.ts +238 -0
- package/packages/plugins/src/loaders/auto-import.ts +219 -0
- package/packages/plugins/src/loaders/external.ts +332 -0
- package/packages/plugins/src/transforms/index.ts +407 -0
- package/packages/plugins/src/types.ts +296 -0
- package/packages/plugins/tsconfig.json +8 -0
- package/packages/reactivity/package.json +36 -0
- package/packages/reactivity/src/computed.d.ts +3 -0
- package/packages/reactivity/src/computed.d.ts.map +1 -0
- package/packages/reactivity/src/computed.js +64 -0
- package/packages/reactivity/src/computed.js.map +1 -0
- package/packages/reactivity/src/computed.test.ts +83 -0
- package/packages/reactivity/src/computed.ts +69 -0
- package/packages/reactivity/src/index.d.ts +6 -0
- package/packages/reactivity/src/index.d.ts.map +1 -0
- package/packages/reactivity/src/index.js +7 -0
- package/packages/reactivity/src/index.js.map +1 -0
- package/packages/reactivity/src/index.ts +18 -0
- package/packages/reactivity/src/resource.d.ts +6 -0
- package/packages/reactivity/src/resource.d.ts.map +1 -0
- package/packages/reactivity/src/resource.js +43 -0
- package/packages/reactivity/src/resource.js.map +1 -0
- package/packages/reactivity/src/resource.test.ts +70 -0
- package/packages/reactivity/src/resource.ts +59 -0
- package/packages/reactivity/src/signal.d.ts +7 -0
- package/packages/reactivity/src/signal.d.ts.map +1 -0
- package/packages/reactivity/src/signal.js +145 -0
- package/packages/reactivity/src/signal.js.map +1 -0
- package/packages/reactivity/src/signal.test.ts +130 -0
- package/packages/reactivity/src/signal.ts +207 -0
- package/packages/reactivity/src/store.d.ts +4 -0
- package/packages/reactivity/src/store.d.ts.map +1 -0
- package/packages/reactivity/src/store.js +62 -0
- package/packages/reactivity/src/store.js.map +1 -0
- package/packages/reactivity/src/store.test.ts +38 -0
- package/packages/reactivity/src/store.ts +111 -0
- package/packages/reactivity/src/types.d.ts +43 -0
- package/packages/reactivity/src/types.d.ts.map +1 -0
- package/packages/reactivity/src/types.js +3 -0
- package/packages/reactivity/src/types.js.map +1 -0
- package/packages/reactivity/src/types.ts +43 -0
- package/packages/reactivity/tsconfig.json +9 -0
- package/packages/router/package.json +44 -0
- package/packages/router/src/components.tsx +150 -0
- package/packages/router/src/fs-router.ts +163 -0
- package/packages/router/src/index.ts +22 -0
- package/packages/router/src/router.test.ts +111 -0
- package/packages/router/src/router.ts +112 -0
- package/packages/router/src/types.ts +69 -0
- package/packages/router/tsconfig.json +10 -0
- package/packages/server/package.json +41 -0
- package/packages/server/src/action.test.ts +102 -0
- package/packages/server/src/action.ts +201 -0
- package/packages/server/src/api.ts +143 -0
- package/packages/server/src/index.ts +18 -0
- package/packages/server/src/types.ts +72 -0
- package/packages/server/tsconfig.json +9 -0
- package/pnpm-workspace.yaml +4 -0
- package/scripts/publish.ps1 +138 -0
- package/scripts/publish.sh +142 -0
- package/tsconfig.json +28 -0
- package/turbo.json +24 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// JIT Compiler for Development
|
|
2
|
+
// Ultra-fast compilation with in-memory caching
|
|
3
|
+
|
|
4
|
+
import { transform as esbuildTransform, TransformOptions } from 'esbuild';
|
|
5
|
+
import { createHash } from 'crypto';
|
|
6
|
+
|
|
7
|
+
interface JITCacheEntry {
|
|
8
|
+
code: string;
|
|
9
|
+
map: string;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
dependencies: Set<string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface JITOptions {
|
|
15
|
+
target?: 'es2020' | 'es2022' | 'esnext';
|
|
16
|
+
jsx?: 'transform' | 'preserve';
|
|
17
|
+
jsxImportSource?: string;
|
|
18
|
+
sourceMap?: boolean;
|
|
19
|
+
cacheTimeout?: number; // Cache timeout in ms (default: 5 minutes)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// In-memory cache for compiled modules
|
|
23
|
+
const moduleCache = new Map<string, JITCacheEntry>();
|
|
24
|
+
const fileTimestamps = new Map<string, number>();
|
|
25
|
+
|
|
26
|
+
// Default options
|
|
27
|
+
const defaultOptions: JITOptions = {
|
|
28
|
+
target: 'es2022',
|
|
29
|
+
jsx: 'transform',
|
|
30
|
+
jsxImportSource: 'zylaris',
|
|
31
|
+
sourceMap: true,
|
|
32
|
+
cacheTimeout: 5 * 60 * 1000, // 5 minutes
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate cache key from content and options
|
|
37
|
+
*/
|
|
38
|
+
function generateCacheKey(source: string, filename: string, options: JITOptions): string {
|
|
39
|
+
const content = `${source}::${filename}::${JSON.stringify(options)}`;
|
|
40
|
+
return createHash('md5').update(content).digest('hex');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if cache entry is valid
|
|
45
|
+
*/
|
|
46
|
+
function isCacheValid(entry: JITCacheEntry, cacheTimeout: number): boolean {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
return (now - entry.timestamp) < cacheTimeout;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* JIT Compile TypeScript/TSX/JSX file
|
|
53
|
+
* Uses esbuild for maximum speed with in-memory caching
|
|
54
|
+
*/
|
|
55
|
+
export async function jitCompile(
|
|
56
|
+
source: string,
|
|
57
|
+
filename: string,
|
|
58
|
+
options: JITOptions = {}
|
|
59
|
+
): Promise<{ code: string; map: string }> {
|
|
60
|
+
const opts = { ...defaultOptions, ...options };
|
|
61
|
+
const cacheKey = generateCacheKey(source, filename, opts);
|
|
62
|
+
|
|
63
|
+
// Check cache first
|
|
64
|
+
const cached = moduleCache.get(cacheKey);
|
|
65
|
+
if (cached && isCacheValid(cached, opts.cacheTimeout!)) {
|
|
66
|
+
return { code: cached.code, map: cached.map };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Determine loader based on extension
|
|
70
|
+
const ext = filename.split('.').pop()?.toLowerCase();
|
|
71
|
+
let loader: TransformOptions['loader'] = 'ts';
|
|
72
|
+
|
|
73
|
+
if (ext === 'tsx' || (ext === 'ts' && filename.includes('jsx'))) {
|
|
74
|
+
loader = 'tsx';
|
|
75
|
+
} else if (ext === 'jsx') {
|
|
76
|
+
loader = 'jsx';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Compile with esbuild (faster than SWC for single-file transforms)
|
|
80
|
+
const result = await esbuildTransform(source, {
|
|
81
|
+
loader,
|
|
82
|
+
target: opts.target,
|
|
83
|
+
format: 'esm',
|
|
84
|
+
platform: 'neutral',
|
|
85
|
+
jsx: opts.jsx === 'transform' ? 'automatic' : 'preserve',
|
|
86
|
+
jsxImportSource: opts.jsxImportSource,
|
|
87
|
+
sourcemap: opts.sourceMap ? 'inline' : false,
|
|
88
|
+
sourcesContent: false,
|
|
89
|
+
// Development optimizations
|
|
90
|
+
minifyWhitespace: false,
|
|
91
|
+
minifyIdentifiers: false,
|
|
92
|
+
minifySyntax: false,
|
|
93
|
+
// Keep names for debugging
|
|
94
|
+
keepNames: true,
|
|
95
|
+
// Fastest tree shaking
|
|
96
|
+
treeShaking: true,
|
|
97
|
+
// Preserve import.meta
|
|
98
|
+
define: {
|
|
99
|
+
'import.meta.env.DEV': 'true',
|
|
100
|
+
'import.meta.env.PROD': 'false',
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const output = {
|
|
105
|
+
code: result.code,
|
|
106
|
+
map: result.map || '',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Store in cache
|
|
110
|
+
moduleCache.set(cacheKey, {
|
|
111
|
+
code: output.code,
|
|
112
|
+
map: output.map,
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
dependencies: extractDependencies(source),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return output;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract import dependencies from source
|
|
122
|
+
*/
|
|
123
|
+
function extractDependencies(source: string): Set<string> {
|
|
124
|
+
const deps = new Set<string>();
|
|
125
|
+
const importRegex = /import\s+(?:{[^}]*}|[^'"]*)\s*from\s*['"]([^'"]+)['"]|import\s*['"]([^'"]+)['"]/g;
|
|
126
|
+
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
129
|
+
deps.add(match[1] || match[2]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return deps;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Invalidate cache for a specific file
|
|
137
|
+
*/
|
|
138
|
+
export function invalidateCache(filename: string): void {
|
|
139
|
+
// Find and remove cache entries that depend on this file
|
|
140
|
+
for (const [key, entry] of moduleCache.entries()) {
|
|
141
|
+
if (entry.dependencies.has(filename)) {
|
|
142
|
+
moduleCache.delete(key);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Clear all cache
|
|
149
|
+
*/
|
|
150
|
+
export function clearCache(): void {
|
|
151
|
+
moduleCache.clear();
|
|
152
|
+
fileTimestamps.clear();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get cache stats
|
|
157
|
+
*/
|
|
158
|
+
export function getCacheStats(): {
|
|
159
|
+
size: number;
|
|
160
|
+
entries: number;
|
|
161
|
+
hitRate: number;
|
|
162
|
+
} {
|
|
163
|
+
return {
|
|
164
|
+
size: moduleCache.size,
|
|
165
|
+
entries: moduleCache.size,
|
|
166
|
+
hitRate: 0, // Would need to track hits/misses
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Warm up cache for commonly used files
|
|
172
|
+
*/
|
|
173
|
+
export async function warmupCache(
|
|
174
|
+
files: Array<{ source: string; filename: string }>,
|
|
175
|
+
options?: JITOptions
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
await Promise.all(
|
|
178
|
+
files.map(({ source, filename }) =>
|
|
179
|
+
jitCompile(source, filename, options).catch(() => {
|
|
180
|
+
// Silently ignore errors during warmup
|
|
181
|
+
})
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Export for monitoring
|
|
187
|
+
export { moduleCache };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zylaris",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zylaris - The Universal Web Framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server/index.d.ts",
|
|
16
|
+
"import": "./dist/server/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./jsx-runtime": {
|
|
19
|
+
"types": "./dist/jsx-runtime.d.ts",
|
|
20
|
+
"import": "./dist/jsx-runtime.js"
|
|
21
|
+
},
|
|
22
|
+
"./jsx-dev-runtime": {
|
|
23
|
+
"types": "./dist/jsx-runtime.d.ts",
|
|
24
|
+
"import": "./dist/jsx-runtime.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"dev": "tsc --watch",
|
|
33
|
+
"test": "vitest run --passWithNoTests",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"clean": "rm -rf dist"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@zylaris/reactivity": "workspace:*"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/react": "^18.2.0",
|
|
42
|
+
"typescript": "^5.3.3",
|
|
43
|
+
"vitest": "^1.2.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": "^18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"framework",
|
|
50
|
+
"web",
|
|
51
|
+
"jsx",
|
|
52
|
+
"zylaris"
|
|
53
|
+
],
|
|
54
|
+
"license": "MIT"
|
|
55
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Show, For, Switch, Match } from './components.js';
|
|
3
|
+
import { signal } from '@zylaris/reactivity';
|
|
4
|
+
|
|
5
|
+
describe('Show', () => {
|
|
6
|
+
it('should render children when condition is true', () => {
|
|
7
|
+
const result = Show({
|
|
8
|
+
when: true,
|
|
9
|
+
children: 'Content' as any
|
|
10
|
+
});
|
|
11
|
+
expect(result).toBe('Content');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should render fallback when condition is false', () => {
|
|
15
|
+
const result = Show({
|
|
16
|
+
when: false,
|
|
17
|
+
fallback: 'Fallback' as any,
|
|
18
|
+
children: 'Content' as any
|
|
19
|
+
});
|
|
20
|
+
expect(result).toBe('Fallback');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should render null when no fallback and condition is false', () => {
|
|
24
|
+
const result = Show({
|
|
25
|
+
when: false,
|
|
26
|
+
children: 'Content' as any
|
|
27
|
+
});
|
|
28
|
+
expect(result).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should work with signal', () => {
|
|
32
|
+
const s = signal(true);
|
|
33
|
+
const result = Show({
|
|
34
|
+
when: s,
|
|
35
|
+
children: 'Content' as any
|
|
36
|
+
});
|
|
37
|
+
expect(result).toBe('Content');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should call children as function with value', () => {
|
|
41
|
+
const result = Show({
|
|
42
|
+
when: 'hello',
|
|
43
|
+
children: ((value: string) => `Value: ${value}`) as any
|
|
44
|
+
});
|
|
45
|
+
expect(result).toBe('Value: hello');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('For', () => {
|
|
50
|
+
it('should render array items', () => {
|
|
51
|
+
const items = [1, 2, 3];
|
|
52
|
+
const result = For({
|
|
53
|
+
each: items,
|
|
54
|
+
children: ((item: number) => item * 2) as any
|
|
55
|
+
});
|
|
56
|
+
expect(result).toEqual([2, 4, 6]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return empty array for empty input', () => {
|
|
60
|
+
const result = For({
|
|
61
|
+
each: [],
|
|
62
|
+
children: ((item: any) => item) as any
|
|
63
|
+
});
|
|
64
|
+
expect(result).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return fallback for empty array', () => {
|
|
68
|
+
const result = For({
|
|
69
|
+
each: [],
|
|
70
|
+
fallback: 'No items' as any,
|
|
71
|
+
children: ((item: any) => item) as any
|
|
72
|
+
});
|
|
73
|
+
expect(result).toEqual(['No items']);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should work with signal', () => {
|
|
77
|
+
const s = signal([1, 2, 3]);
|
|
78
|
+
const result = For({
|
|
79
|
+
each: s,
|
|
80
|
+
children: ((item: number) => item * 2) as any
|
|
81
|
+
});
|
|
82
|
+
expect(result).toEqual([2, 4, 6]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('Switch/Match', () => {
|
|
87
|
+
it('should render first matching case', () => {
|
|
88
|
+
const result = Switch({
|
|
89
|
+
children: [
|
|
90
|
+
Match({ when: false, children: 'First' as any }),
|
|
91
|
+
Match({ when: true, children: 'Second' as any }),
|
|
92
|
+
Match({ when: true, children: 'Third' as any })
|
|
93
|
+
] as any
|
|
94
|
+
});
|
|
95
|
+
expect(result).toBe('Second');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should render fallback when no match', () => {
|
|
99
|
+
const result = Switch({
|
|
100
|
+
fallback: 'No match' as any,
|
|
101
|
+
children: [
|
|
102
|
+
Match({ when: false, children: 'First' as any })
|
|
103
|
+
] as any
|
|
104
|
+
});
|
|
105
|
+
expect(result).toBe('No match');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should render null when no match and no fallback', () => {
|
|
109
|
+
const result = Switch({
|
|
110
|
+
children: [
|
|
111
|
+
Match({ when: false, children: 'First' as any })
|
|
112
|
+
] as any
|
|
113
|
+
});
|
|
114
|
+
expect(result).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should call children as function', () => {
|
|
118
|
+
const result = Switch({
|
|
119
|
+
children: [
|
|
120
|
+
Match({ when: true, children: (() => 'Dynamic') as any })
|
|
121
|
+
] as any
|
|
122
|
+
});
|
|
123
|
+
expect(result).toBe('Dynamic');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { Signal } from '@zylaris/reactivity';
|
|
2
|
+
import { isSignal } from './jsx-runtime.js';
|
|
3
|
+
|
|
4
|
+
// Show component - conditional rendering with fallback
|
|
5
|
+
interface ShowProps<T> {
|
|
6
|
+
when: T | Signal<T> | null | undefined | false;
|
|
7
|
+
fallback?: JSX.Element;
|
|
8
|
+
children: ((item: T) => JSX.Element) | JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Show<T>(props: ShowProps<T>): JSX.Element {
|
|
12
|
+
const when = isSignal(props.when) ? props.when() : props.when;
|
|
13
|
+
|
|
14
|
+
if (!when) {
|
|
15
|
+
return props.fallback || (null as unknown as JSX.Element);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof props.children === 'function') {
|
|
19
|
+
return props.children(when as T);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return props.children;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// For component - optimized list rendering
|
|
26
|
+
interface ForProps<T> {
|
|
27
|
+
each: T[] | Signal<T[]>;
|
|
28
|
+
children: (item: T, index: () => number) => JSX.Element;
|
|
29
|
+
fallback?: JSX.Element;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function For<T>(props: ForProps<T>): JSX.Element[] {
|
|
33
|
+
const items = isSignal(props.each) ? props.each() : props.each;
|
|
34
|
+
|
|
35
|
+
if (!items || items.length === 0) {
|
|
36
|
+
return props.fallback ? [props.fallback] : [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return items.map((item, index) => props.children(item, () => index));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Switch/Match components
|
|
43
|
+
interface SwitchProps {
|
|
44
|
+
fallback?: JSX.Element;
|
|
45
|
+
children: JSX.Element | JSX.Element[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface MatchProps {
|
|
49
|
+
when: boolean | Signal<boolean>;
|
|
50
|
+
children: JSX.Element | (() => JSX.Element);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function Switch(props: SwitchProps): JSX.Element {
|
|
54
|
+
const children = Array.isArray(props.children) ? props.children : [props.children];
|
|
55
|
+
|
|
56
|
+
for (const child of children) {
|
|
57
|
+
// Check if it's a Match marker object
|
|
58
|
+
if (child && typeof child === 'object' && 'type' in child && child.type === 'match' && 'props' in child) {
|
|
59
|
+
const matchProps = child.props as MatchProps;
|
|
60
|
+
const when = isSignal(matchProps.when) ? matchProps.when() : matchProps.when;
|
|
61
|
+
|
|
62
|
+
if (when) {
|
|
63
|
+
return typeof matchProps.children === 'function'
|
|
64
|
+
? matchProps.children()
|
|
65
|
+
: matchProps.children;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return props.fallback || (null as unknown as JSX.Element);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Match marker object for Switch
|
|
74
|
+
export function Match(props: MatchProps): { type: 'match'; props: MatchProps } {
|
|
75
|
+
// Return a marker object that Switch can identify
|
|
76
|
+
return { type: 'match', props };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Suspense component
|
|
80
|
+
interface SuspenseProps {
|
|
81
|
+
fallback: JSX.Element;
|
|
82
|
+
children: JSX.Element;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function Suspense(props: SuspenseProps): JSX.Element {
|
|
86
|
+
// In real implementation, this would integrate with resource signals
|
|
87
|
+
return props.children;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Dynamic component
|
|
91
|
+
interface DynamicProps<T> {
|
|
92
|
+
component: (props: T) => JSX.Element;
|
|
93
|
+
props?: T;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function Dynamic<T extends object>(props: DynamicProps<T>): JSX.Element {
|
|
97
|
+
return props.component(props.props || {} as T);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Portal component (for modals, tooltips)
|
|
101
|
+
interface PortalProps {
|
|
102
|
+
mount?: HTMLElement;
|
|
103
|
+
children: JSX.Element;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function Portal(props: PortalProps): JSX.Element {
|
|
107
|
+
// In real implementation, this would render to a different DOM node
|
|
108
|
+
return props.children;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Error Boundary
|
|
112
|
+
interface ErrorBoundaryProps {
|
|
113
|
+
fallback: (error: Error, reset: () => void) => JSX.Element;
|
|
114
|
+
children: JSX.Element;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface ErrorBoundaryState {
|
|
118
|
+
error: Error | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
122
|
+
declare global {
|
|
123
|
+
namespace JSX {
|
|
124
|
+
interface Element {}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface ReactLikeComponent<P, S> {
|
|
129
|
+
props: P;
|
|
130
|
+
state: S;
|
|
131
|
+
setState(state: Partial<S>): void;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export class ErrorBoundary implements ReactLikeComponent<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
135
|
+
props: ErrorBoundaryProps;
|
|
136
|
+
state: ErrorBoundaryState;
|
|
137
|
+
|
|
138
|
+
constructor(props: ErrorBoundaryProps) {
|
|
139
|
+
this.props = props;
|
|
140
|
+
this.state = { error: null };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
144
|
+
return { error };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setState(state: Partial<ErrorBoundaryState>): void {
|
|
148
|
+
this.state = { ...this.state, ...state };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
reset = () => {
|
|
152
|
+
this.setState({ error: null });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
render(): JSX.Element {
|
|
156
|
+
if (this.state.error) {
|
|
157
|
+
return this.props.fallback(this.state.error, this.reset);
|
|
158
|
+
}
|
|
159
|
+
return this.props.children;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Lazy component
|
|
164
|
+
export function lazy<T extends object>(
|
|
165
|
+
loader: () => Promise<{ default: (props: T) => JSX.Element }>
|
|
166
|
+
): (props: T) => JSX.Element {
|
|
167
|
+
let Component: ((props: T) => JSX.Element) | null = null;
|
|
168
|
+
let promise: Promise<void> | null = null;
|
|
169
|
+
|
|
170
|
+
return function LazyComponent(props: T): JSX.Element {
|
|
171
|
+
if (!Component) {
|
|
172
|
+
if (!promise) {
|
|
173
|
+
promise = loader().then((module) => {
|
|
174
|
+
Component = module.default;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
throw promise;
|
|
178
|
+
}
|
|
179
|
+
return Component(props);
|
|
180
|
+
};
|
|
181
|
+
}
|