azoxjs 0.1.0 → 0.3.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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # Azox Framework
2
2
 
3
3
  [![CI](https://github.com/darilpratomo/azox/actions/workflows/ci.yml/badge.svg)](https://github.com/darilpratomo/azox/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/azoxjs.svg)](https://www.npmjs.com/package/azoxjs)
5
+ [![install size](https://img.shields.io/badge/dependencies-0-brightgreen)](https://www.npmjs.com/package/azoxjs)
4
6
 
5
7
  **The Sound of Future Web**
6
8
 
@@ -9,7 +11,7 @@ third-party CLI dependencies, no borrowed syntax from React, Vue, or
9
11
  Next.js. It compiles `.azox` components directly into fine-grained,
10
12
  signal-driven DOM updates.
11
13
 
12
- > Status: early development (v0.1.0). APIs are unstable and will
14
+ > Status: early development (v0.3.0). APIs are unstable and will
13
15
  > change without notice until v1.0.
14
16
 
15
17
  ## Why Azox
@@ -63,6 +65,46 @@ pages/blog/first-post.azox → /blog/first-post
63
65
  Build one page with `azox compile --page=blog/first-post`, or by its
64
66
  URL: `azox compile --page=/blog/first-post`.
65
67
 
68
+ ### Dynamic routes
69
+
70
+ A bracketed segment in a filename is a parameter, and the file becomes
71
+ a template that builds one page per entry it declares:
72
+
73
+ ```html
74
+ <!-- pages/blog/[slug].azox -->
75
+ <script>
76
+ import posts from '../../posts.json' with { type: 'json' };
77
+
78
+ // Which pages to build.
79
+ routes(posts.map((p) => ({ slug: p.slug })));
80
+
81
+ // The parameters of the page being built.
82
+ const { slug } = params();
83
+ const post = posts.find((p) => p.slug === slug);
84
+ </script>
85
+
86
+ <article>
87
+ <h1>{post.title}</h1>
88
+ <p>{post.body}</p>
89
+ </article>
90
+ ```
91
+
92
+ ```
93
+ posts.json with two entries → /blog/hello
94
+ → /blog/second
95
+ ```
96
+
97
+ `routes()` takes an array of objects, one per page, each supplying
98
+ every parameter the filename asks for. A filename may hold several
99
+ (`pages/[lang]/[slug].azox`), and `params()` returns them all.
100
+
101
+ Both are build-time declarations: neither reaches the browser. The
102
+ parameters for each page are compiled into its module as a constant.
103
+
104
+ A missing `routes()` call, an entry missing a parameter, a value
105
+ containing a `/`, and two entries producing the same URL are all
106
+ reported as build errors rather than producing a broken site.
107
+
66
108
  ## Components
67
109
 
68
110
  A component is a `.azox` file that declares what it accepts and
@@ -120,10 +162,150 @@ Declaring props with `props()` is what lets the compiler reject a
120
162
  caller that passes something the component never asked for, instead
121
163
  of dropping it silently.
122
164
 
123
- In this version components are presentational: they take props and
124
- render markup, and state lives in the page that uses them. A
125
- component that declares its own logic is rejected with an explicit
126
- error rather than quietly sharing the caller's scope.
165
+ A component may hold its own state. Its `<script>` becomes a scope
166
+ of its own, so two uses of the same component are independent — each
167
+ `<Counter />` below counts separately:
168
+
169
+ ```html
170
+ <!-- components/Counter.azox -->
171
+ <script>
172
+ import { signal } from 'azox/reactivity';
173
+ const count = signal(0);
174
+ </script>
175
+
176
+ <button on:click={() => count.set(count() + 1)}>{count()}</button>
177
+ ```
178
+
179
+ ```html
180
+ <main>
181
+ <Counter />
182
+ <Counter />
183
+ </main>
184
+ ```
185
+
186
+ There is still no component instance at runtime: the compiler wraps
187
+ each use in its own JavaScript scope, which is ordinary scoping
188
+ rather than a framework construct.
189
+
190
+ ## Layouts and the document head
191
+
192
+ A component can carry a `<head>` block, so one shared component holds
193
+ the stylesheet, fonts and scripts every page needs:
194
+
195
+ ```html
196
+ <!-- components/Shell.azox -->
197
+ <head>
198
+ <link rel="stylesheet" href="/style.css" />
199
+ </head>
200
+
201
+ <div class="shell">
202
+ <header>My site</header>
203
+ <slot />
204
+ </div>
205
+ ```
206
+
207
+ ```html
208
+ <!-- pages/index.azox -->
209
+ <head>
210
+ <title>Home — my site</title>
211
+ </head>
212
+
213
+ <script>
214
+ import Shell from '../components/Shell.azox';
215
+ </script>
216
+
217
+ <Shell><main>Just this page's content.</main></Shell>
218
+ ```
219
+
220
+ Blocks are merged with the component's first, so the page has the last
221
+ word. Identical lines are emitted once, and a component used twice
222
+ contributes once. A `<title>` or `<meta name="…">` set by the page
223
+ replaces the component's rather than joining it — a document may hold
224
+ only one of each — so a layout's title is a default, not a conflict.
225
+
226
+ ## Loops and conditionals
227
+
228
+ Control flow is expressed as tags, so it nests inside markup like
229
+ anything else.
230
+
231
+ ```html
232
+ <ul>
233
+ <each item={todos()} as="todo" index="i">
234
+ <li>{i + 1}. {todo}</li>
235
+ </each>
236
+ </ul>
237
+
238
+ <if cond={user()}>
239
+ <p>Signed in as {user().name}</p>
240
+ <else />
241
+ <a href="/login">Sign in</a>
242
+ </if>
243
+ ```
244
+
245
+ Each block marks its place with a pair of comment nodes, and an
246
+ update replaces only the nodes between them.
247
+
248
+ By default a change to a list rebuilds its rows. Give a row an
249
+ identity with `key` and it survives instead: reordering moves it,
250
+ removing one leaves the rest untouched, and adding one does not
251
+ disturb what is already there.
252
+
253
+ ```html
254
+ <each item={tasks()} as="task" key={task.id}>
255
+ <li><TaskRow title={task.title} /></li>
256
+ </each>
257
+ ```
258
+
259
+ Use something stable and unique to the row — a database id, not its
260
+ position, since a position changes when the list does.
261
+
262
+ ## Client-side routing
263
+
264
+ By default every link is a full page load, which is the right
265
+ behaviour for a static site. Opt in to client-side navigation with
266
+ `router: true` in your project's `package.json`:
267
+
268
+ ```json
269
+ {
270
+ "router": true
271
+ }
272
+ ```
273
+
274
+ Internal links are then swapped in place: the new page's HTML is
275
+ fetched, the document body and `<head>` are replaced, and its module
276
+ runs. Scroll position, the back button, and `<a target>` all behave
277
+ as they would with a full load. A link is prefetched when the pointer
278
+ enters it, so the page is usually already in hand by the time it is
279
+ clicked.
280
+
281
+ Anything the router cannot handle — an external origin, a download,
282
+ a modifier-click — falls through to the browser untouched.
283
+
284
+ ## Importing data
285
+
286
+ A `<script>` block may import a `.json` file, which is how a page
287
+ reads a constant it should not have written out by hand:
288
+
289
+ ```html
290
+ <script>
291
+ import pkg from '../package.json' with { type: 'json' };
292
+ </script>
293
+
294
+ <span>v{pkg.version}</span>
295
+ ```
296
+
297
+ The file is read once during the build. Server rendering evaluates
298
+ against it, and the value is compiled into the module as a constant
299
+ rather than imported — the file sits outside the build directory and
300
+ is never deployed, so an import would 404 in the browser.
301
+
302
+ Only the properties the markup reads are included, so importing
303
+ `package.json` for a version does not ship the rest of the file to
304
+ every visitor.
305
+
306
+ Importing a `.js` module is not supported: it would mean executing
307
+ project code during the build. Use a `.json` file for data, and
308
+ `azox/reactivity` for signals.
127
309
 
128
310
  ## Getting Started
129
311
 
@@ -179,6 +361,7 @@ azox/
179
361
  │ ├── dev/ dev server, file watching, live reload
180
362
  │ ├── reactivity/ signal() / effect() / computed()
181
363
  │ ├── renderer/ server-side HTML rendering
364
+ │ ├── router/ opt-in client-side navigation
182
365
  │ ├── build.js the build pipeline, shared by commands
183
366
  │ ├── routes.js file layout → urls and output paths
184
367
  │ └── meta.js version and identity strings