create-aziz-app 1.0.3 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-aziz-app.js +39 -20
- package/package.json +3 -1
- package/template/README.md +189 -0
- package/template/api/hello.js +14 -0
- package/template/api/users.js +62 -0
- package/template/app.config.js +41 -0
- package/template/app.html +127 -24
- package/template/components/Counter.jsx +30 -12
- package/template/components/Footer.jsx +3 -3
- package/template/components/Navbar.jsx +16 -0
- package/template/docs/API-GUIDE.md +196 -0
- package/template/docs/AUTO-IMPORT.md +99 -0
- package/template/docs/GETTING-STARTED.md +205 -0
- package/template/docs/QUICK-START.md +111 -0
- package/template/modules/README.md +129 -0
- package/template/modules/api.js +148 -0
- package/template/modules/auto-import.js +130 -0
- package/template/modules/router.js +185 -0
- package/template/modules/state.js +54 -0
- package/template/package.json +18 -0
- package/template/pages/About.jsx +33 -0
- package/template/pages/ApiDemo.jsx +209 -0
- package/template/pages/Contact.jsx +27 -0
- package/template/pages/Home.jsx +39 -0
- package/template/pages/NewPage.jsx +39 -0
- package/template/pages/Test.jsx +74 -0
- package/template/scripts/build.js +81 -0
- package/template/scripts/dev-server.js +172 -0
- package/template/scripts/generate-imports.js +98 -0
- package/template/app.js +0 -83
- package/template/components/Header.jsx +0 -10
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Modules Folder
|
|
2
|
+
|
|
3
|
+
This folder contains the core system modules that power your application.
|
|
4
|
+
|
|
5
|
+
## Core Modules
|
|
6
|
+
|
|
7
|
+
### 📦 router.js
|
|
8
|
+
The main application engine containing:
|
|
9
|
+
- **`h()` function** - JSX createElement (converts JSX to DOM elements)
|
|
10
|
+
- **Router system** - File-based routing with hash navigation
|
|
11
|
+
- **`registerPage()`** - Register page components
|
|
12
|
+
- **`navigate()`** - Navigate between pages
|
|
13
|
+
- **`renderPage()`** - Render engine with focus preservation
|
|
14
|
+
- **Global state** - Basic global state management
|
|
15
|
+
|
|
16
|
+
### 🔄 state.js
|
|
17
|
+
State management system:
|
|
18
|
+
- **`useState(initialValue)`** - React-like hooks for local component state
|
|
19
|
+
- **Hook system** - Manages state across re-renders
|
|
20
|
+
- **`resetHooks()`** - Resets hook index before each render
|
|
21
|
+
|
|
22
|
+
### 🌐 api.js
|
|
23
|
+
API route system (Next.js-like):
|
|
24
|
+
- **`registerApiRoute(path, handler)`** - Register API endpoints
|
|
25
|
+
- **`fetchApi(path, options)`** - Fetch-like API for making requests
|
|
26
|
+
- **Request/Response objects** - Express-like req/res handling
|
|
27
|
+
- **Mock API system** - Client-side API simulation
|
|
28
|
+
|
|
29
|
+
## How It Works
|
|
30
|
+
|
|
31
|
+
### 1. Router System
|
|
32
|
+
```javascript
|
|
33
|
+
// Register a page
|
|
34
|
+
registerPage("home", HomePage);
|
|
35
|
+
|
|
36
|
+
// Navigate programmatically
|
|
37
|
+
navigate("about");
|
|
38
|
+
|
|
39
|
+
// Get current path
|
|
40
|
+
const path = getCurrentPath(); // returns "home", "about", etc.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. State Management
|
|
44
|
+
```javascript
|
|
45
|
+
// Use state in components
|
|
46
|
+
const [count, setCount] = useState(0);
|
|
47
|
+
const [name, setName] = useState('');
|
|
48
|
+
|
|
49
|
+
// Update state
|
|
50
|
+
setCount(count + 1);
|
|
51
|
+
setName('New Name');
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. API Routes
|
|
55
|
+
```javascript
|
|
56
|
+
// Register an API route
|
|
57
|
+
registerApiRoute('myroute', async (req, res) => {
|
|
58
|
+
return res.status(200).json({ data: 'Hello!' });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Call from components
|
|
62
|
+
const response = await fetchApi('/api/myroute');
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Module Loading Order
|
|
67
|
+
|
|
68
|
+
The modules must be loaded in this order (see `app.html`):
|
|
69
|
+
|
|
70
|
+
1. **router.js** - Core engine and routing
|
|
71
|
+
2. **state.js** - State management
|
|
72
|
+
3. **api.js** - API system
|
|
73
|
+
4. **API routes** - Your API endpoints
|
|
74
|
+
5. **Components** - Reusable UI components
|
|
75
|
+
6. **Pages** - Page components
|
|
76
|
+
7. **Start app** - Call `startApp()`
|
|
77
|
+
|
|
78
|
+
## Extending the System
|
|
79
|
+
|
|
80
|
+
### Add New Core Features
|
|
81
|
+
|
|
82
|
+
Create new modules in this folder for additional core functionality:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// modules/auth.js
|
|
86
|
+
function login(username, password) {
|
|
87
|
+
// Authentication logic
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function logout() {
|
|
91
|
+
// Logout logic
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
window.auth = { login, logout };
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then load it in `app.html`:
|
|
98
|
+
```html
|
|
99
|
+
<script type="text/babel" src="modules/auth.js"></script>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Module Best Practices
|
|
103
|
+
|
|
104
|
+
1. **Export to global scope** - Use `window.functionName` or `globalThis.functionName`
|
|
105
|
+
2. **Keep modules focused** - Each module should have a single responsibility
|
|
106
|
+
3. **Document your modules** - Add comments explaining what each function does
|
|
107
|
+
4. **Load order matters** - Ensure dependencies are loaded first
|
|
108
|
+
|
|
109
|
+
## File Structure
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
template/
|
|
113
|
+
├── modules/ # Core system modules (you are here)
|
|
114
|
+
│ ├── router.js # Routing and rendering engine
|
|
115
|
+
│ ├── state.js # State management
|
|
116
|
+
│ ├── api.js # API route system
|
|
117
|
+
│ └── README.md # This file
|
|
118
|
+
├── api/ # API route handlers
|
|
119
|
+
├── components/ # Reusable UI components
|
|
120
|
+
├── pages/ # Page components
|
|
121
|
+
└── app.html # Main HTML file
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Notes
|
|
125
|
+
|
|
126
|
+
- These modules are loaded via Babel standalone for JSX support
|
|
127
|
+
- All modules use the `/** @jsx h */` pragma to use the custom `h()` function
|
|
128
|
+
- The system is designed to be simple and easy to understand
|
|
129
|
+
- You can replace any module with your own implementation
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// API ROUTE SYSTEM
|
|
5
|
+
// Next.js-like API routes for client-side
|
|
6
|
+
// Can be easily replaced with real backend calls
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
// API routes registry
|
|
10
|
+
const apiRoutes = {};
|
|
11
|
+
|
|
12
|
+
// Register an API route
|
|
13
|
+
function registerApiRoute(path, handler) {
|
|
14
|
+
apiRoutes[path] = handler;
|
|
15
|
+
console.log(`Registered API route: /api/${path}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// API request/response helpers
|
|
19
|
+
class ApiRequest {
|
|
20
|
+
constructor(method, body, query, params) {
|
|
21
|
+
this.method = method;
|
|
22
|
+
this.body = body;
|
|
23
|
+
this.query = query;
|
|
24
|
+
this.params = params;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class ApiResponse {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.statusCode = 200;
|
|
31
|
+
this.headers = {};
|
|
32
|
+
this.data = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
status(code) {
|
|
36
|
+
this.statusCode = code;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
json(data) {
|
|
41
|
+
this.data = data;
|
|
42
|
+
this.headers['Content-Type'] = 'application/json';
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
send(data) {
|
|
47
|
+
this.data = data;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Execute an API route
|
|
53
|
+
async function callApi(path, options = {}) {
|
|
54
|
+
const {
|
|
55
|
+
method = 'GET',
|
|
56
|
+
body = null,
|
|
57
|
+
query = {},
|
|
58
|
+
params = {}
|
|
59
|
+
} = options;
|
|
60
|
+
|
|
61
|
+
// Find the route handler
|
|
62
|
+
const handler = apiRoutes[path];
|
|
63
|
+
|
|
64
|
+
if (!handler) {
|
|
65
|
+
return {
|
|
66
|
+
status: 404,
|
|
67
|
+
data: { error: 'API route not found' }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Create request and response objects
|
|
72
|
+
const req = new ApiRequest(method, body, query, params);
|
|
73
|
+
const res = new ApiResponse();
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Call the handler
|
|
77
|
+
await handler(req, res);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
status: res.statusCode,
|
|
81
|
+
data: res.data,
|
|
82
|
+
headers: res.headers
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error(`API Error on ${path}:`, error);
|
|
86
|
+
return {
|
|
87
|
+
status: 500,
|
|
88
|
+
data: { error: error.message }
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Fetch-like API for making requests
|
|
94
|
+
async function fetchApi(path, options = {}) {
|
|
95
|
+
const {
|
|
96
|
+
method = 'GET',
|
|
97
|
+
body = null,
|
|
98
|
+
headers = {}
|
|
99
|
+
} = options;
|
|
100
|
+
|
|
101
|
+
// Parse query params from path
|
|
102
|
+
const [routePath, queryString] = path.split('?');
|
|
103
|
+
const query = {};
|
|
104
|
+
if (queryString) {
|
|
105
|
+
queryString.split('&').forEach(param => {
|
|
106
|
+
const [key, value] = param.split('=');
|
|
107
|
+
query[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Parse body if it's JSON
|
|
112
|
+
let parsedBody = body;
|
|
113
|
+
if (body && typeof body === 'string') {
|
|
114
|
+
try {
|
|
115
|
+
parsedBody = JSON.parse(body);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
parsedBody = body;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const result = await callApi(routePath.replace('/api/', ''), {
|
|
122
|
+
method,
|
|
123
|
+
body: parsedBody,
|
|
124
|
+
query
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Return a fetch-like response
|
|
128
|
+
return {
|
|
129
|
+
ok: result.status >= 200 && result.status < 300,
|
|
130
|
+
status: result.status,
|
|
131
|
+
statusText: result.status === 200 ? 'OK' : 'Error',
|
|
132
|
+
headers: result.headers,
|
|
133
|
+
json: async () => result.data,
|
|
134
|
+
text: async () => JSON.stringify(result.data)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Export to global scope
|
|
139
|
+
window.registerApiRoute = registerApiRoute;
|
|
140
|
+
window.fetchApi = fetchApi;
|
|
141
|
+
window.callApi = callApi;
|
|
142
|
+
|
|
143
|
+
// Also make available without window prefix
|
|
144
|
+
if (typeof globalThis !== 'undefined') {
|
|
145
|
+
globalThis.registerApiRoute = registerApiRoute;
|
|
146
|
+
globalThis.fetchApi = fetchApi;
|
|
147
|
+
globalThis.callApi = callApi;
|
|
148
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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();
|
|
@@ -0,0 +1,185 @@
|
|
|
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
|
|
60
|
+
// 2. Load it in app.html with <script type="text/babel">
|
|
61
|
+
// 3. Register it below in the `routes` object
|
|
62
|
+
// ============================================================
|
|
63
|
+
const routes = {};
|
|
64
|
+
|
|
65
|
+
// registerPage("about", AboutPage) — called by each page file
|
|
66
|
+
function registerPage(path, component) {
|
|
67
|
+
routes[path] = component;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getCurrentPath() {
|
|
71
|
+
// Remove the "#/" prefix → "about", "contact", or "" for home
|
|
72
|
+
return window.location.hash.replace("#/", "") || "home";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function navigate(path) {
|
|
76
|
+
window.location.hash = "/" + path;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// ============================================================
|
|
81
|
+
// STATE MANAGEMENT
|
|
82
|
+
// Simple global state — update it and the page re-renders
|
|
83
|
+
// ============================================================
|
|
84
|
+
let state = {
|
|
85
|
+
count: 0
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function setState(newState) {
|
|
89
|
+
// Merge new values into state
|
|
90
|
+
state = { ...state, ...newState };
|
|
91
|
+
renderPage(); // re-render when state changes
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// ============================================================
|
|
96
|
+
// RENDER ENGINE
|
|
97
|
+
// ============================================================
|
|
98
|
+
function renderPage() {
|
|
99
|
+
// Reset hooks before rendering
|
|
100
|
+
if (typeof resetHooks === 'function') {
|
|
101
|
+
resetHooks();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const path = getCurrentPath();
|
|
105
|
+
const root = document.getElementById("root");
|
|
106
|
+
|
|
107
|
+
// Save focus state before re-render
|
|
108
|
+
const activeElement = document.activeElement;
|
|
109
|
+
const shouldRestoreFocus = activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA');
|
|
110
|
+
const focusInfo = shouldRestoreFocus ? {
|
|
111
|
+
type: activeElement.type,
|
|
112
|
+
placeholder: activeElement.placeholder,
|
|
113
|
+
value: activeElement.value,
|
|
114
|
+
selectionStart: activeElement.selectionStart,
|
|
115
|
+
selectionEnd: activeElement.selectionEnd
|
|
116
|
+
} : null;
|
|
117
|
+
|
|
118
|
+
root.innerHTML = "";
|
|
119
|
+
|
|
120
|
+
const PageComponent = routes[path] || NotFoundPage;
|
|
121
|
+
root.appendChild(PageComponent());
|
|
122
|
+
|
|
123
|
+
// Restore focus after re-render (use setTimeout to ensure DOM is ready)
|
|
124
|
+
if (focusInfo) {
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
const inputs = root.querySelectorAll('input, textarea');
|
|
127
|
+
for (let input of inputs) {
|
|
128
|
+
// Match by type and placeholder for unique identification
|
|
129
|
+
if (input.type === focusInfo.type &&
|
|
130
|
+
input.placeholder === focusInfo.placeholder) {
|
|
131
|
+
input.focus();
|
|
132
|
+
// Restore cursor position
|
|
133
|
+
if (focusInfo.selectionStart !== null && focusInfo.selectionEnd !== null) {
|
|
134
|
+
input.setSelectionRange(focusInfo.selectionStart, focusInfo.selectionEnd);
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}, 0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function renderShell() {
|
|
144
|
+
// Render navbar and footer (they don't change between pages)
|
|
145
|
+
const navbar = document.getElementById("navbar");
|
|
146
|
+
const footer = document.getElementById("footer");
|
|
147
|
+
navbar.innerHTML = "";
|
|
148
|
+
footer.innerHTML = "";
|
|
149
|
+
navbar.appendChild(Navbar());
|
|
150
|
+
footer.appendChild(Footer());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 404 page — shown when no route matches
|
|
154
|
+
function NotFoundPage() {
|
|
155
|
+
return (
|
|
156
|
+
<div class="not-found">
|
|
157
|
+
<h1>404</h1>
|
|
158
|
+
<p>Page not found.</p>
|
|
159
|
+
<button class="btn btn-primary" onClick={() => navigate("home")}>Go Home</button>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
// ============================================================
|
|
166
|
+
// START
|
|
167
|
+
// Called once after all scripts are loaded
|
|
168
|
+
// ============================================================
|
|
169
|
+
function startApp() {
|
|
170
|
+
renderShell();
|
|
171
|
+
renderPage();
|
|
172
|
+
|
|
173
|
+
// Re-render the page whenever the hash changes (back/forward navigation)
|
|
174
|
+
window.addEventListener("hashchange", renderPage);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Export to global scope
|
|
178
|
+
window.h = h;
|
|
179
|
+
window.registerPage = registerPage;
|
|
180
|
+
window.getCurrentPath = getCurrentPath;
|
|
181
|
+
window.navigate = navigate;
|
|
182
|
+
window.state = state;
|
|
183
|
+
window.setState = setState;
|
|
184
|
+
window.renderPage = renderPage;
|
|
185
|
+
window.startApp = startApp;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// STATE MANAGEMENT SYSTEM
|
|
5
|
+
// A simple, reactive state management system
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
// Component state storage - keyed by component instance
|
|
9
|
+
const componentStates = new Map();
|
|
10
|
+
let currentComponent = null;
|
|
11
|
+
let hookIndex = 0;
|
|
12
|
+
|
|
13
|
+
// Create a hook-like state for components
|
|
14
|
+
function useState(initialValue) {
|
|
15
|
+
// Create a unique key for this component and hook position
|
|
16
|
+
const componentKey = currentComponent || 'global';
|
|
17
|
+
const currentHookIndex = hookIndex++;
|
|
18
|
+
const stateKey = `${componentKey}_${currentHookIndex}`;
|
|
19
|
+
|
|
20
|
+
// Initialize if not exists
|
|
21
|
+
if (!componentStates.has(stateKey)) {
|
|
22
|
+
componentStates.set(stateKey, initialValue);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Get current value
|
|
26
|
+
const currentValue = componentStates.get(stateKey);
|
|
27
|
+
|
|
28
|
+
// Return getter and setter
|
|
29
|
+
const setValue = (newValue) => {
|
|
30
|
+
componentStates.set(stateKey, newValue);
|
|
31
|
+
// Reset hook index before re-render
|
|
32
|
+
hookIndex = 0;
|
|
33
|
+
// Trigger re-render
|
|
34
|
+
if (typeof renderPage === 'function') {
|
|
35
|
+
renderPage();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return [currentValue, setValue];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Reset hook index before each render
|
|
43
|
+
function resetHooks() {
|
|
44
|
+
hookIndex = 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Export to global scope - make it available like React
|
|
48
|
+
window.useState = useState;
|
|
49
|
+
window.resetHooks = resetHooks;
|
|
50
|
+
// Also make it available without window prefix
|
|
51
|
+
if (typeof globalThis !== 'undefined') {
|
|
52
|
+
globalThis.useState = useState;
|
|
53
|
+
globalThis.resetHooks = resetHooks;
|
|
54
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aziz-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A simple, clean React-like framework with state management, routing, and API routes",
|
|
5
|
+
"main": "app.html",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "node scripts/dev-server.js",
|
|
8
|
+
"build": "node scripts/generate-imports.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"react",
|
|
12
|
+
"framework",
|
|
13
|
+
"routing",
|
|
14
|
+
"state-management"
|
|
15
|
+
],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC"
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// Register this page at route "about" → "#/about"
|
|
4
|
+
registerPage("about", AboutPage);
|
|
5
|
+
|
|
6
|
+
function AboutPage() {
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
<h1>About 📖</h1>
|
|
10
|
+
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2>What is this?</h2>
|
|
13
|
+
<p>
|
|
14
|
+
This app is built with <strong>create-aziz-app</strong> — a lightweight
|
|
15
|
+
vanilla JS framework with JSX support and file-based routing.
|
|
16
|
+
No React, no Vite, no dependencies. Just plain JavaScript + Babel.
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="card">
|
|
21
|
+
<h2>How routing works</h2>
|
|
22
|
+
<p>
|
|
23
|
+
The URL hash controls which page is shown.
|
|
24
|
+
Changing <strong>#/about</strong> to <strong>#/contact</strong>
|
|
25
|
+
loads the Contact page — no page refresh needed.
|
|
26
|
+
</p>
|
|
27
|
+
<button class="btn btn-primary" onClick={() => navigate("contact")}>
|
|
28
|
+
Go to Contact →
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|