create-aziz-app 3.1.2 → 3.1.3
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/README.md +658 -207
- package/bin/create-aziz-app.js +302 -302
- package/package.json +1 -1
- package/template/README.md +402 -402
- package/template/_framework/api.js +219 -219
- package/template/_framework/boot.js +33 -33
- package/template/_framework/hooks.js +367 -291
- package/template/_framework/next-response.js +128 -128
- package/template/_framework/route-match.js +145 -145
- package/template/_framework/router.js +465 -259
- package/template/_framework/runtime.js +264 -264
- package/template/app/layout.js +18 -18
- package/template/app/page.js +33 -33
- package/template/docs/01-getting-started.md +151 -151
- package/template/docs/02-project-structure.md +109 -109
- package/template/docs/03-pages-and-layouts.md +140 -140
- package/template/docs/04-routing.md +120 -120
- package/template/docs/05-hooks.md +169 -169
- package/template/docs/06-api-routes.md +210 -210
- package/template/docs/07-api-client.md +151 -151
- package/template/docs/08-styling.md +94 -94
- package/template/docs/09-imports-and-modules.md +97 -97
- package/template/docs/10-database-and-models.md +154 -154
- package/template/docs/11-authentication.md +171 -171
- package/template/docs/12-production-and-deployment.md +121 -121
- package/template/docs/13-internals.md +179 -179
- package/template/docs/README.md +67 -67
- package/template/index.html +20 -20
- package/template/package.json +16 -16
- package/template/scripts/dev-server.js +493 -381
- package/template/scripts/lib/api-router.js +525 -525
- package/template/scripts/lib/js-transform.js +213 -195
- package/template/scripts/lib/module-resolver.js +132 -132
|
@@ -1,264 +1,264 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Aziz.js Runtime
|
|
3
|
-
//
|
|
4
|
-
// h() — JSX factory (like React.createElement)
|
|
5
|
-
// materialize — turn a node tree into real DOM
|
|
6
|
-
// mountPage — mount a page + its layout chain
|
|
7
|
-
//
|
|
8
|
-
// Rendering model
|
|
9
|
-
// ---------------
|
|
10
|
-
// Function (component) tags are LAZY: h(Component) returns a
|
|
11
|
-
// lightweight descriptor. Descriptors are executed top-down when
|
|
12
|
-
// they are attached to the DOM, which gives every component
|
|
13
|
-
// instance its own hook "fiber" (keyed by its position in the
|
|
14
|
-
// tree) and makes context propagation work correctly.
|
|
15
|
-
// ============================================================
|
|
16
|
-
|
|
17
|
-
import { prepareHooks, finishHooks, setMaterializer } from './hooks.js';
|
|
18
|
-
|
|
19
|
-
export const Fragment = Symbol('Fragment');
|
|
20
|
-
|
|
21
|
-
const VNODE = Symbol('aziz.vnode');
|
|
22
|
-
|
|
23
|
-
// ─── Fibers ──────────────────────────────────────────────────
|
|
24
|
-
// One fiber per component instance, keyed by a stable tree path.
|
|
25
|
-
|
|
26
|
-
const fibers = new Map();
|
|
27
|
-
|
|
28
|
-
export function getFiber(id) {
|
|
29
|
-
let fiber = fibers.get(id);
|
|
30
|
-
if (!fiber) {
|
|
31
|
-
fiber = { id, hooks: [], mounted: true };
|
|
32
|
-
fibers.set(id, fiber);
|
|
33
|
-
} else {
|
|
34
|
-
fiber.mounted = true;
|
|
35
|
-
}
|
|
36
|
-
return fiber;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function fiberCount() {
|
|
40
|
-
return fibers.size;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Unmount everything: run pending effect cleanups, drop state.
|
|
44
|
-
export function disposeFibers() {
|
|
45
|
-
for (const fiber of fibers.values()) {
|
|
46
|
-
fiber.mounted = false;
|
|
47
|
-
for (const hook of fiber.hooks) {
|
|
48
|
-
if (hook && typeof hook.cleanup === 'function') {
|
|
49
|
-
try { hook.cleanup(); } catch (err) { console.error('Error in effect cleanup:', err); }
|
|
50
|
-
hook.cleanup = null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
fibers.clear();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ─── Render context ──────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
const pathStack = []; // component segments, outermost first
|
|
60
|
-
const counters = []; // per-component occurrence counters
|
|
61
|
-
let rootCounter = 0;
|
|
62
|
-
let baseId = '';
|
|
63
|
-
|
|
64
|
-
// Begin a fresh render pass rooted at `base` (e.g. "page:/about").
|
|
65
|
-
export function beginRender(base) {
|
|
66
|
-
baseId = base || '';
|
|
67
|
-
pathStack.length = 0;
|
|
68
|
-
counters.length = 0;
|
|
69
|
-
rootCounter = 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function nextKey(props) {
|
|
73
|
-
if (props && props.key !== undefined && props.key !== null) return 'k:' + props.key;
|
|
74
|
-
if (counters.length === 0) return 'i:' + (++rootCounter);
|
|
75
|
-
const top = counters.length - 1;
|
|
76
|
-
return 'i:' + (++counters[top]);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function componentName(fn) {
|
|
80
|
-
return fn.displayName || fn.name || 'Anonymous';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ─── JSX factory ─────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
export function h(tag, props, ...children) {
|
|
86
|
-
// Everything is a lazy descriptor. Elements are only turned into real
|
|
87
|
-
// DOM during materialization, so nested components are always executed
|
|
88
|
-
// top-down (giving correct context + hook scoping).
|
|
89
|
-
let kids = flatChildren(children);
|
|
90
|
-
if (kids.length === 0 && props && props.children !== undefined && props.children !== null) {
|
|
91
|
-
kids = flatChildren(Array.isArray(props.children) ? props.children : [props.children]);
|
|
92
|
-
}
|
|
93
|
-
return { [VNODE]: true, type: tag, props: props || null, children: kids };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function flatChildren(children) {
|
|
97
|
-
return children
|
|
98
|
-
.flat(Infinity)
|
|
99
|
-
.filter(c => c !== null && c !== undefined && c !== false && c !== true);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ─── DOM element creation (eager) ────────────────────────────
|
|
103
|
-
|
|
104
|
-
function createElement(tag, props, kids) {
|
|
105
|
-
const el = document.createElement(tag);
|
|
106
|
-
applyProps(el, tag, props);
|
|
107
|
-
appendNodes(el, materialize(kids));
|
|
108
|
-
return el;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const EVENT_ALIASES = {
|
|
112
|
-
doubleclick: 'dblclick',
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
function eventNameFromProp(key) {
|
|
116
|
-
const name = key.slice(2).toLowerCase();
|
|
117
|
-
return EVENT_ALIASES[name] || name;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function applyProps(el, tag, props) {
|
|
121
|
-
if (!props) return;
|
|
122
|
-
|
|
123
|
-
for (const key in props) {
|
|
124
|
-
if (key === 'children' || key === 'key') continue;
|
|
125
|
-
const value = props[key];
|
|
126
|
-
|
|
127
|
-
if (key === 'ref') {
|
|
128
|
-
if (typeof value === 'function') value(el);
|
|
129
|
-
else if (value && typeof value === 'object') value.current = el;
|
|
130
|
-
} else if (key === 'className' || key === 'class') {
|
|
131
|
-
if (value != null) el.className = value;
|
|
132
|
-
} else if (key === 'htmlFor') {
|
|
133
|
-
if (value != null) el.setAttribute('for', value);
|
|
134
|
-
} else if (key === 'style') {
|
|
135
|
-
if (value && typeof value === 'object') Object.assign(el.style, value);
|
|
136
|
-
else if (value != null) el.setAttribute('style', value);
|
|
137
|
-
} else if (key === 'dangerouslySetInnerHTML') {
|
|
138
|
-
if (value && value.__html != null) el.innerHTML = value.__html;
|
|
139
|
-
} else if (key === 'value' && (tag === 'input' || tag === 'textarea' || tag === 'select')) {
|
|
140
|
-
el.value = value == null ? '' : value;
|
|
141
|
-
} else if (key === 'checked' && tag === 'input') {
|
|
142
|
-
el.checked = !!value;
|
|
143
|
-
} else if (key.startsWith('on') && typeof value === 'function') {
|
|
144
|
-
el.addEventListener(eventNameFromProp(key), value);
|
|
145
|
-
} else if (typeof value === 'boolean') {
|
|
146
|
-
if (value) el.setAttribute(key, '');
|
|
147
|
-
else el.removeAttribute(key);
|
|
148
|
-
} else if (value != null) {
|
|
149
|
-
el.setAttribute(key, value);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ─── Materialization ─────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
// Turn any node value into real DOM node(s).
|
|
157
|
-
// Returns: Node | Node[] | null
|
|
158
|
-
export function materialize(node) {
|
|
159
|
-
if (node === null || node === undefined || node === false || node === true) return null;
|
|
160
|
-
|
|
161
|
-
if (Array.isArray(node)) {
|
|
162
|
-
const out = [];
|
|
163
|
-
for (const child of node) {
|
|
164
|
-
const m = materialize(child);
|
|
165
|
-
if (Array.isArray(m)) {
|
|
166
|
-
for (const n of m) if (n !== null) out.push(n);
|
|
167
|
-
} else if (m !== null) {
|
|
168
|
-
out.push(m);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return out;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (typeof node === 'object' && node[VNODE]) {
|
|
175
|
-
return materializeVNode(node);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (typeof Node !== 'undefined' && node instanceof Node) return node;
|
|
179
|
-
|
|
180
|
-
return document.createTextNode(String(node));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function materializeVNode(vnode) {
|
|
184
|
-
const { type, props, children } = vnode;
|
|
185
|
-
|
|
186
|
-
if (type === Fragment) return materialize(children);
|
|
187
|
-
if (typeof type === 'function') return materializeComponent(type, props, children);
|
|
188
|
-
|
|
189
|
-
return createElement(type, props, children);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function materializeComponent(type, props, children) {
|
|
193
|
-
const segment = componentName(type) + '@' + nextKey(props);
|
|
194
|
-
pathStack.push(segment);
|
|
195
|
-
counters.push(0);
|
|
196
|
-
|
|
197
|
-
const id = (baseId ? baseId + '>' : '') + pathStack.join('>');
|
|
198
|
-
const fiber = getFiber(id);
|
|
199
|
-
|
|
200
|
-
prepareHooks(fiber);
|
|
201
|
-
|
|
202
|
-
let output;
|
|
203
|
-
try {
|
|
204
|
-
output = type({ ...(props || {}), children });
|
|
205
|
-
finishHooks();
|
|
206
|
-
} catch (err) {
|
|
207
|
-
finishHooks();
|
|
208
|
-
counters.pop();
|
|
209
|
-
pathStack.pop();
|
|
210
|
-
throw err;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Materialize the component's output while its scope is still on the
|
|
214
|
-
// stack so child fibers nest under this component (stable keys).
|
|
215
|
-
const nodes = materialize(output);
|
|
216
|
-
counters.pop();
|
|
217
|
-
pathStack.pop();
|
|
218
|
-
return nodes;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function appendNodes(container, nodes) {
|
|
222
|
-
if (nodes === null || nodes === undefined) return;
|
|
223
|
-
if (Array.isArray(nodes)) {
|
|
224
|
-
for (const n of nodes) {
|
|
225
|
-
if (n !== null && n !== undefined) container.appendChild(n);
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
container.appendChild(nodes);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ─── Mounting ────────────────────────────────────────────────
|
|
233
|
-
|
|
234
|
-
// Mount a page wrapped in its layout chain.
|
|
235
|
-
// `layouts` must be ordered outermost → innermost.
|
|
236
|
-
export function mountPage(PageComponent, layouts, container, base) {
|
|
237
|
-
beginRender(base || 'page');
|
|
238
|
-
|
|
239
|
-
let tree = h(PageComponent, {});
|
|
240
|
-
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
241
|
-
tree = h(layouts[i], { children: tree });
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const nodes = materialize(tree);
|
|
245
|
-
container.innerHTML = '';
|
|
246
|
-
appendNodes(container, nodes);
|
|
247
|
-
|
|
248
|
-
return container;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Simple mount helper (kept for backwards compatibility).
|
|
252
|
-
export function render(component, container) {
|
|
253
|
-
beginRender('root');
|
|
254
|
-
const tree = typeof component === 'function' ? h(component, {}) : component;
|
|
255
|
-
const nodes = materialize(tree);
|
|
256
|
-
container.innerHTML = '';
|
|
257
|
-
appendNodes(container, nodes);
|
|
258
|
-
return container;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Let hooks.js materialize context-provider children in scope.
|
|
262
|
-
setMaterializer(materialize);
|
|
263
|
-
|
|
264
|
-
export default { h, render, materialize, Fragment, mountPage, disposeFibers, getFiber };
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Aziz.js Runtime
|
|
3
|
+
//
|
|
4
|
+
// h() — JSX factory (like React.createElement)
|
|
5
|
+
// materialize — turn a node tree into real DOM
|
|
6
|
+
// mountPage — mount a page + its layout chain
|
|
7
|
+
//
|
|
8
|
+
// Rendering model
|
|
9
|
+
// ---------------
|
|
10
|
+
// Function (component) tags are LAZY: h(Component) returns a
|
|
11
|
+
// lightweight descriptor. Descriptors are executed top-down when
|
|
12
|
+
// they are attached to the DOM, which gives every component
|
|
13
|
+
// instance its own hook "fiber" (keyed by its position in the
|
|
14
|
+
// tree) and makes context propagation work correctly.
|
|
15
|
+
// ============================================================
|
|
16
|
+
|
|
17
|
+
import { prepareHooks, finishHooks, setMaterializer } from './hooks.js';
|
|
18
|
+
|
|
19
|
+
export const Fragment = Symbol('Fragment');
|
|
20
|
+
|
|
21
|
+
const VNODE = Symbol('aziz.vnode');
|
|
22
|
+
|
|
23
|
+
// ─── Fibers ──────────────────────────────────────────────────
|
|
24
|
+
// One fiber per component instance, keyed by a stable tree path.
|
|
25
|
+
|
|
26
|
+
const fibers = new Map();
|
|
27
|
+
|
|
28
|
+
export function getFiber(id) {
|
|
29
|
+
let fiber = fibers.get(id);
|
|
30
|
+
if (!fiber) {
|
|
31
|
+
fiber = { id, hooks: [], mounted: true };
|
|
32
|
+
fibers.set(id, fiber);
|
|
33
|
+
} else {
|
|
34
|
+
fiber.mounted = true;
|
|
35
|
+
}
|
|
36
|
+
return fiber;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function fiberCount() {
|
|
40
|
+
return fibers.size;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Unmount everything: run pending effect cleanups, drop state.
|
|
44
|
+
export function disposeFibers() {
|
|
45
|
+
for (const fiber of fibers.values()) {
|
|
46
|
+
fiber.mounted = false;
|
|
47
|
+
for (const hook of fiber.hooks) {
|
|
48
|
+
if (hook && typeof hook.cleanup === 'function') {
|
|
49
|
+
try { hook.cleanup(); } catch (err) { console.error('Error in effect cleanup:', err); }
|
|
50
|
+
hook.cleanup = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
fibers.clear();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Render context ──────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
const pathStack = []; // component segments, outermost first
|
|
60
|
+
const counters = []; // per-component occurrence counters
|
|
61
|
+
let rootCounter = 0;
|
|
62
|
+
let baseId = '';
|
|
63
|
+
|
|
64
|
+
// Begin a fresh render pass rooted at `base` (e.g. "page:/about").
|
|
65
|
+
export function beginRender(base) {
|
|
66
|
+
baseId = base || '';
|
|
67
|
+
pathStack.length = 0;
|
|
68
|
+
counters.length = 0;
|
|
69
|
+
rootCounter = 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function nextKey(props) {
|
|
73
|
+
if (props && props.key !== undefined && props.key !== null) return 'k:' + props.key;
|
|
74
|
+
if (counters.length === 0) return 'i:' + (++rootCounter);
|
|
75
|
+
const top = counters.length - 1;
|
|
76
|
+
return 'i:' + (++counters[top]);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function componentName(fn) {
|
|
80
|
+
return fn.displayName || fn.name || 'Anonymous';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── JSX factory ─────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export function h(tag, props, ...children) {
|
|
86
|
+
// Everything is a lazy descriptor. Elements are only turned into real
|
|
87
|
+
// DOM during materialization, so nested components are always executed
|
|
88
|
+
// top-down (giving correct context + hook scoping).
|
|
89
|
+
let kids = flatChildren(children);
|
|
90
|
+
if (kids.length === 0 && props && props.children !== undefined && props.children !== null) {
|
|
91
|
+
kids = flatChildren(Array.isArray(props.children) ? props.children : [props.children]);
|
|
92
|
+
}
|
|
93
|
+
return { [VNODE]: true, type: tag, props: props || null, children: kids };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function flatChildren(children) {
|
|
97
|
+
return children
|
|
98
|
+
.flat(Infinity)
|
|
99
|
+
.filter(c => c !== null && c !== undefined && c !== false && c !== true);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── DOM element creation (eager) ────────────────────────────
|
|
103
|
+
|
|
104
|
+
function createElement(tag, props, kids) {
|
|
105
|
+
const el = document.createElement(tag);
|
|
106
|
+
applyProps(el, tag, props);
|
|
107
|
+
appendNodes(el, materialize(kids));
|
|
108
|
+
return el;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const EVENT_ALIASES = {
|
|
112
|
+
doubleclick: 'dblclick',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function eventNameFromProp(key) {
|
|
116
|
+
const name = key.slice(2).toLowerCase();
|
|
117
|
+
return EVENT_ALIASES[name] || name;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function applyProps(el, tag, props) {
|
|
121
|
+
if (!props) return;
|
|
122
|
+
|
|
123
|
+
for (const key in props) {
|
|
124
|
+
if (key === 'children' || key === 'key') continue;
|
|
125
|
+
const value = props[key];
|
|
126
|
+
|
|
127
|
+
if (key === 'ref') {
|
|
128
|
+
if (typeof value === 'function') value(el);
|
|
129
|
+
else if (value && typeof value === 'object') value.current = el;
|
|
130
|
+
} else if (key === 'className' || key === 'class') {
|
|
131
|
+
if (value != null) el.className = value;
|
|
132
|
+
} else if (key === 'htmlFor') {
|
|
133
|
+
if (value != null) el.setAttribute('for', value);
|
|
134
|
+
} else if (key === 'style') {
|
|
135
|
+
if (value && typeof value === 'object') Object.assign(el.style, value);
|
|
136
|
+
else if (value != null) el.setAttribute('style', value);
|
|
137
|
+
} else if (key === 'dangerouslySetInnerHTML') {
|
|
138
|
+
if (value && value.__html != null) el.innerHTML = value.__html;
|
|
139
|
+
} else if (key === 'value' && (tag === 'input' || tag === 'textarea' || tag === 'select')) {
|
|
140
|
+
el.value = value == null ? '' : value;
|
|
141
|
+
} else if (key === 'checked' && tag === 'input') {
|
|
142
|
+
el.checked = !!value;
|
|
143
|
+
} else if (key.startsWith('on') && typeof value === 'function') {
|
|
144
|
+
el.addEventListener(eventNameFromProp(key), value);
|
|
145
|
+
} else if (typeof value === 'boolean') {
|
|
146
|
+
if (value) el.setAttribute(key, '');
|
|
147
|
+
else el.removeAttribute(key);
|
|
148
|
+
} else if (value != null) {
|
|
149
|
+
el.setAttribute(key, value);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Materialization ─────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
// Turn any node value into real DOM node(s).
|
|
157
|
+
// Returns: Node | Node[] | null
|
|
158
|
+
export function materialize(node) {
|
|
159
|
+
if (node === null || node === undefined || node === false || node === true) return null;
|
|
160
|
+
|
|
161
|
+
if (Array.isArray(node)) {
|
|
162
|
+
const out = [];
|
|
163
|
+
for (const child of node) {
|
|
164
|
+
const m = materialize(child);
|
|
165
|
+
if (Array.isArray(m)) {
|
|
166
|
+
for (const n of m) if (n !== null) out.push(n);
|
|
167
|
+
} else if (m !== null) {
|
|
168
|
+
out.push(m);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (typeof node === 'object' && node[VNODE]) {
|
|
175
|
+
return materializeVNode(node);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (typeof Node !== 'undefined' && node instanceof Node) return node;
|
|
179
|
+
|
|
180
|
+
return document.createTextNode(String(node));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function materializeVNode(vnode) {
|
|
184
|
+
const { type, props, children } = vnode;
|
|
185
|
+
|
|
186
|
+
if (type === Fragment) return materialize(children);
|
|
187
|
+
if (typeof type === 'function') return materializeComponent(type, props, children);
|
|
188
|
+
|
|
189
|
+
return createElement(type, props, children);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function materializeComponent(type, props, children) {
|
|
193
|
+
const segment = componentName(type) + '@' + nextKey(props);
|
|
194
|
+
pathStack.push(segment);
|
|
195
|
+
counters.push(0);
|
|
196
|
+
|
|
197
|
+
const id = (baseId ? baseId + '>' : '') + pathStack.join('>');
|
|
198
|
+
const fiber = getFiber(id);
|
|
199
|
+
|
|
200
|
+
prepareHooks(fiber);
|
|
201
|
+
|
|
202
|
+
let output;
|
|
203
|
+
try {
|
|
204
|
+
output = type({ ...(props || {}), children });
|
|
205
|
+
finishHooks();
|
|
206
|
+
} catch (err) {
|
|
207
|
+
finishHooks();
|
|
208
|
+
counters.pop();
|
|
209
|
+
pathStack.pop();
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Materialize the component's output while its scope is still on the
|
|
214
|
+
// stack so child fibers nest under this component (stable keys).
|
|
215
|
+
const nodes = materialize(output);
|
|
216
|
+
counters.pop();
|
|
217
|
+
pathStack.pop();
|
|
218
|
+
return nodes;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function appendNodes(container, nodes) {
|
|
222
|
+
if (nodes === null || nodes === undefined) return;
|
|
223
|
+
if (Array.isArray(nodes)) {
|
|
224
|
+
for (const n of nodes) {
|
|
225
|
+
if (n !== null && n !== undefined) container.appendChild(n);
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
container.appendChild(nodes);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─── Mounting ────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
// Mount a page wrapped in its layout chain.
|
|
235
|
+
// `layouts` must be ordered outermost → innermost.
|
|
236
|
+
export function mountPage(PageComponent, layouts, container, base, props) {
|
|
237
|
+
beginRender(base || 'page');
|
|
238
|
+
|
|
239
|
+
let tree = h(PageComponent, props || {});
|
|
240
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
241
|
+
tree = h(layouts[i], { children: tree });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const nodes = materialize(tree);
|
|
245
|
+
container.innerHTML = '';
|
|
246
|
+
appendNodes(container, nodes);
|
|
247
|
+
|
|
248
|
+
return container;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Simple mount helper (kept for backwards compatibility).
|
|
252
|
+
export function render(component, container) {
|
|
253
|
+
beginRender('root');
|
|
254
|
+
const tree = typeof component === 'function' ? h(component, {}) : component;
|
|
255
|
+
const nodes = materialize(tree);
|
|
256
|
+
container.innerHTML = '';
|
|
257
|
+
appendNodes(container, nodes);
|
|
258
|
+
return container;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Let hooks.js materialize context-provider children in scope.
|
|
262
|
+
setMaterializer(materialize);
|
|
263
|
+
|
|
264
|
+
export default { h, render, materialize, Fragment, mountPage, disposeFibers, getFiber };
|
package/template/app/layout.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// Root layout — wraps every page (like Next.js app/layout.js)
|
|
2
|
-
export default function Layout({ children }) {
|
|
3
|
-
return (
|
|
4
|
-
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
|
5
|
-
<main style={{ flex: 1 }}>
|
|
6
|
-
{children}
|
|
7
|
-
</main>
|
|
8
|
-
<footer>
|
|
9
|
-
Built with Aziz.js
|
|
10
|
-
</footer>
|
|
11
|
-
</div>
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const metadata = {
|
|
16
|
-
title: 'My App',
|
|
17
|
-
description: 'Built with Aziz.js'
|
|
18
|
-
};
|
|
1
|
+
// Root layout — wraps every page (like Next.js app/layout.js)
|
|
2
|
+
export default function Layout({ children }) {
|
|
3
|
+
return (
|
|
4
|
+
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
|
5
|
+
<main style={{ flex: 1 }}>
|
|
6
|
+
{children}
|
|
7
|
+
</main>
|
|
8
|
+
<footer>
|
|
9
|
+
Built with Aziz.js
|
|
10
|
+
</footer>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const metadata = {
|
|
16
|
+
title: 'My App',
|
|
17
|
+
description: 'Built with Aziz.js'
|
|
18
|
+
};
|
package/template/app/page.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
// Home page — route: /
|
|
2
|
-
export default function Page() {
|
|
3
|
-
const [count, setCount] = useState(0);
|
|
4
|
-
|
|
5
|
-
return (
|
|
6
|
-
<div style={{ padding: '48px 32px', maxWidth: '720px', margin: '0 auto' }}>
|
|
7
|
-
<h1>Welcome to Aziz.js</h1>
|
|
8
|
-
<p>
|
|
9
|
-
Your app is running. Edit <code>app/page.js</code> and save —
|
|
10
|
-
the browser refreshes automatically.
|
|
11
|
-
</p>
|
|
12
|
-
|
|
13
|
-
<div class="card">
|
|
14
|
-
<h2>Interactive state</h2>
|
|
15
|
-
<p>A tiny slice of state, exactly like React's <code>useState</code>.</p>
|
|
16
|
-
<div class="count-display">{count}</div>
|
|
17
|
-
<div class="counter-actions">
|
|
18
|
-
<button class="btn btn-danger" onClick={() => setCount(c => c - 1)}>−</button>
|
|
19
|
-
<button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
|
|
20
|
-
<button class="btn btn-grey" onClick={() => setCount(0)}>Reset</button>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
|
|
24
|
-
<div class="card">
|
|
25
|
-
<h2>Add a page</h2>
|
|
26
|
-
<p>
|
|
27
|
-
Each <code>page.js</code> inside <code>app/</code> is a route.
|
|
28
|
-
Create <code>app/about/page.js</code> and it becomes <code>/about</code>.
|
|
29
|
-
</p>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
1
|
+
// Home page — route: /
|
|
2
|
+
export default function Page() {
|
|
3
|
+
const [count, setCount] = useState(0);
|
|
4
|
+
|
|
5
|
+
return (
|
|
6
|
+
<div style={{ padding: '48px 32px', maxWidth: '720px', margin: '0 auto' }}>
|
|
7
|
+
<h1>Welcome to Aziz.js</h1>
|
|
8
|
+
<p>
|
|
9
|
+
Your app is running. Edit <code>app/page.js</code> and save —
|
|
10
|
+
the browser refreshes automatically.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<div class="card">
|
|
14
|
+
<h2>Interactive state</h2>
|
|
15
|
+
<p>A tiny slice of state, exactly like React's <code>useState</code>.</p>
|
|
16
|
+
<div class="count-display">{count}</div>
|
|
17
|
+
<div class="counter-actions">
|
|
18
|
+
<button class="btn btn-danger" onClick={() => setCount(c => c - 1)}>−</button>
|
|
19
|
+
<button class="btn btn-primary" onClick={() => setCount(c => c + 1)}>+</button>
|
|
20
|
+
<button class="btn btn-grey" onClick={() => setCount(0)}>Reset</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h2>Add a page</h2>
|
|
26
|
+
<p>
|
|
27
|
+
Each <code>page.js</code> inside <code>app/</code> is a route.
|
|
28
|
+
Create <code>app/about/page.js</code> and it becomes <code>/about</code>.
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|