create-aziz-app 2.0.14 → 3.0.0
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 +227 -0
- package/bin/create-aziz-app.js +9 -9
- package/package.json +10 -11
- package/template/README.md +862 -138
- package/template/_framework/boot.js +36 -0
- package/template/_framework/hooks.js +207 -0
- package/template/{modules → _framework}/next-response.js +5 -13
- package/template/_framework/router.js +279 -0
- package/template/_framework/runtime.js +86 -0
- package/template/api/createUser.js +1 -1
- package/template/api/hello.js +8 -17
- package/template/api/loginUser.js +1 -1
- package/template/api/users.js +38 -134
- package/template/app/about/page.js +45 -0
- package/template/app/layout.js +23 -0
- package/template/app/page.js +54 -0
- package/template/app/test/page.js +55 -0
- package/template/components/Footer.js +10 -0
- package/template/components/Navbar.js +30 -0
- package/template/index.html +24 -0
- package/template/package.json +5 -14
- package/template/scripts/dev-server.js +330 -302
- package/template/scripts/live-reload-client.js +0 -1
- package/template/.vscode/settings.json +0 -15
- package/template/api/getUser.js +0 -60
- package/template/api/getUserWithDB.js +0 -65
- package/template/app.config.js +0 -41
- package/template/app.html +0 -55
- package/template/components/Counter.jsx +0 -36
- package/template/components/Footer.jsx +0 -9
- package/template/components/Navbar.jsx +0 -17
- package/template/docs/API-GUIDE.md +0 -196
- package/template/docs/API-ROUTES.md +0 -538
- package/template/docs/AUTO-IMPORT.md +0 -99
- package/template/docs/AUTO-REGISTRATION.md +0 -248
- package/template/docs/CSS-FRAMEWORKS.md +0 -221
- package/template/docs/DEV-SERVER.md +0 -184
- package/template/docs/GETTING-STARTED.md +0 -205
- package/template/docs/HOW-IT-WORKS.md +0 -362
- package/template/docs/NEXTJS-APP-ROUTER.md +0 -536
- package/template/docs/NEXTJS-COMPARISON.md +0 -473
- package/template/docs/QUICK-START.md +0 -111
- package/template/docs/STYLING.md +0 -269
- package/template/docs/TROUBLESHOOTING.md +0 -282
- package/template/modules/README.md +0 -129
- package/template/modules/api.js +0 -44
- package/template/modules/auto-import.js +0 -130
- package/template/modules/router.js +0 -218
- package/template/modules/state.js +0 -248
- package/template/package-lock.json +0 -13
- package/template/pages/About.jsx +0 -48
- package/template/pages/ApiDemo.jsx +0 -209
- package/template/pages/Contact.jsx +0 -25
- package/template/pages/Example.jsx +0 -59
- package/template/pages/Home.jsx +0 -39
- package/template/pages/HooksDemo.jsx +0 -194
- package/template/pages/test.jsx +0 -72
- package/template/scripts/build.js +0 -81
- package/template/scripts/generate-imports.js +0 -88
- package/template/tailwind.config.js +0 -17
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// ============================================================
|
|
4
|
-
// AUTO-IMPORT SYSTEM
|
|
5
|
-
// Automatically discovers and loads all components, pages, and API routes
|
|
6
|
-
// ============================================================
|
|
7
|
-
|
|
8
|
-
// Track loaded scripts
|
|
9
|
-
const loadedScripts = new Set();
|
|
10
|
-
let scriptsToLoad = 0;
|
|
11
|
-
let scriptsLoaded = 0;
|
|
12
|
-
|
|
13
|
-
// Get configuration from app.config.js
|
|
14
|
-
function getConfig() {
|
|
15
|
-
return window.APP_CONFIG || {
|
|
16
|
-
apiRoutes: [],
|
|
17
|
-
components: [],
|
|
18
|
-
pages: []
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Load a script dynamically
|
|
23
|
-
function loadScript(src) {
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
if (loadedScripts.has(src)) {
|
|
26
|
-
resolve();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const script = document.createElement('script');
|
|
31
|
-
script.type = 'text/babel';
|
|
32
|
-
script.src = src;
|
|
33
|
-
|
|
34
|
-
script.onload = () => {
|
|
35
|
-
loadedScripts.add(src);
|
|
36
|
-
scriptsLoaded++;
|
|
37
|
-
console.log(`✓ Loaded: ${src} (${scriptsLoaded}/${scriptsToLoad})`);
|
|
38
|
-
resolve();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
script.onerror = () => {
|
|
42
|
-
console.error(`✗ Failed to load: ${src}`);
|
|
43
|
-
scriptsLoaded++;
|
|
44
|
-
reject(new Error(`Failed to load ${src}`));
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
document.body.appendChild(script);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Load all scripts in order
|
|
52
|
-
async function autoLoadScripts() {
|
|
53
|
-
console.log('🚀 Auto-import starting...');
|
|
54
|
-
|
|
55
|
-
const config = getConfig();
|
|
56
|
-
|
|
57
|
-
// Calculate total scripts to load
|
|
58
|
-
scriptsToLoad =
|
|
59
|
-
config.apiRoutes.length +
|
|
60
|
-
config.components.length +
|
|
61
|
-
config.pages.length;
|
|
62
|
-
|
|
63
|
-
if (scriptsToLoad === 0) {
|
|
64
|
-
console.warn('⚠️ No files configured in app.config.js');
|
|
65
|
-
if (typeof startApp === 'function') {
|
|
66
|
-
startApp();
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
// Load API routes first
|
|
73
|
-
if (config.apiRoutes.length > 0) {
|
|
74
|
-
console.log('📡 Loading API routes...');
|
|
75
|
-
for (const route of config.apiRoutes) {
|
|
76
|
-
await loadScript(route);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Load components
|
|
81
|
-
if (config.components.length > 0) {
|
|
82
|
-
console.log('🧩 Loading components...');
|
|
83
|
-
for (const component of config.components) {
|
|
84
|
-
await loadScript(component);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Load pages
|
|
89
|
-
if (config.pages.length > 0) {
|
|
90
|
-
console.log('📄 Loading pages...');
|
|
91
|
-
for (const page of config.pages) {
|
|
92
|
-
await loadScript(page);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
console.log('✅ Auto-import complete!');
|
|
97
|
-
|
|
98
|
-
// Start the app after everything is loaded
|
|
99
|
-
if (typeof startApp === 'function') {
|
|
100
|
-
console.log('🎉 Starting app...');
|
|
101
|
-
startApp();
|
|
102
|
-
} else {
|
|
103
|
-
console.error('❌ startApp function not found!');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error('❌ Auto-import failed:', error);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Export to global scope
|
|
112
|
-
window.autoLoadScripts = autoLoadScripts;
|
|
113
|
-
|
|
114
|
-
// Start auto-loading when this script loads
|
|
115
|
-
function startAutoImport() {
|
|
116
|
-
// Small delay to ensure config is loaded
|
|
117
|
-
if (!window.APP_CONFIG) {
|
|
118
|
-
console.warn('⚠️ APP_CONFIG not found, retrying...');
|
|
119
|
-
setTimeout(startAutoImport, 50);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (document.readyState === 'loading') {
|
|
124
|
-
document.addEventListener('DOMContentLoaded', autoLoadScripts);
|
|
125
|
-
} else {
|
|
126
|
-
autoLoadScripts();
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
startAutoImport();
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// ============================================================
|
|
4
|
-
// h() — our createElement
|
|
5
|
-
// Babel compiles <div class="x"> → h("div", { class: "x" })
|
|
6
|
-
// This builds a real DOM element from that
|
|
7
|
-
// ============================================================
|
|
8
|
-
function h(tag, props, ...children) {
|
|
9
|
-
// If it's a component function, call it with its props
|
|
10
|
-
if (typeof tag === "function") {
|
|
11
|
-
return tag({ ...props, children });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const el = document.createElement(tag);
|
|
15
|
-
|
|
16
|
-
if (props) {
|
|
17
|
-
for (let key in props) {
|
|
18
|
-
if (key === "children") continue;
|
|
19
|
-
|
|
20
|
-
if (key.startsWith("on")) {
|
|
21
|
-
// onClick → "click" event listener
|
|
22
|
-
el.addEventListener(key.slice(2).toLowerCase(), props[key]);
|
|
23
|
-
} else if (key === "className") {
|
|
24
|
-
el.className = props[key];
|
|
25
|
-
} else if (key === "style" && typeof props[key] === "object") {
|
|
26
|
-
Object.assign(el.style, props[key]);
|
|
27
|
-
} else if (key === "value" && (tag === "input" || tag === "textarea")) {
|
|
28
|
-
// Set value property directly for inputs/textareas
|
|
29
|
-
el.value = props[key];
|
|
30
|
-
} else {
|
|
31
|
-
el.setAttribute(key, props[key]);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Flatten and append children
|
|
37
|
-
const flat = children.flat(Infinity);
|
|
38
|
-
for (let child of flat) {
|
|
39
|
-
if (child === null || child === undefined || child === false) continue;
|
|
40
|
-
if (typeof child === "string" || typeof child === "number") {
|
|
41
|
-
el.appendChild(document.createTextNode(child));
|
|
42
|
-
} else if (child instanceof Node) {
|
|
43
|
-
el.appendChild(child);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return el;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// ============================================================
|
|
52
|
-
// ROUTER
|
|
53
|
-
// File-based routing using the URL hash
|
|
54
|
-
// "#/" → Home page
|
|
55
|
-
// "#/about" → About page
|
|
56
|
-
// "#/contact" → Contact page
|
|
57
|
-
//
|
|
58
|
-
// To add a new page:
|
|
59
|
-
// 1. Create pages/YourPage.jsx with a function like AboutPage()
|
|
60
|
-
// 2. The page will automatically be registered!
|
|
61
|
-
// 3. Optional: Call registerPage("about", AboutPage) for custom route names
|
|
62
|
-
// ============================================================
|
|
63
|
-
const routes = {};
|
|
64
|
-
|
|
65
|
-
// registerPage("about", AboutPage) — called by each page file
|
|
66
|
-
// Now optional! Pages auto-register based on function name
|
|
67
|
-
function registerPage(path, component) {
|
|
68
|
-
routes[path] = component;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Auto-register pages from global scope
|
|
72
|
-
// Looks for functions ending with "Page" (e.g., AboutPage, HomePage)
|
|
73
|
-
function autoRegisterPages() {
|
|
74
|
-
for (let key in window) {
|
|
75
|
-
if (key.endsWith('Page') && typeof window[key] === 'function') {
|
|
76
|
-
// Convert "AboutPage" → "about", "HomePage" → "home"
|
|
77
|
-
const routeName = key.replace('Page', '').toLowerCase();
|
|
78
|
-
|
|
79
|
-
// Only register if not already registered
|
|
80
|
-
if (!routes[routeName]) {
|
|
81
|
-
routes[routeName] = window[key];
|
|
82
|
-
console.log(`📄 Auto-registered: ${routeName} → ${key}()`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function getCurrentPath() {
|
|
89
|
-
// Remove the "#/" prefix → "about", "contact", or "" for home
|
|
90
|
-
return window.location.hash.replace("#/", "") || "home";
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function navigate(path) {
|
|
94
|
-
window.location.hash = "/" + path;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// ============================================================
|
|
99
|
-
// STATE MANAGEMENT
|
|
100
|
-
// Simple global state — update it and the page re-renders
|
|
101
|
-
// ============================================================
|
|
102
|
-
let state = {
|
|
103
|
-
count: 0
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
function setState(newState) {
|
|
107
|
-
// Merge new values into state
|
|
108
|
-
state = { ...state, ...newState };
|
|
109
|
-
renderPage(); // re-render when state changes
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// ============================================================
|
|
114
|
-
// RENDER ENGINE
|
|
115
|
-
// ============================================================
|
|
116
|
-
function renderPage() {
|
|
117
|
-
// Reset hooks before rendering
|
|
118
|
-
if (typeof resetHooks === 'function') {
|
|
119
|
-
resetHooks();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const path = getCurrentPath();
|
|
123
|
-
const root = document.getElementById("root");
|
|
124
|
-
|
|
125
|
-
// Save focus state before re-render
|
|
126
|
-
const activeElement = document.activeElement;
|
|
127
|
-
const shouldRestoreFocus = activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA');
|
|
128
|
-
const focusInfo = shouldRestoreFocus ? {
|
|
129
|
-
type: activeElement.type,
|
|
130
|
-
placeholder: activeElement.placeholder,
|
|
131
|
-
value: activeElement.value,
|
|
132
|
-
selectionStart: activeElement.selectionStart,
|
|
133
|
-
selectionEnd: activeElement.selectionEnd
|
|
134
|
-
} : null;
|
|
135
|
-
|
|
136
|
-
root.innerHTML = "";
|
|
137
|
-
|
|
138
|
-
const PageComponent = routes[path] || NotFoundPage;
|
|
139
|
-
root.appendChild(PageComponent());
|
|
140
|
-
|
|
141
|
-
// Restore focus after re-render (use setTimeout to ensure DOM is ready)
|
|
142
|
-
if (focusInfo) {
|
|
143
|
-
setTimeout(() => {
|
|
144
|
-
const inputs = root.querySelectorAll('input, textarea');
|
|
145
|
-
for (let input of inputs) {
|
|
146
|
-
// Match by type and placeholder for unique identification
|
|
147
|
-
if (input.type === focusInfo.type &&
|
|
148
|
-
input.placeholder === focusInfo.placeholder) {
|
|
149
|
-
input.focus();
|
|
150
|
-
// Restore cursor position
|
|
151
|
-
if (focusInfo.selectionStart !== null && focusInfo.selectionEnd !== null) {
|
|
152
|
-
input.setSelectionRange(focusInfo.selectionStart, focusInfo.selectionEnd);
|
|
153
|
-
}
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}, 0);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function renderShell() {
|
|
162
|
-
// Render navbar and footer (they don't change between pages)
|
|
163
|
-
const navbar = document.getElementById("navbar");
|
|
164
|
-
const footer = document.getElementById("footer");
|
|
165
|
-
navbar.innerHTML = "";
|
|
166
|
-
footer.innerHTML = "";
|
|
167
|
-
navbar.appendChild(Navbar());
|
|
168
|
-
footer.appendChild(Footer());
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Re-render shell when route changes (for navbar active state)
|
|
172
|
-
function updateShell() {
|
|
173
|
-
const navbar = document.getElementById("navbar");
|
|
174
|
-
navbar.innerHTML = "";
|
|
175
|
-
navbar.appendChild(Navbar());
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 404 page — shown when no route matches
|
|
179
|
-
function NotFoundPage() {
|
|
180
|
-
return (
|
|
181
|
-
<div class="not-found">
|
|
182
|
-
<h1>404</h1>
|
|
183
|
-
<p>Oops! The page you're looking for doesn't exist.</p>
|
|
184
|
-
<button class="btn btn-primary" onClick={() => navigate("home")}>
|
|
185
|
-
Go Home
|
|
186
|
-
</button>
|
|
187
|
-
</div>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// ============================================================
|
|
193
|
-
// START
|
|
194
|
-
// Called once after all scripts are loaded
|
|
195
|
-
// ============================================================
|
|
196
|
-
function startApp() {
|
|
197
|
-
// Auto-register any pages that weren't manually registered
|
|
198
|
-
autoRegisterPages();
|
|
199
|
-
|
|
200
|
-
renderShell();
|
|
201
|
-
renderPage();
|
|
202
|
-
|
|
203
|
-
// Re-render the page and navbar whenever the hash changes (back/forward navigation)
|
|
204
|
-
window.addEventListener("hashchange", () => {
|
|
205
|
-
updateShell(); // Update navbar active state
|
|
206
|
-
renderPage();
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Export to global scope
|
|
211
|
-
window.h = h;
|
|
212
|
-
window.registerPage = registerPage;
|
|
213
|
-
window.getCurrentPath = getCurrentPath;
|
|
214
|
-
window.navigate = navigate;
|
|
215
|
-
window.state = state;
|
|
216
|
-
window.setState = setState;
|
|
217
|
-
window.renderPage = renderPage;
|
|
218
|
-
window.startApp = startApp;
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// ============================================================
|
|
4
|
-
// STATE MANAGEMENT
|
|
5
|
-
// React-like hooks system (useState, useEffect, etc.)
|
|
6
|
-
// Exactly like React/Next.js!
|
|
7
|
-
// ============================================================
|
|
8
|
-
|
|
9
|
-
let currentComponent = null;
|
|
10
|
-
let currentHookIndex = 0;
|
|
11
|
-
const componentStates = new Map();
|
|
12
|
-
|
|
13
|
-
// Get or create state for current component
|
|
14
|
-
function getComponentState() {
|
|
15
|
-
if (!currentComponent) {
|
|
16
|
-
currentComponent = { hooks: [] };
|
|
17
|
-
}
|
|
18
|
-
return currentComponent;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// useState - Exactly like React!
|
|
22
|
-
function useState(initialValue) {
|
|
23
|
-
const component = getComponentState();
|
|
24
|
-
const hookIndex = currentHookIndex++;
|
|
25
|
-
|
|
26
|
-
// Initialize state if first render
|
|
27
|
-
if (component.hooks[hookIndex] === undefined) {
|
|
28
|
-
component.hooks[hookIndex] = {
|
|
29
|
-
value: typeof initialValue === 'function' ? initialValue() : initialValue
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const hook = component.hooks[hookIndex];
|
|
34
|
-
|
|
35
|
-
const setState = (newValue) => {
|
|
36
|
-
const nextValue = typeof newValue === 'function'
|
|
37
|
-
? newValue(hook.value)
|
|
38
|
-
: newValue;
|
|
39
|
-
|
|
40
|
-
if (hook.value !== nextValue) {
|
|
41
|
-
hook.value = nextValue;
|
|
42
|
-
// Trigger re-render
|
|
43
|
-
if (typeof renderPage === 'function') {
|
|
44
|
-
renderPage();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return [hook.value, setState];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// useEffect - Exactly like React!
|
|
53
|
-
function useEffect(callback, dependencies) {
|
|
54
|
-
const component = getComponentState();
|
|
55
|
-
const hookIndex = currentHookIndex++;
|
|
56
|
-
|
|
57
|
-
const oldDeps = component.hooks[hookIndex]?.deps;
|
|
58
|
-
|
|
59
|
-
let hasChanged = true;
|
|
60
|
-
if (oldDeps) {
|
|
61
|
-
hasChanged = dependencies.some((dep, i) => dep !== oldDeps[i]);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (hasChanged) {
|
|
65
|
-
// Clean up previous effect
|
|
66
|
-
if (component.hooks[hookIndex]?.cleanup) {
|
|
67
|
-
component.hooks[hookIndex].cleanup();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Run effect after render
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
const cleanup = callback();
|
|
73
|
-
if (component.hooks[hookIndex]) {
|
|
74
|
-
component.hooks[hookIndex].cleanup = cleanup;
|
|
75
|
-
}
|
|
76
|
-
}, 0);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
component.hooks[hookIndex] = {
|
|
80
|
-
deps: dependencies,
|
|
81
|
-
cleanup: component.hooks[hookIndex]?.cleanup
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// useMemo - Exactly like React!
|
|
86
|
-
function useMemo(factory, dependencies) {
|
|
87
|
-
const component = getComponentState();
|
|
88
|
-
const hookIndex = currentHookIndex++;
|
|
89
|
-
|
|
90
|
-
const oldDeps = component.hooks[hookIndex]?.deps;
|
|
91
|
-
|
|
92
|
-
let hasChanged = true;
|
|
93
|
-
if (oldDeps) {
|
|
94
|
-
hasChanged = dependencies.some((dep, i) => dep !== oldDeps[i]);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (hasChanged || !component.hooks[hookIndex]) {
|
|
98
|
-
const value = factory();
|
|
99
|
-
component.hooks[hookIndex] = {
|
|
100
|
-
value,
|
|
101
|
-
deps: dependencies
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return component.hooks[hookIndex].value;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// useCallback - Exactly like React!
|
|
109
|
-
function useCallback(callback, dependencies) {
|
|
110
|
-
return useMemo(() => callback, dependencies);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// useRef - Exactly like React!
|
|
114
|
-
function useRef(initialValue) {
|
|
115
|
-
const component = getComponentState();
|
|
116
|
-
const hookIndex = currentHookIndex++;
|
|
117
|
-
|
|
118
|
-
if (!component.hooks[hookIndex]) {
|
|
119
|
-
component.hooks[hookIndex] = {
|
|
120
|
-
current: initialValue
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return component.hooks[hookIndex];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// useReducer - Exactly like React!
|
|
128
|
-
function useReducer(reducer, initialState, init) {
|
|
129
|
-
const [state, setState] = useState(
|
|
130
|
-
init ? init(initialState) : initialState
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const dispatch = (action) => {
|
|
134
|
-
setState(prevState => reducer(prevState, action));
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return [state, dispatch];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Custom hook: useLocalStorage
|
|
141
|
-
function useLocalStorage(key, initialValue) {
|
|
142
|
-
const [storedValue, setStoredValue] = useState(() => {
|
|
143
|
-
try {
|
|
144
|
-
const item = window.localStorage.getItem(key);
|
|
145
|
-
return item ? JSON.parse(item) : initialValue;
|
|
146
|
-
} catch (error) {
|
|
147
|
-
console.error(error);
|
|
148
|
-
return initialValue;
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const setValue = (value) => {
|
|
153
|
-
try {
|
|
154
|
-
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
155
|
-
setStoredValue(valueToStore);
|
|
156
|
-
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error(error);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
return [storedValue, setValue];
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Custom hook: useFetch
|
|
166
|
-
function useFetch(url, options = {}) {
|
|
167
|
-
const [data, setData] = useState(null);
|
|
168
|
-
const [loading, setLoading] = useState(true);
|
|
169
|
-
const [error, setError] = useState(null);
|
|
170
|
-
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
let cancelled = false;
|
|
173
|
-
|
|
174
|
-
const fetchData = async () => {
|
|
175
|
-
try {
|
|
176
|
-
setLoading(true);
|
|
177
|
-
const response = await fetch(url, options);
|
|
178
|
-
|
|
179
|
-
if (!response.ok) {
|
|
180
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const json = await response.json();
|
|
184
|
-
|
|
185
|
-
if (!cancelled) {
|
|
186
|
-
setData(json);
|
|
187
|
-
setError(null);
|
|
188
|
-
}
|
|
189
|
-
} catch (e) {
|
|
190
|
-
if (!cancelled) {
|
|
191
|
-
setError(e.message);
|
|
192
|
-
setData(null);
|
|
193
|
-
}
|
|
194
|
-
} finally {
|
|
195
|
-
if (!cancelled) {
|
|
196
|
-
setLoading(false);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
fetchData();
|
|
202
|
-
|
|
203
|
-
return () => {
|
|
204
|
-
cancelled = true;
|
|
205
|
-
};
|
|
206
|
-
}, [url, JSON.stringify(options)]);
|
|
207
|
-
|
|
208
|
-
return { data, loading, error };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Reset hooks before each render
|
|
212
|
-
function resetHooks() {
|
|
213
|
-
currentHookIndex = 0;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Initialize component state
|
|
217
|
-
function initComponent() {
|
|
218
|
-
currentComponent = { hooks: [] };
|
|
219
|
-
currentHookIndex = 0;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Export to global scope (like React)
|
|
223
|
-
window.useState = useState;
|
|
224
|
-
window.useEffect = useEffect;
|
|
225
|
-
window.useMemo = useMemo;
|
|
226
|
-
window.useCallback = useCallback;
|
|
227
|
-
window.useRef = useRef;
|
|
228
|
-
window.useReducer = useReducer;
|
|
229
|
-
window.useLocalStorage = useLocalStorage;
|
|
230
|
-
window.useFetch = useFetch;
|
|
231
|
-
window.resetHooks = resetHooks;
|
|
232
|
-
window.initComponent = initComponent;
|
|
233
|
-
|
|
234
|
-
// Also make available without window prefix
|
|
235
|
-
if (typeof globalThis !== 'undefined') {
|
|
236
|
-
globalThis.useState = useState;
|
|
237
|
-
globalThis.useEffect = useEffect;
|
|
238
|
-
globalThis.useMemo = useMemo;
|
|
239
|
-
globalThis.useCallback = useCallback;
|
|
240
|
-
globalThis.useRef = useRef;
|
|
241
|
-
globalThis.useReducer = useReducer;
|
|
242
|
-
globalThis.useLocalStorage = useLocalStorage;
|
|
243
|
-
globalThis.useFetch = useFetch;
|
|
244
|
-
globalThis.resetHooks = resetHooks;
|
|
245
|
-
globalThis.initComponent = initComponent;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
console.log('✅ React-like hooks loaded: useState, useEffect, useMemo, useCallback, useRef, useReducer');
|
package/template/pages/About.jsx
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/** @jsx h */
|
|
2
|
-
|
|
3
|
-
// Optional: You can still manually register if you want a custom route name
|
|
4
|
-
// registerPage("about", AboutPage);
|
|
5
|
-
|
|
6
|
-
// Or just define the function and it auto-registers as "about"
|
|
7
|
-
function AboutPage() {
|
|
8
|
-
return (
|
|
9
|
-
<div>
|
|
10
|
-
<h1>About 📖</h1>
|
|
11
|
-
|
|
12
|
-
<div class="card">
|
|
13
|
-
<h2>What is this?</h2>
|
|
14
|
-
<p>
|
|
15
|
-
This app is built with <strong>create-aziz-app</strong> — a lightweight
|
|
16
|
-
vanilla JS framework with JSX support and file-based routing.
|
|
17
|
-
No React, no Vite, no dependencies. Just plain JavaScript + Babel.
|
|
18
|
-
</p>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
<div class="card">
|
|
22
|
-
<h2>How routing works</h2>
|
|
23
|
-
<p>
|
|
24
|
-
The URL hash controls which page is shown.
|
|
25
|
-
Changing <strong>#/about</strong> to <strong>#/contact</strong>
|
|
26
|
-
loads the Contact page — no page refresh needed.
|
|
27
|
-
</p>
|
|
28
|
-
<button class="btn btn-primary" onClick={() => navigate("contact")}>
|
|
29
|
-
Go to Contact →
|
|
30
|
-
</button>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<div class="card">
|
|
34
|
-
<h2>✨ Auto-Registration</h2>
|
|
35
|
-
<p>
|
|
36
|
-
This page works without calling <code>registerPage()</code>!
|
|
37
|
-
Just name your function <code>YourNamePage()</code> and it automatically
|
|
38
|
-
becomes a route.
|
|
39
|
-
</p>
|
|
40
|
-
<ul style={{ lineHeight: '1.8', marginLeft: '20px' }}>
|
|
41
|
-
<li><code>HomePage()</code> → <code>#/home</code></li>
|
|
42
|
-
<li><code>AboutPage()</code> → <code>#/about</code></li>
|
|
43
|
-
<li><code>ContactPage()</code> → <code>#/contact</code></li>
|
|
44
|
-
</ul>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
);
|
|
48
|
-
}
|