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,179 +1,179 @@
|
|
|
1
|
-
# 13 · Internals
|
|
2
|
-
|
|
3
|
-
This document explains **how Aziz.js actually works**. It's written for readers who want to understand (and maybe debug) the runtime. You don't need to read it to use the framework.
|
|
4
|
-
|
|
5
|
-
## High-level architecture
|
|
6
|
-
|
|
7
|
-
Aziz.js is three distinct things glued together:
|
|
8
|
-
|
|
9
|
-
1. **Dev server** (`scripts/dev-server.js`) — Node HTTP server. Scans `app/`, serves manifest, transforms JSX, resolves `/_modules/*`, dispatches `/api/*`, serves static files, provides WebSocket live reload.
|
|
10
|
-
2. **Browser runtime** (`_framework/`) — runs in the page. `boot.js` exposes globals, initializes hooks, and calls the router. The router loads the manifest, imports pages/layouts, and mounts them into `#root` via `runtime.js`.
|
|
11
|
-
3. **Transform pipeline** (`scripts/lib/js-transform.js`) — runs on demand: `@babel/standalone` compiles JSX to `h()` calls; import rewriting maps `@/`, bare npm packages, and absolute paths to browser-servable URLs; results are **mtime-cached** per file.
|
|
12
|
-
|
|
13
|
-
## Rendering model (lazy vnodes)
|
|
14
|
-
|
|
15
|
-
The core idea: **everything is a lazy vnode descriptor, not an immediate DOM element**.
|
|
16
|
-
|
|
17
|
-
- `h(tag, props, ...children)` returns `{ [VNODE]: true, type, props, children }`.
|
|
18
|
-
- `type` can be a string (`'div'`), a function (component), `Fragment` symbol, or (rarely) an existing `Node`.
|
|
19
|
-
- `materialize(node)` walks the tree and **turns descriptors into real DOM** top-down.
|
|
20
|
-
- `createElement(tag, props, kids)` only runs when materializing a **string-type vnode** — so nested components don't materialize until their parent/ancestor materializes them. This is the key to getting **context propagation correct** and keeps hook fibers stable.
|
|
21
|
-
|
|
22
|
-
The runtime header literally states this: "Descriptors are executed top-down when they are attached to the DOM."
|
|
23
|
-
|
|
24
|
-
## Fibers & component identity
|
|
25
|
-
|
|
26
|
-
To support hooks per component instance, Aziz.js uses a lightweight **fiber** per mounted component instance.
|
|
27
|
-
|
|
28
|
-
- Each component call is a "materialization". `materializeComponent()` computes a **stable path id** based on the current render path + a counter or `key`.
|
|
29
|
-
- The id looks roughly like `page:/about>About@k:1>Form@i:1`.
|
|
30
|
-
- `getFiber(id)` returns `{ id, hooks: [], mounted: true }` — created on first render, reused on subsequent renders of the same instance.
|
|
31
|
-
- `useState/useEffect/etc.` read/write `currentFiber.hooks[hookIndex++]`.
|
|
32
|
-
- `disposeFibers()` runs every route change: it marks fibers as unmounted, **executes any pending effect cleanups** for hooks that registered them, then clears the whole `fibers` Map. This prevents memory leaks and stale effects.
|
|
33
|
-
- `beginRender(base)` resets the path/counters for a new mount pass.
|
|
34
|
-
|
|
35
|
-
Why keying matters: with `{ key }` you can tell the runtime "this is a different instance" vs "the same instance moved". In a list, giving stable keys preserves hook state for re-ordered items. The code still works without keys, but using `key` is the right thing to do for dynamic lists.
|
|
36
|
-
|
|
37
|
-
## Hooks implementation
|
|
38
|
-
|
|
39
|
-
`hooks.js` owns:
|
|
40
|
-
|
|
41
|
-
- `currentFiber` and `hookIndex` (threaded through `prepareHooks`/`finishHooks`).
|
|
42
|
-
- All hooks as plain functions that access the module's current fiber.
|
|
43
|
-
- `ctxStack` to support `createContext`/`useContext` during render.
|
|
44
|
-
- `scheduleRender()` — defers `window.__rerenderPage()` to a `requestAnimationFrame` (with a 120ms fallback). `renderScheduled` coalesces multiple `setState` calls into one paint.
|
|
45
|
-
|
|
46
|
-
Hooks details:
|
|
47
|
-
|
|
48
|
-
- `useState` stores `{ value }`. `setState` bails if the next value is the same (by reference/equality) and **guards `fiber.mounted === false`** — you can't set state on an unmounted component.
|
|
49
|
-
- `useEffect` compares dependency arrays by reference (`!== prev.deps?.[i]`). It **schedules the effect** via `queueMicrotask()` and only runs if still `fiber.mounted !== false`. Cleanup is stored in the same hook slot and re-run before re-executing the effect.
|
|
50
|
-
- `useContext` walks `ctxStack` from top → bottom (`i >= 0`) to return the nearest provider's value. If none found, falls back to `ctx._defaultValue`.
|
|
51
|
-
- `ContextProvider` pushes `{ ctx, value }`, calls `materializeChildren(props.children)` (injected by `runtime.js`), then pops — so providers need to control the materialization of their subtree. The runtime sets `setMaterializer(materialize)` so providers render children while the context is active.
|
|
52
|
-
- `useLocalStorage` uses `localStorage` with `try/catch` (quota/availability) and listens for `storage` events to sync across tabs.
|
|
53
|
-
- `useFetch` aborts previous in-flight requests, parses JSON/text appropriately, and ignores `AbortError`.
|
|
54
|
-
|
|
55
|
-
**Hooks must only be called while rendering a component.** Calling outside throws.
|
|
56
|
-
|
|
57
|
-
## Materialization walk (conceptually)
|
|
58
|
-
|
|
59
|
-
1. `mountPage(Page, layouts, container, base)` builds `tree = h(Page, {})` wrapped by each layout from inner→outer: `h(Layout, { children: tree })`.
|
|
60
|
-
2. `materialize(tree)` returns `Node | Node[] | null`. If it's a vnode and `type === function`, `materializeComponent` runs.
|
|
61
|
-
3. `materializeComponent` pushes path → computes id → gets fiber → `prepareHooks` → calls `type({...props, children})` → `finishHooks` → `materialize(output)` → pops path → returns nodes.
|
|
62
|
-
4. For string elements, `createElement` creates `document.createElement(tag)`, `applyProps`, then `appendNodes(el, materialize(kids))` — so children are materialized **immediately**, but only because the element vnode itself is being materialized top-down.
|
|
63
|
-
5. `applyProps` handles `ref` (function or object), `class`/`className`, `htmlFor`, `style` object/string, `dangerouslySetInnerHTML`, `value`/`checked` for inputs, `onClick`/`onDoubleClick`/etc. (with `dblclick` alias), boolean attributes (sets/removes attribute), and other attributes.
|
|
64
|
-
|
|
65
|
-
## Router internals
|
|
66
|
-
|
|
67
|
-
- Maintains `routes` (path → module), `layouts` (`[{ path, module }]`), `notFoundModule`, `dev`, `currentParams`, `_lastPathname`.
|
|
68
|
-
- `loadRoutes()` fetches `/_framework/routes.json`, then `import(url)`s each `filePath` (adds cache-bust `?t=Date.now()` in dev).
|
|
69
|
-
- `render()` normalizes pathname, calls `disposeFibers()` if route changed (cleanups + clear state), resolves route via `route-match.js`, collects layouts, applies metadata (outer→inner, page wins for title), captures/restores focus, `mountPage`, adjusts scroll (top on new route, restore on same).
|
|
70
|
-
- `Link` intercepts click only for left-button, no modifiers, same-origin non-hash non-mailto.
|
|
71
|
-
- `useSearchParams` builds a new `URLSearchParams` object each time it's called (it's small).
|
|
72
|
-
|
|
73
|
-
`route-match.js` does the heavy lifting: `matchRoute`, `specificity`/`compareSpecificity`, `resolveRoute`, `collectLayouts`. It treats `[...path]` as a prefix match with required suffix (length >= prefix). `matchesLayout` compares split paths segment by segment and allows dynamic segments to match a concrete path.
|
|
74
|
-
|
|
75
|
-
## API system
|
|
76
|
-
|
|
77
|
-
### Discovery
|
|
78
|
-
|
|
79
|
-
`discoverApiFiles(ROOT)` finds:
|
|
80
|
-
|
|
81
|
-
- `app/api/**/route.js` or `.mjs` → pattern is the folder path under `/api` (e.g. `app/api/users/[id]/route.js` → `/api/users/[id]`)
|
|
82
|
-
- `api/**/*.js` or `.mjs` → `/api/<relative path>` with extension removed (e.g. `api/users/[id].js` → `/api/users/[id]`)
|
|
83
|
-
|
|
84
|
-
Discovered routes are scored and sorted by `compareSpecificity` (static count desc, dynamic asc, catch-all asc, depth desc) so the best match wins.
|
|
85
|
-
|
|
86
|
-
### Loading
|
|
87
|
-
|
|
88
|
-
- `.mjs` → dynamic `import()` with cache-buster
|
|
89
|
-
- `.js` → `require()` (delete from cache first on reload), with fallback to `import()` if Node reports `ERR_REQUIRE_ESM` (handle mixed ESM)
|
|
90
|
-
- `normalizeHandlers(mod)` accepts: CJS `{ GET, POST }` / ESM named exports / ESM `default { … }` / function → legacy `(req, res)`
|
|
91
|
-
|
|
92
|
-
### Request building
|
|
93
|
-
|
|
94
|
-
`buildRequest(req, params, rawBody, port)`:
|
|
95
|
-
|
|
96
|
-
- builds `URL(req.url, base)` → exposes `nextUrl`, `query` (URLSearchParams)
|
|
97
|
-
- parses cookies from `Cookie` header
|
|
98
|
-
- lazy-parses `application/json` body; `request.json()` re-parses on demand and throws `SyntaxError` with `status = 400` on invalid
|
|
99
|
-
- `request.formData()` supports `application/x-www-form-urlencoded` → object
|
|
100
|
-
- exposes `params`, `headers`, `rawBody`
|
|
101
|
-
|
|
102
|
-
### Response sending
|
|
103
|
-
|
|
104
|
-
`sendHandlerResult(result, res, { headOnly })`:
|
|
105
|
-
|
|
106
|
-
- `undefined`/`null` → `500` with hint
|
|
107
|
-
- `string` → `200` + `text/plain`
|
|
108
|
-
- response-like (has `body` or `status`+`headers`) → preserves `status`, copies headers, sends body unless `HEAD`
|
|
109
|
-
- plain object/array → `200` + `application/json`
|
|
110
|
-
|
|
111
|
-
CORS headers are added to all API responses.
|
|
112
|
-
|
|
113
|
-
## Module resolution (browser bare imports)
|
|
114
|
-
|
|
115
|
-
`/_modules/<specifier>` requests are handled by `scripts/lib/module-resolver.js` → `resolveNodeModule(specifier, ROOT)`.
|
|
116
|
-
|
|
117
|
-
- Splits `specifier` into package + subpath
|
|
118
|
-
- Looks in `node_modules/` (project root)
|
|
119
|
-
- Reads `package.json` of the package
|
|
120
|
-
- Honors `exports` field if present: checks `"browser"`, `"module"`, `"main"` in that order when mapping to a file
|
|
121
|
-
- Falls back to `browser`/`module`/`main` fields
|
|
122
|
-
- Resolves relative `./…` subpaths
|
|
123
|
-
- Returns the final absolute filesystem path, or `null` if not found
|
|
124
|
-
|
|
125
|
-
The dev server reads that file and runs `transformModule` (JSX transform skipped for framework, but bare modules usually contain no JSX; imports inside that module also get rewritten if they're from your app tree? No — the module being served is an npm package file; `transformModule` detects `isFrameworkPath`? Framework files are under `/_framework/` — npm packages are under `node_modules/`, so `isFrameworkPath` is false for them, but Babel will run; many packages don't use JSX. Import rewriting inside that served package's code would rewrite its own imports — but rewriting is correct because it's being served to the browser. The transform is idempotent-ish.
|
|
126
|
-
|
|
127
|
-
## Transform cache
|
|
128
|
-
|
|
129
|
-
`transformModule(code, filePath, mtimeMs)`:
|
|
130
|
-
|
|
131
|
-
- returns cached code if `hit.mtimeMs === mtimeMs`
|
|
132
|
-
- skips JSX transform for files whose path includes `/_framework/`
|
|
133
|
-
- otherwise compiles with Babel React preset (`pragma: 'h'`, `pragmaFrag: 'Fragment'`)
|
|
134
|
-
- always runs `rewriteImports` after
|
|
135
|
-
- stores `{ mtimeMs, code }`
|
|
136
|
-
|
|
137
|
-
Invalidated on file change by the watcher.
|
|
138
|
-
|
|
139
|
-
## Live reload
|
|
140
|
-
|
|
141
|
-
- Development only. `server.on('upgrade')` upgrades HTTP → WS, uses a minimal control-frame (no text message parser needed — just accepts the protocol and can send a 1-byte-ish ping/reload; it sends `reload` as a text frame by writing the header+payload for a single-frame text message `0x81 <len> ...`).
|
|
142
|
-
- Watcher monitors: `app`, `api`, `components`, `lib`, `models`, `styles`, `_framework` recursively. Ignores non-JS/JSX/CSS/JSON extensions for the "changed" signal.
|
|
143
|
-
- When a change happens: invalidate transform cache for that file; if the file was under `api/`, `lib/`, or `models/`, **reload API routes** (purge project require cache, `loadApiRoutes`) and then notify clients; otherwise just notify clients. Reloading API routes after `lib/`/`models/` changes means `require()` sees fresh code.
|
|
144
|
-
- In production, the live-reload client file (`/scripts/live-reload-client.js`) is replaced by an empty stub.
|
|
145
|
-
|
|
146
|
-
## Path traversal guard
|
|
147
|
-
|
|
148
|
-
When serving static files (anything that's not `/_modules/*`, `/_framework/routes.json`, or API), the server:
|
|
149
|
-
|
|
150
|
-
```js
|
|
151
|
-
const resolved = path.resolve(ROOT, '.' + (decoded.startsWith('/') ? decoded : '/' + decoded));
|
|
152
|
-
const rootResolved = path.resolve(ROOT);
|
|
153
|
-
if (resolved !== rootResolved && !resolved.startsWith(rootResolved + path.sep)) {
|
|
154
|
-
res.writeHead(403); res.end('Forbidden'); return;
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
This prevents `../` from escaping the project directory. The check is done after `decodeURIComponent` but before `fs.statSync`.
|
|
159
|
-
|
|
160
|
-
## What's not "done" (by design)
|
|
161
|
-
|
|
162
|
-
- No real build step. On-the-fly transform + ETag/304 covers small apps.
|
|
163
|
-
- No SSR/SSG, only client-side rendering of pages.
|
|
164
|
-
- No CSS extraction/processing beyond serving files.
|
|
165
|
-
- No `.env` loader in the server (just `process.env`).
|
|
166
|
-
- No type checking (no TS support baked in; `.jsx` extension also not required, just `.js` with JSX).
|
|
167
|
-
|
|
168
|
-
## Tips for debugging
|
|
169
|
-
|
|
170
|
-
- Check `/ _framework/routes.json` for manifest accuracy.
|
|
171
|
-
- Read the server logs: API load summary, transform errors, 403/404s.
|
|
172
|
-
- In the browser console, `window.__azizDev` shows `dev` mode, `window.__azizReady` is `true` after boot.
|
|
173
|
-
- `api.defaults` reveals effective baseURL/timeout/retries.
|
|
174
|
-
- Network tab: see `/_modules/*` requests and transformed JS.
|
|
175
|
-
- For state bugs, console log in hooks? The source is short and readable.
|
|
176
|
-
|
|
177
|
-
## Wrap-up
|
|
178
|
-
|
|
179
|
-
The runtime is deliberately small: `h` + materialize (top-down, lazy), fibers + hooks (module-level state), a context stack, a file-based router. It's "React-like" in behavior for the bits it supports, and "Next.js-like" for the developer surface (`app/`, `api/`, layouts, hooks) — but cuts out the build tooling and ceremony.
|
|
1
|
+
# 13 · Internals
|
|
2
|
+
|
|
3
|
+
This document explains **how Aziz.js actually works**. It's written for readers who want to understand (and maybe debug) the runtime. You don't need to read it to use the framework.
|
|
4
|
+
|
|
5
|
+
## High-level architecture
|
|
6
|
+
|
|
7
|
+
Aziz.js is three distinct things glued together:
|
|
8
|
+
|
|
9
|
+
1. **Dev server** (`scripts/dev-server.js`) — Node HTTP server. Scans `app/`, serves manifest, transforms JSX, resolves `/_modules/*`, dispatches `/api/*`, serves static files, provides WebSocket live reload.
|
|
10
|
+
2. **Browser runtime** (`_framework/`) — runs in the page. `boot.js` exposes globals, initializes hooks, and calls the router. The router loads the manifest, imports pages/layouts, and mounts them into `#root` via `runtime.js`.
|
|
11
|
+
3. **Transform pipeline** (`scripts/lib/js-transform.js`) — runs on demand: `@babel/standalone` compiles JSX to `h()` calls; import rewriting maps `@/`, bare npm packages, and absolute paths to browser-servable URLs; results are **mtime-cached** per file.
|
|
12
|
+
|
|
13
|
+
## Rendering model (lazy vnodes)
|
|
14
|
+
|
|
15
|
+
The core idea: **everything is a lazy vnode descriptor, not an immediate DOM element**.
|
|
16
|
+
|
|
17
|
+
- `h(tag, props, ...children)` returns `{ [VNODE]: true, type, props, children }`.
|
|
18
|
+
- `type` can be a string (`'div'`), a function (component), `Fragment` symbol, or (rarely) an existing `Node`.
|
|
19
|
+
- `materialize(node)` walks the tree and **turns descriptors into real DOM** top-down.
|
|
20
|
+
- `createElement(tag, props, kids)` only runs when materializing a **string-type vnode** — so nested components don't materialize until their parent/ancestor materializes them. This is the key to getting **context propagation correct** and keeps hook fibers stable.
|
|
21
|
+
|
|
22
|
+
The runtime header literally states this: "Descriptors are executed top-down when they are attached to the DOM."
|
|
23
|
+
|
|
24
|
+
## Fibers & component identity
|
|
25
|
+
|
|
26
|
+
To support hooks per component instance, Aziz.js uses a lightweight **fiber** per mounted component instance.
|
|
27
|
+
|
|
28
|
+
- Each component call is a "materialization". `materializeComponent()` computes a **stable path id** based on the current render path + a counter or `key`.
|
|
29
|
+
- The id looks roughly like `page:/about>About@k:1>Form@i:1`.
|
|
30
|
+
- `getFiber(id)` returns `{ id, hooks: [], mounted: true }` — created on first render, reused on subsequent renders of the same instance.
|
|
31
|
+
- `useState/useEffect/etc.` read/write `currentFiber.hooks[hookIndex++]`.
|
|
32
|
+
- `disposeFibers()` runs every route change: it marks fibers as unmounted, **executes any pending effect cleanups** for hooks that registered them, then clears the whole `fibers` Map. This prevents memory leaks and stale effects.
|
|
33
|
+
- `beginRender(base)` resets the path/counters for a new mount pass.
|
|
34
|
+
|
|
35
|
+
Why keying matters: with `{ key }` you can tell the runtime "this is a different instance" vs "the same instance moved". In a list, giving stable keys preserves hook state for re-ordered items. The code still works without keys, but using `key` is the right thing to do for dynamic lists.
|
|
36
|
+
|
|
37
|
+
## Hooks implementation
|
|
38
|
+
|
|
39
|
+
`hooks.js` owns:
|
|
40
|
+
|
|
41
|
+
- `currentFiber` and `hookIndex` (threaded through `prepareHooks`/`finishHooks`).
|
|
42
|
+
- All hooks as plain functions that access the module's current fiber.
|
|
43
|
+
- `ctxStack` to support `createContext`/`useContext` during render.
|
|
44
|
+
- `scheduleRender()` — defers `window.__rerenderPage()` to a `requestAnimationFrame` (with a 120ms fallback). `renderScheduled` coalesces multiple `setState` calls into one paint.
|
|
45
|
+
|
|
46
|
+
Hooks details:
|
|
47
|
+
|
|
48
|
+
- `useState` stores `{ value }`. `setState` bails if the next value is the same (by reference/equality) and **guards `fiber.mounted === false`** — you can't set state on an unmounted component.
|
|
49
|
+
- `useEffect` compares dependency arrays by reference (`!== prev.deps?.[i]`). It **schedules the effect** via `queueMicrotask()` and only runs if still `fiber.mounted !== false`. Cleanup is stored in the same hook slot and re-run before re-executing the effect.
|
|
50
|
+
- `useContext` walks `ctxStack` from top → bottom (`i >= 0`) to return the nearest provider's value. If none found, falls back to `ctx._defaultValue`.
|
|
51
|
+
- `ContextProvider` pushes `{ ctx, value }`, calls `materializeChildren(props.children)` (injected by `runtime.js`), then pops — so providers need to control the materialization of their subtree. The runtime sets `setMaterializer(materialize)` so providers render children while the context is active.
|
|
52
|
+
- `useLocalStorage` uses `localStorage` with `try/catch` (quota/availability) and listens for `storage` events to sync across tabs.
|
|
53
|
+
- `useFetch` aborts previous in-flight requests, parses JSON/text appropriately, and ignores `AbortError`.
|
|
54
|
+
|
|
55
|
+
**Hooks must only be called while rendering a component.** Calling outside throws.
|
|
56
|
+
|
|
57
|
+
## Materialization walk (conceptually)
|
|
58
|
+
|
|
59
|
+
1. `mountPage(Page, layouts, container, base)` builds `tree = h(Page, {})` wrapped by each layout from inner→outer: `h(Layout, { children: tree })`.
|
|
60
|
+
2. `materialize(tree)` returns `Node | Node[] | null`. If it's a vnode and `type === function`, `materializeComponent` runs.
|
|
61
|
+
3. `materializeComponent` pushes path → computes id → gets fiber → `prepareHooks` → calls `type({...props, children})` → `finishHooks` → `materialize(output)` → pops path → returns nodes.
|
|
62
|
+
4. For string elements, `createElement` creates `document.createElement(tag)`, `applyProps`, then `appendNodes(el, materialize(kids))` — so children are materialized **immediately**, but only because the element vnode itself is being materialized top-down.
|
|
63
|
+
5. `applyProps` handles `ref` (function or object), `class`/`className`, `htmlFor`, `style` object/string, `dangerouslySetInnerHTML`, `value`/`checked` for inputs, `onClick`/`onDoubleClick`/etc. (with `dblclick` alias), boolean attributes (sets/removes attribute), and other attributes.
|
|
64
|
+
|
|
65
|
+
## Router internals
|
|
66
|
+
|
|
67
|
+
- Maintains `routes` (path → module), `layouts` (`[{ path, module }]`), `notFoundModule`, `dev`, `currentParams`, `_lastPathname`.
|
|
68
|
+
- `loadRoutes()` fetches `/_framework/routes.json`, then `import(url)`s each `filePath` (adds cache-bust `?t=Date.now()` in dev).
|
|
69
|
+
- `render()` normalizes pathname, calls `disposeFibers()` if route changed (cleanups + clear state), resolves route via `route-match.js`, collects layouts, applies metadata (outer→inner, page wins for title), captures/restores focus, `mountPage`, adjusts scroll (top on new route, restore on same).
|
|
70
|
+
- `Link` intercepts click only for left-button, no modifiers, same-origin non-hash non-mailto.
|
|
71
|
+
- `useSearchParams` builds a new `URLSearchParams` object each time it's called (it's small).
|
|
72
|
+
|
|
73
|
+
`route-match.js` does the heavy lifting: `matchRoute`, `specificity`/`compareSpecificity`, `resolveRoute`, `collectLayouts`. It treats `[...path]` as a prefix match with required suffix (length >= prefix). `matchesLayout` compares split paths segment by segment and allows dynamic segments to match a concrete path.
|
|
74
|
+
|
|
75
|
+
## API system
|
|
76
|
+
|
|
77
|
+
### Discovery
|
|
78
|
+
|
|
79
|
+
`discoverApiFiles(ROOT)` finds:
|
|
80
|
+
|
|
81
|
+
- `app/api/**/route.js` or `.mjs` → pattern is the folder path under `/api` (e.g. `app/api/users/[id]/route.js` → `/api/users/[id]`)
|
|
82
|
+
- `api/**/*.js` or `.mjs` → `/api/<relative path>` with extension removed (e.g. `api/users/[id].js` → `/api/users/[id]`)
|
|
83
|
+
|
|
84
|
+
Discovered routes are scored and sorted by `compareSpecificity` (static count desc, dynamic asc, catch-all asc, depth desc) so the best match wins.
|
|
85
|
+
|
|
86
|
+
### Loading
|
|
87
|
+
|
|
88
|
+
- `.mjs` → dynamic `import()` with cache-buster
|
|
89
|
+
- `.js` → `require()` (delete from cache first on reload), with fallback to `import()` if Node reports `ERR_REQUIRE_ESM` (handle mixed ESM)
|
|
90
|
+
- `normalizeHandlers(mod)` accepts: CJS `{ GET, POST }` / ESM named exports / ESM `default { … }` / function → legacy `(req, res)`
|
|
91
|
+
|
|
92
|
+
### Request building
|
|
93
|
+
|
|
94
|
+
`buildRequest(req, params, rawBody, port)`:
|
|
95
|
+
|
|
96
|
+
- builds `URL(req.url, base)` → exposes `nextUrl`, `query` (URLSearchParams)
|
|
97
|
+
- parses cookies from `Cookie` header
|
|
98
|
+
- lazy-parses `application/json` body; `request.json()` re-parses on demand and throws `SyntaxError` with `status = 400` on invalid
|
|
99
|
+
- `request.formData()` supports `application/x-www-form-urlencoded` → object
|
|
100
|
+
- exposes `params`, `headers`, `rawBody`
|
|
101
|
+
|
|
102
|
+
### Response sending
|
|
103
|
+
|
|
104
|
+
`sendHandlerResult(result, res, { headOnly })`:
|
|
105
|
+
|
|
106
|
+
- `undefined`/`null` → `500` with hint
|
|
107
|
+
- `string` → `200` + `text/plain`
|
|
108
|
+
- response-like (has `body` or `status`+`headers`) → preserves `status`, copies headers, sends body unless `HEAD`
|
|
109
|
+
- plain object/array → `200` + `application/json`
|
|
110
|
+
|
|
111
|
+
CORS headers are added to all API responses.
|
|
112
|
+
|
|
113
|
+
## Module resolution (browser bare imports)
|
|
114
|
+
|
|
115
|
+
`/_modules/<specifier>` requests are handled by `scripts/lib/module-resolver.js` → `resolveNodeModule(specifier, ROOT)`.
|
|
116
|
+
|
|
117
|
+
- Splits `specifier` into package + subpath
|
|
118
|
+
- Looks in `node_modules/` (project root)
|
|
119
|
+
- Reads `package.json` of the package
|
|
120
|
+
- Honors `exports` field if present: checks `"browser"`, `"module"`, `"main"` in that order when mapping to a file
|
|
121
|
+
- Falls back to `browser`/`module`/`main` fields
|
|
122
|
+
- Resolves relative `./…` subpaths
|
|
123
|
+
- Returns the final absolute filesystem path, or `null` if not found
|
|
124
|
+
|
|
125
|
+
The dev server reads that file and runs `transformModule` (JSX transform skipped for framework, but bare modules usually contain no JSX; imports inside that module also get rewritten if they're from your app tree? No — the module being served is an npm package file; `transformModule` detects `isFrameworkPath`? Framework files are under `/_framework/` — npm packages are under `node_modules/`, so `isFrameworkPath` is false for them, but Babel will run; many packages don't use JSX. Import rewriting inside that served package's code would rewrite its own imports — but rewriting is correct because it's being served to the browser. The transform is idempotent-ish.
|
|
126
|
+
|
|
127
|
+
## Transform cache
|
|
128
|
+
|
|
129
|
+
`transformModule(code, filePath, mtimeMs)`:
|
|
130
|
+
|
|
131
|
+
- returns cached code if `hit.mtimeMs === mtimeMs`
|
|
132
|
+
- skips JSX transform for files whose path includes `/_framework/`
|
|
133
|
+
- otherwise compiles with Babel React preset (`pragma: 'h'`, `pragmaFrag: 'Fragment'`)
|
|
134
|
+
- always runs `rewriteImports` after
|
|
135
|
+
- stores `{ mtimeMs, code }`
|
|
136
|
+
|
|
137
|
+
Invalidated on file change by the watcher.
|
|
138
|
+
|
|
139
|
+
## Live reload
|
|
140
|
+
|
|
141
|
+
- Development only. `server.on('upgrade')` upgrades HTTP → WS, uses a minimal control-frame (no text message parser needed — just accepts the protocol and can send a 1-byte-ish ping/reload; it sends `reload` as a text frame by writing the header+payload for a single-frame text message `0x81 <len> ...`).
|
|
142
|
+
- Watcher monitors: `app`, `api`, `components`, `lib`, `models`, `styles`, `_framework` recursively. Ignores non-JS/JSX/CSS/JSON extensions for the "changed" signal.
|
|
143
|
+
- When a change happens: invalidate transform cache for that file; if the file was under `api/`, `lib/`, or `models/`, **reload API routes** (purge project require cache, `loadApiRoutes`) and then notify clients; otherwise just notify clients. Reloading API routes after `lib/`/`models/` changes means `require()` sees fresh code.
|
|
144
|
+
- In production, the live-reload client file (`/scripts/live-reload-client.js`) is replaced by an empty stub.
|
|
145
|
+
|
|
146
|
+
## Path traversal guard
|
|
147
|
+
|
|
148
|
+
When serving static files (anything that's not `/_modules/*`, `/_framework/routes.json`, or API), the server:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
const resolved = path.resolve(ROOT, '.' + (decoded.startsWith('/') ? decoded : '/' + decoded));
|
|
152
|
+
const rootResolved = path.resolve(ROOT);
|
|
153
|
+
if (resolved !== rootResolved && !resolved.startsWith(rootResolved + path.sep)) {
|
|
154
|
+
res.writeHead(403); res.end('Forbidden'); return;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This prevents `../` from escaping the project directory. The check is done after `decodeURIComponent` but before `fs.statSync`.
|
|
159
|
+
|
|
160
|
+
## What's not "done" (by design)
|
|
161
|
+
|
|
162
|
+
- No real build step. On-the-fly transform + ETag/304 covers small apps.
|
|
163
|
+
- No SSR/SSG, only client-side rendering of pages.
|
|
164
|
+
- No CSS extraction/processing beyond serving files.
|
|
165
|
+
- No `.env` loader in the server (just `process.env`).
|
|
166
|
+
- No type checking (no TS support baked in; `.jsx` extension also not required, just `.js` with JSX).
|
|
167
|
+
|
|
168
|
+
## Tips for debugging
|
|
169
|
+
|
|
170
|
+
- Check `/ _framework/routes.json` for manifest accuracy.
|
|
171
|
+
- Read the server logs: API load summary, transform errors, 403/404s.
|
|
172
|
+
- In the browser console, `window.__azizDev` shows `dev` mode, `window.__azizReady` is `true` after boot.
|
|
173
|
+
- `api.defaults` reveals effective baseURL/timeout/retries.
|
|
174
|
+
- Network tab: see `/_modules/*` requests and transformed JS.
|
|
175
|
+
- For state bugs, console log in hooks? The source is short and readable.
|
|
176
|
+
|
|
177
|
+
## Wrap-up
|
|
178
|
+
|
|
179
|
+
The runtime is deliberately small: `h` + materialize (top-down, lazy), fibers + hooks (module-level state), a context stack, a file-based router. It's "React-like" in behavior for the bits it supports, and "Next.js-like" for the developer surface (`app/`, `api/`, layouts, hooks) — but cuts out the build tooling and ceremony.
|
package/template/docs/README.md
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
# Aziz.js Documentation
|
|
2
|
-
|
|
3
|
-
A lightweight Next.js clone — file-based routing, React hooks, server-side API routes, and ESM imports, with zero runtime dependencies.
|
|
4
|
-
|
|
5
|
-
New to Aziz.js? Start at **01**. Comfortable? Jump to the reference you need.
|
|
6
|
-
|
|
7
|
-
## Contents
|
|
8
|
-
|
|
9
|
-
| # | Guide | Who it's for |
|
|
10
|
-
|---|-------|--------------|
|
|
11
|
-
| 01 | [Getting Started](./01-getting-started.md) | Beginner — install and run your first app |
|
|
12
|
-
| 02 | [Project Structure](./02-project-structure.md) | Beginner — what every folder is for |
|
|
13
|
-
| 03 | [Pages & Layouts](./03-pages-and-layouts.md) | Beginner — creating your first screens |
|
|
14
|
-
| 04 | [Routing](./04-routing.md) | Intermediate — dynamic, nested, catch-all routes |
|
|
15
|
-
| 05 | [Hooks](./05-hooks.md) | Intermediate — state, effects, context |
|
|
16
|
-
| 06 | [API Routes](./06-api-routes.md) | Intermediate — the server side |
|
|
17
|
-
| 07 | [API Client](./07-api-client.md) | Intermediate — calling your API from the browser |
|
|
18
|
-
| 08 | [Styling](./08-styling.md) | Beginner — CSS, Bootstrap, Tailwind |
|
|
19
|
-
| 09 | [Imports & Modules](./09-imports-and-modules.md) | Intermediate — `@/`, bare npm imports, aliases |
|
|
20
|
-
| 10 | [Database & Models](./10-database-and-models.md) | Intermediate — MongoDB/Mongoose, REST data |
|
|
21
|
-
| 11 | [Authentication](./11-authentication.md) | Advanced — sessions, JWT, protected routes |
|
|
22
|
-
| 12 | [Production & Deployment](./12-production-and-deployment.md) | Advanced — build, ship, host |
|
|
23
|
-
| 13 | [Internals](./13-internals.md) | Advanced — how the runtime actually works |
|
|
24
|
-
|
|
25
|
-
## TL;DR
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npx create-aziz-app my-app
|
|
29
|
-
cd my-app
|
|
30
|
-
npm install
|
|
31
|
-
npm run dev
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Open <http://localhost:3000>.
|
|
35
|
-
|
|
36
|
-
Then:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
// app/todos/page.js → /todos
|
|
40
|
-
export default function Todos() {
|
|
41
|
-
const [items, setItems] = useState([]);
|
|
42
|
-
return <div>{items.length}</div>;
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
```js
|
|
47
|
-
// api/todos.js → /api/todos
|
|
48
|
-
module.exports = { GET: async () => ({ todos: [] }) };
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Everything else is documented in these guides.
|
|
52
|
-
|
|
53
|
-
## Where things live in an app
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
my-app/
|
|
57
|
-
├── index.html ← entry point (loaded first)
|
|
58
|
-
├── app/ ← pages + layouts (file-based routing)
|
|
59
|
-
├── api/ ← server-side API routes (Node.js)
|
|
60
|
-
├── components/ ← shared UI components
|
|
61
|
-
├── lib/ models/ ← helpers + data models
|
|
62
|
-
├── styles/ ← CSS
|
|
63
|
-
├── _framework/ ← the runtime (h, hooks, router, api client)
|
|
64
|
-
└── scripts/ ← dev server + live reload
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
See [Project Structure](./02-project-structure.md) for the full breakdown.
|
|
1
|
+
# Aziz.js Documentation
|
|
2
|
+
|
|
3
|
+
A lightweight Next.js clone — file-based routing, React hooks, server-side API routes, and ESM imports, with zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
New to Aziz.js? Start at **01**. Comfortable? Jump to the reference you need.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
| # | Guide | Who it's for |
|
|
10
|
+
|---|-------|--------------|
|
|
11
|
+
| 01 | [Getting Started](./01-getting-started.md) | Beginner — install and run your first app |
|
|
12
|
+
| 02 | [Project Structure](./02-project-structure.md) | Beginner — what every folder is for |
|
|
13
|
+
| 03 | [Pages & Layouts](./03-pages-and-layouts.md) | Beginner — creating your first screens |
|
|
14
|
+
| 04 | [Routing](./04-routing.md) | Intermediate — dynamic, nested, catch-all routes |
|
|
15
|
+
| 05 | [Hooks](./05-hooks.md) | Intermediate — state, effects, context |
|
|
16
|
+
| 06 | [API Routes](./06-api-routes.md) | Intermediate — the server side |
|
|
17
|
+
| 07 | [API Client](./07-api-client.md) | Intermediate — calling your API from the browser |
|
|
18
|
+
| 08 | [Styling](./08-styling.md) | Beginner — CSS, Bootstrap, Tailwind |
|
|
19
|
+
| 09 | [Imports & Modules](./09-imports-and-modules.md) | Intermediate — `@/`, bare npm imports, aliases |
|
|
20
|
+
| 10 | [Database & Models](./10-database-and-models.md) | Intermediate — MongoDB/Mongoose, REST data |
|
|
21
|
+
| 11 | [Authentication](./11-authentication.md) | Advanced — sessions, JWT, protected routes |
|
|
22
|
+
| 12 | [Production & Deployment](./12-production-and-deployment.md) | Advanced — build, ship, host |
|
|
23
|
+
| 13 | [Internals](./13-internals.md) | Advanced — how the runtime actually works |
|
|
24
|
+
|
|
25
|
+
## TL;DR
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx create-aziz-app my-app
|
|
29
|
+
cd my-app
|
|
30
|
+
npm install
|
|
31
|
+
npm run dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Open <http://localhost:3000>.
|
|
35
|
+
|
|
36
|
+
Then:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
// app/todos/page.js → /todos
|
|
40
|
+
export default function Todos() {
|
|
41
|
+
const [items, setItems] = useState([]);
|
|
42
|
+
return <div>{items.length}</div>;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// api/todos.js → /api/todos
|
|
48
|
+
module.exports = { GET: async () => ({ todos: [] }) };
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Everything else is documented in these guides.
|
|
52
|
+
|
|
53
|
+
## Where things live in an app
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
my-app/
|
|
57
|
+
├── index.html ← entry point (loaded first)
|
|
58
|
+
├── app/ ← pages + layouts (file-based routing)
|
|
59
|
+
├── api/ ← server-side API routes (Node.js)
|
|
60
|
+
├── components/ ← shared UI components
|
|
61
|
+
├── lib/ models/ ← helpers + data models
|
|
62
|
+
├── styles/ ← CSS
|
|
63
|
+
├── _framework/ ← the runtime (h, hooks, router, api client)
|
|
64
|
+
└── scripts/ ← dev server + live reload
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
See [Project Structure](./02-project-structure.md) for the full breakdown.
|
package/template/index.html
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
7
|
-
<meta http-equiv="Pragma" content="no-cache">
|
|
8
|
-
<meta http-equiv="Expires" content="0">
|
|
9
|
-
<meta name="description" content="Built with Aziz.js">
|
|
10
|
-
<title>My App</title>
|
|
11
|
-
|
|
12
|
-
<!-- Global styles (nav, cards, buttons, etc.) -->
|
|
13
|
-
<link rel="stylesheet" href="/styles/globals.css">
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<div id="root"></div>
|
|
17
|
-
<script type="module" src="/_framework/boot.js"></script>
|
|
18
|
-
<script src="/scripts/live-reload-client.js"></script>
|
|
19
|
-
</body>
|
|
20
|
-
</html>
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
7
|
+
<meta http-equiv="Pragma" content="no-cache">
|
|
8
|
+
<meta http-equiv="Expires" content="0">
|
|
9
|
+
<meta name="description" content="Built with Aziz.js">
|
|
10
|
+
<title>My App</title>
|
|
11
|
+
|
|
12
|
+
<!-- Global styles (nav, cards, buttons, etc.) -->
|
|
13
|
+
<link rel="stylesheet" href="/styles/globals.css">
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
<script type="module" src="/_framework/boot.js"></script>
|
|
18
|
+
<script src="/scripts/live-reload-client.js"></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
package/template/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "my-aziz-app",
|
|
3
|
-
"version": "3.1.0",
|
|
4
|
-
"description": "Built with Aziz.js",
|
|
5
|
-
"private": true,
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "node scripts/dev-server.js",
|
|
8
|
-
"build": "node scripts/dev-server.js --production",
|
|
9
|
-
"start": "node scripts/dev-server.js --production",
|
|
10
|
-
"test": "node --test"
|
|
11
|
-
},
|
|
12
|
-
"devDependencies": {
|
|
13
|
-
"@babel/standalone": "^7.0.0"
|
|
14
|
-
},
|
|
15
|
-
"license": "MIT"
|
|
16
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "my-aziz-app",
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Built with Aziz.js",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "node scripts/dev-server.js",
|
|
8
|
+
"build": "node scripts/dev-server.js --production",
|
|
9
|
+
"start": "node scripts/dev-server.js --production",
|
|
10
|
+
"test": "node --test"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@babel/standalone": "^7.0.0"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT"
|
|
16
|
+
}
|