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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +558 -0
  3. package/Zylaris.js.png +0 -0
  4. package/examples/default/index.html +13 -0
  5. package/examples/default/package.json +23 -0
  6. package/examples/default/src/app/about/page.tsx +18 -0
  7. package/examples/default/src/app/counter/page.tsx +22 -0
  8. package/examples/default/src/app/global.css +225 -0
  9. package/examples/default/src/app/layout.tsx +33 -0
  10. package/examples/default/src/app/page.tsx +14 -0
  11. package/examples/default/src/entry-client.tsx +87 -0
  12. package/examples/default/src/entry-server.tsx +52 -0
  13. package/examples/default/src/router.ts +60 -0
  14. package/examples/default/tsconfig.json +28 -0
  15. package/examples/default/zylaris.config.ts +24 -0
  16. package/package.json +34 -0
  17. package/packages/adapter/package.json +59 -0
  18. package/packages/adapter/src/adapters/bun.ts +215 -0
  19. package/packages/adapter/src/adapters/cloudflare.ts +278 -0
  20. package/packages/adapter/src/adapters/deno.ts +219 -0
  21. package/packages/adapter/src/adapters/netlify.ts +274 -0
  22. package/packages/adapter/src/adapters/node.ts +155 -0
  23. package/packages/adapter/src/adapters/static.ts +134 -0
  24. package/packages/adapter/src/adapters/vercel.ts +239 -0
  25. package/packages/adapter/src/index.ts +115 -0
  26. package/packages/adapter/src/lib/builder.ts +361 -0
  27. package/packages/adapter/src/types.ts +191 -0
  28. package/packages/adapter/tsconfig.json +8 -0
  29. package/packages/cli/package.json +43 -0
  30. package/packages/cli/src/bin.ts +107 -0
  31. package/packages/cli/src/commands/build.ts +197 -0
  32. package/packages/cli/src/commands/create.ts +222 -0
  33. package/packages/cli/src/commands/deploy.ts +90 -0
  34. package/packages/cli/src/commands/dev.ts +108 -0
  35. package/packages/cli/src/index.ts +6 -0
  36. package/packages/cli/tsconfig.json +9 -0
  37. package/packages/compiler/package.json +39 -0
  38. package/packages/compiler/src/index.ts +210 -0
  39. package/packages/compiler/src/jit.ts +187 -0
  40. package/packages/compiler/tsconfig.json +9 -0
  41. package/packages/core/package.json +55 -0
  42. package/packages/core/src/components.test.ts +125 -0
  43. package/packages/core/src/components.ts +181 -0
  44. package/packages/core/src/config.ts +204 -0
  45. package/packages/core/src/hooks.ts +142 -0
  46. package/packages/core/src/index.ts +59 -0
  47. package/packages/core/src/jsx-runtime.ts +46 -0
  48. package/packages/core/tsconfig.json +16 -0
  49. package/packages/dev-server/package.json +51 -0
  50. package/packages/dev-server/src/index.ts +306 -0
  51. package/packages/dev-server/src/jit-middleware.ts +78 -0
  52. package/packages/dev-server/tsconfig.json +9 -0
  53. package/packages/plugins/package.json +44 -0
  54. package/packages/plugins/src/cdn/loader.ts +275 -0
  55. package/packages/plugins/src/index.ts +238 -0
  56. package/packages/plugins/src/loaders/auto-import.ts +219 -0
  57. package/packages/plugins/src/loaders/external.ts +332 -0
  58. package/packages/plugins/src/transforms/index.ts +407 -0
  59. package/packages/plugins/src/types.ts +296 -0
  60. package/packages/plugins/tsconfig.json +8 -0
  61. package/packages/reactivity/package.json +36 -0
  62. package/packages/reactivity/src/computed.d.ts +3 -0
  63. package/packages/reactivity/src/computed.d.ts.map +1 -0
  64. package/packages/reactivity/src/computed.js +64 -0
  65. package/packages/reactivity/src/computed.js.map +1 -0
  66. package/packages/reactivity/src/computed.test.ts +83 -0
  67. package/packages/reactivity/src/computed.ts +69 -0
  68. package/packages/reactivity/src/index.d.ts +6 -0
  69. package/packages/reactivity/src/index.d.ts.map +1 -0
  70. package/packages/reactivity/src/index.js +7 -0
  71. package/packages/reactivity/src/index.js.map +1 -0
  72. package/packages/reactivity/src/index.ts +18 -0
  73. package/packages/reactivity/src/resource.d.ts +6 -0
  74. package/packages/reactivity/src/resource.d.ts.map +1 -0
  75. package/packages/reactivity/src/resource.js +43 -0
  76. package/packages/reactivity/src/resource.js.map +1 -0
  77. package/packages/reactivity/src/resource.test.ts +70 -0
  78. package/packages/reactivity/src/resource.ts +59 -0
  79. package/packages/reactivity/src/signal.d.ts +7 -0
  80. package/packages/reactivity/src/signal.d.ts.map +1 -0
  81. package/packages/reactivity/src/signal.js +145 -0
  82. package/packages/reactivity/src/signal.js.map +1 -0
  83. package/packages/reactivity/src/signal.test.ts +130 -0
  84. package/packages/reactivity/src/signal.ts +207 -0
  85. package/packages/reactivity/src/store.d.ts +4 -0
  86. package/packages/reactivity/src/store.d.ts.map +1 -0
  87. package/packages/reactivity/src/store.js +62 -0
  88. package/packages/reactivity/src/store.js.map +1 -0
  89. package/packages/reactivity/src/store.test.ts +38 -0
  90. package/packages/reactivity/src/store.ts +111 -0
  91. package/packages/reactivity/src/types.d.ts +43 -0
  92. package/packages/reactivity/src/types.d.ts.map +1 -0
  93. package/packages/reactivity/src/types.js +3 -0
  94. package/packages/reactivity/src/types.js.map +1 -0
  95. package/packages/reactivity/src/types.ts +43 -0
  96. package/packages/reactivity/tsconfig.json +9 -0
  97. package/packages/router/package.json +44 -0
  98. package/packages/router/src/components.tsx +150 -0
  99. package/packages/router/src/fs-router.ts +163 -0
  100. package/packages/router/src/index.ts +22 -0
  101. package/packages/router/src/router.test.ts +111 -0
  102. package/packages/router/src/router.ts +112 -0
  103. package/packages/router/src/types.ts +69 -0
  104. package/packages/router/tsconfig.json +10 -0
  105. package/packages/server/package.json +41 -0
  106. package/packages/server/src/action.test.ts +102 -0
  107. package/packages/server/src/action.ts +201 -0
  108. package/packages/server/src/api.ts +143 -0
  109. package/packages/server/src/index.ts +18 -0
  110. package/packages/server/src/types.ts +72 -0
  111. package/packages/server/tsconfig.json +9 -0
  112. package/pnpm-workspace.yaml +4 -0
  113. package/scripts/publish.ps1 +138 -0
  114. package/scripts/publish.sh +142 -0
  115. package/tsconfig.json +28 -0
  116. 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,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["dist", "node_modules"]
9
+ }
@@ -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
+ }