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.
@@ -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
+ }