frontend-hamroun 1.1.90 → 1.2.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/dist/{src/backend → backend}/api-utils.d.ts +2 -2
- package/dist/backend/api-utils.js +135 -0
- package/dist/backend/auth.js +387 -0
- package/dist/{src/backend → backend}/database.d.ts +1 -1
- package/dist/backend/database.js +91 -0
- package/dist/{src/backend → backend}/model.d.ts +2 -2
- package/dist/backend/model.js +176 -0
- package/dist/{src/backend → backend}/router.d.ts +1 -1
- package/dist/backend/router.js +137 -0
- package/dist/backend/server.js +268 -0
- package/dist/batch.js +22 -0
- package/dist/cli/index.js +1 -0
- package/dist/{src/component.d.ts → component.d.ts} +1 -1
- package/dist/component.js +84 -0
- package/dist/components/Counter.js +2 -0
- package/dist/context.js +20 -0
- package/dist/frontend-hamroun.es.js +1680 -0
- package/dist/frontend-hamroun.es.js.map +1 -0
- package/dist/frontend-hamroun.umd.js +2 -0
- package/dist/frontend-hamroun.umd.js.map +1 -0
- package/dist/hooks.js +164 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +52 -355
- package/dist/jsx-runtime/index.d.ts +9 -0
- package/dist/jsx-runtime/index.js +16 -0
- package/dist/jsx-runtime/jsx-dev-runtime.js +1 -0
- package/dist/jsx-runtime/jsx-runtime.js +91 -0
- package/dist/{src/jsx-runtime.d.ts → jsx-runtime.d.ts} +1 -1
- package/dist/jsx-runtime.js +192 -0
- package/dist/renderer.js +51 -0
- package/dist/{src/server-renderer.d.ts → server-renderer.d.ts} +3 -0
- package/dist/server-renderer.js +102 -0
- package/dist/vdom.js +27 -0
- package/package.json +40 -53
- package/scripts/build-cli.js +81 -0
- package/scripts/generate.js +134 -0
- package/src/backend/api-utils.ts +178 -0
- package/src/backend/auth.ts +543 -0
- package/src/backend/database.ts +104 -0
- package/src/backend/model.ts +196 -0
- package/src/backend/router.ts +176 -0
- package/src/backend/server.ts +330 -0
- package/src/backend/types.ts +257 -0
- package/src/batch.ts +24 -0
- package/src/cli/index.js +22 -40
- package/src/cli/index.ts +251 -0
- package/src/component.ts +98 -0
- package/src/components/Counter.tsx +4 -0
- package/src/context.ts +32 -0
- package/src/hooks.ts +211 -0
- package/src/index.ts +113 -0
- package/src/jsx-runtime/index.ts +24 -0
- package/src/jsx-runtime/jsx-runtime.ts +99 -0
- package/src/jsx-runtime.ts +226 -0
- package/src/renderer.ts +55 -0
- package/src/server-renderer.ts +114 -0
- package/src/types/bcrypt.d.ts +30 -0
- package/src/types/jsonwebtoken.d.ts +55 -0
- package/src/types.d.ts +26 -0
- package/src/types.ts +21 -0
- package/src/vdom.ts +34 -0
- package/templates/basic-app/package.json +17 -15
- package/templates/basic-app/postcss.config.js +1 -0
- package/templates/basic-app/src/App.tsx +65 -0
- package/templates/basic-app/src/api.ts +58 -0
- package/templates/basic-app/src/components/Counter.tsx +26 -0
- package/templates/basic-app/src/components/Header.tsx +9 -0
- package/templates/basic-app/src/components/TodoList.tsx +90 -0
- package/templates/basic-app/src/main.ts +20 -0
- package/templates/basic-app/src/server.ts +99 -0
- package/templates/basic-app/tailwind.config.js +23 -2
- package/templates/full-stack/index.html +13 -0
- package/templates/full-stack/package.json +25 -18
- package/templates/full-stack/src/main.css +3 -0
- package/templates/full-stack/src/main.ts +20 -0
- package/bin/cli.js +0 -371
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -139269
- package/dist/index.mjs.map +0 -1
- package/dist/src/index.d.ts +0 -16
- package/dist/test/setupTests.d.ts +0 -4
- /package/dist/{src/backend → backend}/auth.d.ts +0 -0
- /package/dist/{src/backend → backend}/server.d.ts +0 -0
- /package/dist/{src/backend → backend}/types.d.ts +0 -0
- /package/dist/{test/backend.test.d.ts → backend/types.js} +0 -0
- /package/dist/{src/batch.d.ts → batch.d.ts} +0 -0
- /package/dist/{src/cli → cli}/index.d.ts +0 -0
- /package/dist/{src/components → components}/Counter.d.ts +0 -0
- /package/dist/{src/context.d.ts → context.d.ts} +0 -0
- /package/dist/{src/hooks.d.ts → hooks.d.ts} +0 -0
- /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-dev-runtime.d.ts +0 -0
- /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-runtime.d.ts +0 -0
- /package/dist/{src/renderer.d.ts → renderer.d.ts} +0 -0
- /package/dist/{src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{test/mockTest.d.ts → types.js} +0 -0
- /package/dist/{src/vdom.d.ts → vdom.d.ts} +0 -0
- /package/{dist/test/mongooseSetup.d.ts → src/jsx-runtime/jsx-dev-runtime.ts} +0 -0
package/src/context.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const contexts = new Map<symbol, any>();
|
|
4
|
+
let currentRender: Function | null = null;
|
|
5
|
+
|
|
6
|
+
export interface Context<T> {
|
|
7
|
+
Provider: (props: { value: T; children?: any }) => any;
|
|
8
|
+
Consumer: (props: { children: (value: T) => any }) => any;
|
|
9
|
+
_id: symbol;
|
|
10
|
+
useSelector: <S>(selector: (state: T) => S) => S;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createContext<T>(defaultValue: T): Context<T> {
|
|
14
|
+
const context = {
|
|
15
|
+
Provider: ({ value, children }: { value: T, children?: any }) => {
|
|
16
|
+
return children;
|
|
17
|
+
},
|
|
18
|
+
Consumer: ({ children }: { children: (value: T) => any }) => {
|
|
19
|
+
return children(defaultValue);
|
|
20
|
+
},
|
|
21
|
+
_id: Symbol(),
|
|
22
|
+
useSelector: <S>(selector: (state: T) => S) => {
|
|
23
|
+
return selector(defaultValue);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useContext<T>(context: any): T {
|
|
31
|
+
return context;
|
|
32
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { createElement } from './jsx-runtime';
|
|
2
|
+
import { batchUpdates, isBatching } from './batch';
|
|
3
|
+
import { diff } from './vdom';
|
|
4
|
+
|
|
5
|
+
let currentRender: number = 0;
|
|
6
|
+
const states = new Map<number, any[]>();
|
|
7
|
+
const stateIndices = new Map<number, number>();
|
|
8
|
+
const effects = new Map<number, Effect[]>();
|
|
9
|
+
const memos = new Map<number, { value: any; deps: any[] }[]>();
|
|
10
|
+
const refs = new Map<number, any[]>();
|
|
11
|
+
|
|
12
|
+
interface Effect {
|
|
13
|
+
cleanup?: () => void;
|
|
14
|
+
deps?: any[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Add at the top with other declarations
|
|
18
|
+
let globalRenderCallback: ((element: any, container: HTMLElement) => void) | null = null;
|
|
19
|
+
let globalContainer: HTMLElement | null = null;
|
|
20
|
+
let currentElement: any = null;
|
|
21
|
+
|
|
22
|
+
const isServer = typeof window === 'undefined';
|
|
23
|
+
const serverStates = new Map<number, any>();
|
|
24
|
+
|
|
25
|
+
export function setRenderCallback(
|
|
26
|
+
callback: (element: any, container: HTMLElement) => void,
|
|
27
|
+
element: any,
|
|
28
|
+
container: HTMLElement
|
|
29
|
+
) {
|
|
30
|
+
globalRenderCallback = callback;
|
|
31
|
+
globalContainer = container;
|
|
32
|
+
currentElement = element;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function prepareRender() {
|
|
36
|
+
currentRender++;
|
|
37
|
+
stateIndices.set(currentRender, 0);
|
|
38
|
+
return currentRender;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function finishRender() {
|
|
42
|
+
if (isServer) {
|
|
43
|
+
serverStates.delete(currentRender);
|
|
44
|
+
}
|
|
45
|
+
currentRender = 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useState<T>(initial: T): [T, (value: T | ((prev: T) => T)) => void] {
|
|
49
|
+
if (!currentRender) {
|
|
50
|
+
throw new Error('useState must be called within a render');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isServer) {
|
|
54
|
+
// Server-side state handling
|
|
55
|
+
if (!serverStates.has(currentRender)) {
|
|
56
|
+
serverStates.set(currentRender, new Map());
|
|
57
|
+
}
|
|
58
|
+
const componentState = serverStates.get(currentRender)!;
|
|
59
|
+
const index = stateIndices.get(currentRender) || 0;
|
|
60
|
+
|
|
61
|
+
if (!componentState.has(index)) {
|
|
62
|
+
componentState.set(index, initial);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const state = componentState.get(index);
|
|
66
|
+
const setState = (newValue: T | ((prev: T) => T)) => {
|
|
67
|
+
// No-op for server-side
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
stateIndices.set(currentRender, index + 1);
|
|
71
|
+
return [state, setState];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!states.has(currentRender)) {
|
|
75
|
+
states.set(currentRender, []);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const componentStates = states.get(currentRender)!;
|
|
79
|
+
const index = stateIndices.get(currentRender)!;
|
|
80
|
+
|
|
81
|
+
if (index >= componentStates.length) {
|
|
82
|
+
componentStates.push(initial);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const state = componentStates[index];
|
|
86
|
+
const setState = (newValue: T | ((prev: T) => T)) => {
|
|
87
|
+
const nextValue = typeof newValue === 'function'
|
|
88
|
+
? (newValue as Function)(componentStates[index])
|
|
89
|
+
: newValue;
|
|
90
|
+
|
|
91
|
+
if (componentStates[index] === nextValue) return; // Skip if value hasn't changed
|
|
92
|
+
|
|
93
|
+
componentStates[index] = nextValue;
|
|
94
|
+
|
|
95
|
+
if (isBatching) {
|
|
96
|
+
batchUpdates(() => rerender(currentRender));
|
|
97
|
+
} else {
|
|
98
|
+
rerender(currentRender);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
stateIndices.set(currentRender, index + 1);
|
|
103
|
+
return [state, setState];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function useEffect(callback: () => (() => void) | void, deps?: any[]) {
|
|
107
|
+
if (!currentRender) throw new Error('useEffect must be called within a render');
|
|
108
|
+
|
|
109
|
+
const effectIndex = stateIndices.get(currentRender)!;
|
|
110
|
+
|
|
111
|
+
if (!effects.has(currentRender)) {
|
|
112
|
+
effects.set(currentRender, []);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const componentEffects = effects.get(currentRender)!;
|
|
116
|
+
const prevEffect = componentEffects[effectIndex];
|
|
117
|
+
|
|
118
|
+
// Run effect if deps changed
|
|
119
|
+
if (!prevEffect || !deps || !prevEffect.deps ||
|
|
120
|
+
deps.some((dep, i) => dep !== prevEffect.deps![i])) {
|
|
121
|
+
|
|
122
|
+
// Cleanup previous effect
|
|
123
|
+
if (prevEffect?.cleanup) {
|
|
124
|
+
prevEffect.cleanup();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Schedule new effect
|
|
128
|
+
queueMicrotask(() => {
|
|
129
|
+
const cleanup = callback() || undefined;
|
|
130
|
+
componentEffects[effectIndex] = { cleanup: cleanup, deps };
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
stateIndices.set(currentRender, effectIndex + 1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function useMemo<T>(factory: () => T, deps: any[]): T {
|
|
138
|
+
if (!currentRender) throw new Error('useMemo must be called within a render');
|
|
139
|
+
|
|
140
|
+
const memoIndex = stateIndices.get(currentRender)!;
|
|
141
|
+
|
|
142
|
+
if (!memos.has(currentRender)) {
|
|
143
|
+
memos.set(currentRender, []);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const componentMemos = memos.get(currentRender)!;
|
|
147
|
+
const prevMemo = componentMemos[memoIndex];
|
|
148
|
+
|
|
149
|
+
if (!prevMemo || (deps && deps.some((dep, i) => !Object.is(dep, prevMemo.deps[i])))) {
|
|
150
|
+
const value = factory();
|
|
151
|
+
componentMemos[memoIndex] = { value, deps };
|
|
152
|
+
stateIndices.set(currentRender, memoIndex + 1);
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
stateIndices.set(currentRender, memoIndex + 1);
|
|
157
|
+
return prevMemo.value;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function useRef<T>(initial: T) {
|
|
161
|
+
if (!currentRender) throw new Error('useRef must be called within a render');
|
|
162
|
+
|
|
163
|
+
const refIndex = stateIndices.get(currentRender)!;
|
|
164
|
+
|
|
165
|
+
if (!refs.has(currentRender)) {
|
|
166
|
+
refs.set(currentRender, []);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const componentRefs = refs.get(currentRender)!;
|
|
170
|
+
if (refIndex >= componentRefs.length) {
|
|
171
|
+
// Initialize with an object that has a current property
|
|
172
|
+
const ref = { current: initial };
|
|
173
|
+
componentRefs.push(ref);
|
|
174
|
+
stateIndices.set(currentRender, refIndex + 1);
|
|
175
|
+
return ref;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const ref = componentRefs[refIndex];
|
|
179
|
+
stateIndices.set(currentRender, refIndex + 1);
|
|
180
|
+
return ref;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Add a map to track component DOM nodes
|
|
184
|
+
const componentNodes = new Map<Function, Node>();
|
|
185
|
+
|
|
186
|
+
async function rerender(rendererId: number) {
|
|
187
|
+
try {
|
|
188
|
+
// Clean up effects
|
|
189
|
+
const componentEffects = effects.get(rendererId);
|
|
190
|
+
if (componentEffects) {
|
|
191
|
+
componentEffects.forEach(effect => {
|
|
192
|
+
if (effect.cleanup) effect.cleanup();
|
|
193
|
+
});
|
|
194
|
+
effects.set(rendererId, []);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (globalRenderCallback && globalContainer && currentElement) {
|
|
198
|
+
await globalRenderCallback(currentElement, globalContainer);
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error('Error during rerender:', error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Add new hook for error boundaries
|
|
206
|
+
export function useErrorBoundary(): [Error | null, () => void] {
|
|
207
|
+
const [error, setError] = useState<Error | null>(null);
|
|
208
|
+
return [error, () => setError(null)];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Remove withHooks export
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Frontend exports
|
|
2
|
+
import { jsx, jsxs, createElement, Fragment } from './jsx-runtime/index';
|
|
3
|
+
import { Component } from './component';
|
|
4
|
+
import { useState, useEffect, useRef, useMemo, useErrorBoundary } from './hooks';
|
|
5
|
+
import { render, hydrate } from './renderer';
|
|
6
|
+
import { renderToString } from './server-renderer';
|
|
7
|
+
import { prepareRender, finishRender } from './hooks';
|
|
8
|
+
import { batchUpdates } from './batch';
|
|
9
|
+
|
|
10
|
+
// Backend functionality
|
|
11
|
+
import { createServer } from './backend/server';
|
|
12
|
+
import { Router } from './backend/router';
|
|
13
|
+
import { createApiRouter } from './backend/api-utils';
|
|
14
|
+
|
|
15
|
+
// Export JSX runtime
|
|
16
|
+
export {
|
|
17
|
+
jsx,
|
|
18
|
+
jsxs,
|
|
19
|
+
createElement,
|
|
20
|
+
Fragment
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Export hooks
|
|
24
|
+
export {
|
|
25
|
+
useState,
|
|
26
|
+
useEffect,
|
|
27
|
+
useRef,
|
|
28
|
+
useMemo,
|
|
29
|
+
useErrorBoundary,
|
|
30
|
+
prepareRender,
|
|
31
|
+
finishRender,
|
|
32
|
+
batchUpdates
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Export component base class
|
|
36
|
+
export { Component };
|
|
37
|
+
|
|
38
|
+
// Export renderers
|
|
39
|
+
export {
|
|
40
|
+
render,
|
|
41
|
+
hydrate,
|
|
42
|
+
renderToString
|
|
43
|
+
};
|
|
44
|
+
// Export backend functionality
|
|
45
|
+
export {
|
|
46
|
+
createServer,
|
|
47
|
+
Router,
|
|
48
|
+
createApiRouter
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Default export with all functionality
|
|
52
|
+
export default {
|
|
53
|
+
// Frontend
|
|
54
|
+
jsx,
|
|
55
|
+
jsxs,
|
|
56
|
+
createElement,
|
|
57
|
+
Fragment,
|
|
58
|
+
Component,
|
|
59
|
+
useState,
|
|
60
|
+
useEffect,
|
|
61
|
+
useRef,
|
|
62
|
+
useMemo,
|
|
63
|
+
useErrorBoundary,
|
|
64
|
+
render,
|
|
65
|
+
hydrate,
|
|
66
|
+
renderToString,
|
|
67
|
+
prepareRender,
|
|
68
|
+
finishRender,
|
|
69
|
+
batchUpdates,
|
|
70
|
+
|
|
71
|
+
// Backend
|
|
72
|
+
createServer,
|
|
73
|
+
Router,
|
|
74
|
+
createApiRouter
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Re-export types
|
|
78
|
+
export type { Context } from './context';
|
|
79
|
+
export type { VNode } from './types';
|
|
80
|
+
|
|
81
|
+
// Backend exports
|
|
82
|
+
export { createAuth } from './backend/auth';
|
|
83
|
+
export { createModel, FieldTypes } from './backend/model';
|
|
84
|
+
export { createModelRouter, createApiRouter as createCustomRouter } from './backend/router';
|
|
85
|
+
export {
|
|
86
|
+
sendSuccess,
|
|
87
|
+
sendError,
|
|
88
|
+
apiResponse,
|
|
89
|
+
getPaginationParams,
|
|
90
|
+
paginationMiddleware,
|
|
91
|
+
validateRequest,
|
|
92
|
+
asyncHandler,
|
|
93
|
+
createRestEndpoints
|
|
94
|
+
} from './backend/api-utils';
|
|
95
|
+
export { DatabaseConnector } from './backend/database';
|
|
96
|
+
|
|
97
|
+
// Backend types
|
|
98
|
+
export type {
|
|
99
|
+
HamrounServerOptions,
|
|
100
|
+
RouterOptions,
|
|
101
|
+
RouteContext,
|
|
102
|
+
PaginationOptions,
|
|
103
|
+
PaginatedResult,
|
|
104
|
+
DatabaseOptions,
|
|
105
|
+
Model,
|
|
106
|
+
ApiResponse
|
|
107
|
+
} from './backend/types';
|
|
108
|
+
|
|
109
|
+
export type {
|
|
110
|
+
AuthOptions,
|
|
111
|
+
TokenPair
|
|
112
|
+
} from './backend/auth';
|
|
113
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx, jsxs, createElement, Fragment } from './jsx-runtime';
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
jsx,
|
|
5
|
+
jsxs,
|
|
6
|
+
createElement,
|
|
7
|
+
Fragment
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// For global access in browsers
|
|
11
|
+
if (typeof window !== 'undefined') {
|
|
12
|
+
// TypeScript safe way to add properties to window
|
|
13
|
+
(window as any).jsx = jsx;
|
|
14
|
+
(window as any).jsxs = jsxs;
|
|
15
|
+
(window as any).Fragment = Fragment;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Default export for module usage
|
|
19
|
+
export default {
|
|
20
|
+
jsx,
|
|
21
|
+
jsxs,
|
|
22
|
+
createElement,
|
|
23
|
+
Fragment
|
|
24
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { diff } from '../vdom';
|
|
2
|
+
|
|
3
|
+
// Create a safe global object reference that works in both browser and Node
|
|
4
|
+
const globalObj: any = typeof window !== 'undefined' ? window :
|
|
5
|
+
typeof global !== 'undefined' ? global : {};
|
|
6
|
+
|
|
7
|
+
export function jsx(type: string | Function, props: any, key?: string): any {
|
|
8
|
+
return {
|
|
9
|
+
type,
|
|
10
|
+
props: props || {},
|
|
11
|
+
key
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function jsxs(type: string | Function, props: any, key?: string): any {
|
|
16
|
+
return jsx(type, props, key);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createElement(vnode: any): HTMLElement | Text {
|
|
20
|
+
if (typeof vnode === 'string' || typeof vnode === 'number') {
|
|
21
|
+
return document.createTextNode(String(vnode));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (typeof vnode.type === 'function') {
|
|
25
|
+
const result = vnode.type(vnode.props);
|
|
26
|
+
return createElement(result);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const element = document.createElement(vnode.type);
|
|
30
|
+
|
|
31
|
+
Object.entries(vnode.props || {}).forEach(([name, value]: [string, any]) => {
|
|
32
|
+
if (name === 'children') {
|
|
33
|
+
const children = Array.isArray(value) ? value : [value];
|
|
34
|
+
children.forEach((child: any) => {
|
|
35
|
+
if (child != null) {
|
|
36
|
+
const childElement = createElement(child);
|
|
37
|
+
// Generate stats for testing
|
|
38
|
+
if (process.env.NODE_ENV === 'test' && typeof window !== 'undefined') {
|
|
39
|
+
// Create stats tracking
|
|
40
|
+
if (!globalObj.__renderStats) {
|
|
41
|
+
globalObj.__renderStats = {
|
|
42
|
+
elementsCreated: 0,
|
|
43
|
+
textNodesCreated: 0,
|
|
44
|
+
eventsAttached: 0,
|
|
45
|
+
renderTime: 0
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Write stats to file when tests complete
|
|
49
|
+
if (typeof afterAll === 'function') {
|
|
50
|
+
afterAll(() => {
|
|
51
|
+
try {
|
|
52
|
+
const fs = require('fs');
|
|
53
|
+
const path = require('path');
|
|
54
|
+
const statsPath = path.resolve(process.cwd(), 'jsx-runtime-stats.json');
|
|
55
|
+
fs.writeFileSync(statsPath, JSON.stringify(globalObj.__renderStats, null, 2));
|
|
56
|
+
console.log(`JSX runtime stats written to ${statsPath}`);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to write stats file:', error);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Increment stats based on element type
|
|
65
|
+
if (childElement instanceof Text) {
|
|
66
|
+
globalObj.__renderStats.textNodesCreated++;
|
|
67
|
+
} else {
|
|
68
|
+
globalObj.__renderStats.elementsCreated++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
element.appendChild(childElement);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} else if (name.startsWith('on')) {
|
|
76
|
+
const eventName = name.toLowerCase().substring(2);
|
|
77
|
+
element.addEventListener(eventName, value);
|
|
78
|
+
|
|
79
|
+
// Track event attachment in stats
|
|
80
|
+
if (process.env.NODE_ENV === 'test' && typeof window !== 'undefined' && globalObj.__renderStats) {
|
|
81
|
+
globalObj.__renderStats.eventsAttached++;
|
|
82
|
+
}
|
|
83
|
+
} else if (name === 'className') {
|
|
84
|
+
// Handle className specially by setting it as the class attribute
|
|
85
|
+
element.setAttribute('class', value);
|
|
86
|
+
} else if (name === 'style' && typeof value === 'object') {
|
|
87
|
+
// Handle style objects by merging them into element.style
|
|
88
|
+
Object.entries(value).forEach(([styleProp, styleValue]) => {
|
|
89
|
+
element.style[styleProp as any] = String(styleValue);
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
element.setAttribute(name, value);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return element;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const Fragment = Symbol('Fragment');
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { Component } from './component';
|
|
2
|
+
|
|
3
|
+
interface VNode {
|
|
4
|
+
type: string | Function;
|
|
5
|
+
props: Record<string, any>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function jsx(type: string | Function, props: any, p0: string): VNode {
|
|
9
|
+
console.log('JSX Transform:', { type, props });
|
|
10
|
+
const processedProps = { ...props };
|
|
11
|
+
|
|
12
|
+
// Handle children properly
|
|
13
|
+
if (arguments.length > 2) {
|
|
14
|
+
processedProps.children = Array.prototype.slice.call(arguments, 2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { type, props: processedProps };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const Fragment = ({ children }: { children: any }) => children;
|
|
21
|
+
|
|
22
|
+
// Check if we're in a browser environment
|
|
23
|
+
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
24
|
+
|
|
25
|
+
async function createElement(vnode: VNode | any): Promise<Node> {
|
|
26
|
+
console.log('Creating element from:', vnode);
|
|
27
|
+
|
|
28
|
+
// Create mock DOM elements when in Node environment
|
|
29
|
+
if (!isBrowser) {
|
|
30
|
+
// Return mock node objects in Node.js environment
|
|
31
|
+
if (vnode == null) {
|
|
32
|
+
return { nodeType: 3, textContent: '' } as any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (typeof vnode === 'boolean') {
|
|
36
|
+
return { nodeType: 3, textContent: '' } as any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof vnode === 'number' || typeof vnode === 'string') {
|
|
40
|
+
return { nodeType: 3, textContent: String(vnode) } as any;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle arrays
|
|
44
|
+
if (Array.isArray(vnode)) {
|
|
45
|
+
const fragment = { nodeType: 11, childNodes: [] } as any;
|
|
46
|
+
for (const child of vnode) {
|
|
47
|
+
const node = await createElement(child);
|
|
48
|
+
fragment.childNodes.push(node);
|
|
49
|
+
}
|
|
50
|
+
return fragment;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle VNode
|
|
54
|
+
if ('type' in vnode && vnode.props !== undefined) {
|
|
55
|
+
const { type, props } = vnode;
|
|
56
|
+
|
|
57
|
+
// Handle function components
|
|
58
|
+
if (typeof type === 'function') {
|
|
59
|
+
try {
|
|
60
|
+
const result = await type(props || {});
|
|
61
|
+
const node = await createElement(result);
|
|
62
|
+
return node;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Error rendering component:', error);
|
|
65
|
+
return { nodeType: 3, textContent: '' } as any;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Mock element creation
|
|
70
|
+
const element = {
|
|
71
|
+
nodeType: 1,
|
|
72
|
+
tagName: type,
|
|
73
|
+
attributes: {},
|
|
74
|
+
style: {},
|
|
75
|
+
childNodes: [],
|
|
76
|
+
setAttribute: function(key: string, value: string) {
|
|
77
|
+
this.attributes[key] = value;
|
|
78
|
+
},
|
|
79
|
+
appendChild: function(child: any) {
|
|
80
|
+
this.childNodes.push(child);
|
|
81
|
+
}
|
|
82
|
+
} as any;
|
|
83
|
+
|
|
84
|
+
// Handle props
|
|
85
|
+
for (const [key, value] of Object.entries(props || {})) {
|
|
86
|
+
if (key === 'children') continue;
|
|
87
|
+
if (key.startsWith('on') && typeof value === 'function') {
|
|
88
|
+
// Mock event handlers
|
|
89
|
+
const eventName = key.toLowerCase().slice(2);
|
|
90
|
+
if (!element.__events) {
|
|
91
|
+
element.__events = {};
|
|
92
|
+
}
|
|
93
|
+
element.__events[eventName] = value;
|
|
94
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
95
|
+
Object.assign(element.style, value);
|
|
96
|
+
} else if (key === 'className') {
|
|
97
|
+
element.setAttribute('class', String(value));
|
|
98
|
+
} else if (key !== 'key' && key !== 'ref') {
|
|
99
|
+
element.setAttribute(key, String(value));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Handle children
|
|
104
|
+
const children = props?.children;
|
|
105
|
+
if (children != null) {
|
|
106
|
+
const childArray = Array.isArray(children) ? children.flat() : [children];
|
|
107
|
+
for (const child of childArray) {
|
|
108
|
+
const childNode = await createElement(child);
|
|
109
|
+
element.appendChild(childNode);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return element;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle other objects by converting to string
|
|
117
|
+
return { nodeType: 3, textContent: String(vnode) } as any;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Browser environment implementation
|
|
121
|
+
if (vnode == null) {
|
|
122
|
+
return document.createTextNode('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (typeof vnode === 'boolean') {
|
|
126
|
+
return document.createTextNode('');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (typeof vnode === 'number' || typeof vnode === 'string') {
|
|
130
|
+
return document.createTextNode(String(vnode));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Handle arrays
|
|
134
|
+
if (Array.isArray(vnode)) {
|
|
135
|
+
const fragment = document.createDocumentFragment();
|
|
136
|
+
for (const child of vnode) {
|
|
137
|
+
const node = await createElement(child);
|
|
138
|
+
fragment.appendChild(node);
|
|
139
|
+
}
|
|
140
|
+
return fragment;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Handle VNode
|
|
144
|
+
if ('type' in vnode && vnode.props !== undefined) {
|
|
145
|
+
const { type, props } = vnode;
|
|
146
|
+
|
|
147
|
+
// Handle function components
|
|
148
|
+
if (typeof type === 'function') {
|
|
149
|
+
try {
|
|
150
|
+
const result = await type(props || {});
|
|
151
|
+
const node = await createElement(result);
|
|
152
|
+
if (node instanceof Element) {
|
|
153
|
+
node.setAttribute('data-component-id', type.name || type.toString());
|
|
154
|
+
}
|
|
155
|
+
return node;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error('Error rendering component:', error);
|
|
158
|
+
return document.createTextNode('');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Create DOM element
|
|
163
|
+
const element = document.createElement(type as string);
|
|
164
|
+
|
|
165
|
+
// Handle props
|
|
166
|
+
for (const [key, value] of Object.entries(props || {})) {
|
|
167
|
+
if (key === 'children') continue;
|
|
168
|
+
if (key.startsWith('on') && typeof value === 'function') {
|
|
169
|
+
const eventName = key.toLowerCase().slice(2);
|
|
170
|
+
// Remove existing event listener if any
|
|
171
|
+
const existingHandler = (element as any).__events?.[eventName];
|
|
172
|
+
if (existingHandler) {
|
|
173
|
+
element.removeEventListener(eventName, existingHandler);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Add new event listener
|
|
177
|
+
element.addEventListener(eventName, value as EventListener);
|
|
178
|
+
if (!(element as any).__events) {
|
|
179
|
+
(element as any).__events = {};
|
|
180
|
+
}
|
|
181
|
+
(element as any).__events[eventName] = value;
|
|
182
|
+
} else if (key === 'style' && typeof value === 'object') {
|
|
183
|
+
Object.assign(element.style, value);
|
|
184
|
+
} else if (key === 'className') {
|
|
185
|
+
element.setAttribute('class', String(value));
|
|
186
|
+
} else if (key !== 'key' && key !== 'ref') {
|
|
187
|
+
element.setAttribute(key, String(value));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle children
|
|
192
|
+
const children = props?.children;
|
|
193
|
+
if (children != null) {
|
|
194
|
+
const childArray = Array.isArray(children) ? children.flat() : [children];
|
|
195
|
+
for (const child of childArray) {
|
|
196
|
+
const childNode = await createElement(child);
|
|
197
|
+
element.appendChild(childNode);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return element;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Handle other objects by converting to string
|
|
205
|
+
return document.createTextNode(String(vnode));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Export named functions and aliases without duplicates
|
|
209
|
+
export {
|
|
210
|
+
jsx,
|
|
211
|
+
jsx as jsxs,
|
|
212
|
+
jsx as jsxDEV,
|
|
213
|
+
Fragment,
|
|
214
|
+
createElement
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Named exports object
|
|
218
|
+
const jsxRuntime = {
|
|
219
|
+
jsx,
|
|
220
|
+
jsxs: jsx,
|
|
221
|
+
jsxDEV: jsx,
|
|
222
|
+
Fragment,
|
|
223
|
+
createElement
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export default jsxRuntime;
|