rasti 4.1.0-alpha.0 → 4.1.0-alpha.1

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,7 +1,7 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.0-alpha.0/docs/logo-dark.svg">
4
- <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.0-alpha.0/docs/logo.svg" height="120">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.0-alpha.1/docs/logo-dark.svg">
4
+ <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.1.0-alpha.1/docs/logo.svg" height="120">
5
5
  </picture>
6
6
  </p>
7
7
 
@@ -184,9 +184,35 @@ Counter.mount({ model }, document.body);
184
184
  - **Just the Right Abstraction**
185
185
  Keeps you close to the DOM with no over-engineering. Fully hackable — if you're curious about how something works, just check the source code.
186
186
 
187
+ ## Scaffolding a New Project
188
+
189
+ The fastest way to start a real-world **Rasti** project is [`create-rasti`](https://github.com/8tentaculos/create-rasti), the official scaffolding tool. It generates a ready-to-use **Rasti** + **Vite** setup with optional server-side rendering, routing, styling, and icon components.
190
+
191
+ ```bash
192
+ # Interactive setup
193
+ npm create rasti
194
+
195
+ # Non-interactive single-page app
196
+ npm create rasti my-app
197
+
198
+ # Server-side rendering with routing and Tailwind CSS
199
+ npm create rasti my-app --ssr --router --tailwind
200
+ ```
201
+
202
+ Available options include:
203
+
204
+ - **Rendering** — Single-page app (default), server-side rendering (`--ssr`), or static pre-rendering (`--static`).
205
+ - **Styling** — Plain CSS (default), Tailwind CSS (`--tailwind`), or CSSFUN with light/dark theme support (`--cssfun`).
206
+ - **Routing** (`--router`) — A small universal router built on `path-to-regexp`.
207
+ - **Icons** (`--icons`) — Generate **Rasti** components from popular SVG icon sets (heroicons, akar-icons, feathericon, pixelarticons, and more).
208
+
209
+ See the [`create-rasti` repository](https://github.com/8tentaculos/create-rasti) for the full list of templates and options.
210
+
187
211
  ## Example
188
212
 
189
- You can find a sample **TODO application** in the [example folder](https://github.com/8tentaculos/rasti/tree/master/example/todo) of the **Rasti** [GitHub repository](https://github.com/8tentaculos/rasti). This example serves as a great starting point for your own projects. Try it live [here](https://rasti.js.org/example/todo/index.html).
213
+ To see how **Rasti**'s API and architecture come together in a small app, explore the sample **TODO application** in the [example folder](https://github.com/8tentaculos/rasti/tree/master/example/todo) of the **Rasti** [GitHub repository](https://github.com/8tentaculos/rasti). It's a concise, self-contained reference for understanding how models, views, and components fit together in a simple application. Try it live [here](https://rasti.js.org/example/todo/index.html).
214
+
215
+ To scaffold a real-world project, use [`create-rasti`](#scaffolding-a-new-project).
190
216
 
191
217
  ## API Documentation
192
218
 
@@ -219,6 +245,22 @@ const Plain = Component.create`<div></div>`;
219
245
  new Plain({ anything: 'goes' }); // ✅
220
246
  ```
221
247
 
248
+ `Component.extend` adds the object members to the instance type. Inside its methods, `this` is the extended component, and lifecycle overrides get their parameters typed automatically:
249
+
250
+ ```ts
251
+ const Counter = Component.create<{ initial: number }>`<div>...</div>`.extend({
252
+ onCreate() {
253
+ this.state = new Model({ count: this.props.initial }); // `this` is typed
254
+ },
255
+ onChange(model, changed) { // parameters typed automatically
256
+ if ('count' in changed) this.render();
257
+ },
258
+ increment() { this.state.count++; },
259
+ });
260
+
261
+ Counter.mount({ initial: 0 }, document.body).increment(); // ✅ increment is typed
262
+ ```
263
+
222
264
  ### Models
223
265
 
224
266
  Type the attributes with `Model<Attrs>`. Use **declaration merging** to surface the auto-generated getters/setters as instance properties:
@@ -278,14 +320,9 @@ type S = State<Counter>; // CounterState
278
320
 
279
321
  For those working with LLMs, there is an [AI Agents reference guide](/docs/AGENTS.md) that provides API patterns, lifecycle methods, and best practices, optimized for LLM context. You can share this guide with AI assistants to help them understand **Rasti**'s architecture and component APIs.
280
322
 
281
- ## Version History
282
-
283
- We strive to minimize breaking changes between major versions. However, if you're migrating between major versions, please refer to the release notes below for details on any breaking changes and migration tips.
323
+ ## Changelog
284
324
 
285
- - **[v4.0.0](https://github.com/8tentaculos/rasti/releases/tag/v4.0.0)**
286
- - **[v3.0.0](https://github.com/8tentaculos/rasti/releases/tag/v3.0.0)**
287
- - **[v2.0.0](https://github.com/8tentaculos/rasti/releases/tag/v2.0.0)**
288
- - **[v1.0.0](https://github.com/8tentaculos/rasti/releases/tag/v1.0.0)**
325
+ Release history and migration notes for major versions are in [CHANGELOG.md](CHANGELOG.md).
289
326
 
290
327
  ## License
291
328
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rasti",
3
- "version": "4.1.0-alpha.0",
3
+ "version": "4.1.0-alpha.1",
4
4
  "description": "Modern MVC for building user interfaces",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",
@@ -38,7 +38,7 @@ export interface SafeHTML {
38
38
  }
39
39
 
40
40
  /** A partial template produced by `this.partial` — preserves structure for position-based recycling. */
41
- export interface Partial {
41
+ export interface ComponentPartial {
42
42
  strings: TemplateStringsArray;
43
43
  expressions: any[];
44
44
  }
@@ -75,7 +75,40 @@ export type Props<C> = C extends Component<infer P, any, any> ? P : never;
75
75
  /** Extracts the state type `S` from a Component subclass. */
76
76
  export type State<C> = C extends Component<any, infer S, any> ? S : never;
77
77
  /** Extracts the model type `M` from a Component / View subclass. */
78
- export type ComponentModel<C> = C extends Component<any, any, infer M> ? M : never;
78
+ export type ComponentModel<C> =
79
+ C extends Component<any, any, infer M> ? M :
80
+ C extends View<infer M> ? M :
81
+ never;
82
+
83
+ /**
84
+ * Lifecycle methods, with their real signatures, made available for contextual typing
85
+ * inside `Component.extend({ ... })` so overrides don't need parameter annotations.
86
+ */
87
+ export interface ComponentLifecycle {
88
+ /** Lifecycle. Called at the end of the constructor. Runs on both client and server. */
89
+ onCreate?(...args: any[]): void;
90
+ /** Lifecycle. Called when model/state/props emit `change`. Default: triggers `render`. */
91
+ onChange?(model: object, changed: Record<string, any>, ...args: any[]): void;
92
+ /** Lifecycle. Called after the first render hydrates the DOM. Client only. */
93
+ onHydrate?(): void;
94
+ /** Lifecycle. Called at the start of `recycle`, before any recycling happens. */
95
+ onBeforeRecycle?(): void;
96
+ /** Lifecycle. Called when the component is recycled and its props are updated. */
97
+ onRecycle?(): void;
98
+ /** Lifecycle. Called at the start of `render` on update. */
99
+ onBeforeUpdate?(): void;
100
+ /** Lifecycle. Called at the end of `render` on update. */
101
+ onUpdate?(): void;
102
+ /** Lifecycle. Called when the component is destroyed. */
103
+ onDestroy?(...args: any[]): void;
104
+ }
105
+
106
+ /**
107
+ * Class returned by `Component.extend`: keeps the statics and constructor signature of the
108
+ * parent class `T`, and adds the members of `O` to the instance type.
109
+ */
110
+ export type ExtendedComponent<T extends new (...args: any[]) => any, O> =
111
+ Omit<T, never> & (new (...args: ConstructorParameters<T>) => InstanceType<T> & O);
79
112
 
80
113
  /**
81
114
  * Components are a special kind of `View` designed to be easily composable. Unlike views,
@@ -104,14 +137,23 @@ declare class Component<P = {}, S = any, M = any> extends View<M> {
104
137
 
105
138
  /**
106
139
  * Helper method used to extend a `Component`, creating a subclass.
140
+ *
141
+ * The members of `object` are added to the resulting instance type, and `this` inside
142
+ * its methods is typed as the extended component. Lifecycle overrides (`onCreate`,
143
+ * `onChange`, ...) get their parameters typed automatically.
144
+ *
107
145
  * @param object Object with methods to add to the subclass, or a function that receives
108
146
  * the parent prototype and returns such an object.
109
147
  * @return The newly created Component subclass.
110
148
  */
111
- static extend<T extends new (...args: any[]) => Component<any, any, any>>(
149
+ static extend<T extends new (...args: any[]) => Component<any, any, any>, O extends object>(
150
+ this: T,
151
+ object: (proto: InstanceType<T>) => O & ThisType<InstanceType<T> & O>,
152
+ ): ExtendedComponent<T, O>;
153
+ static extend<T extends new (...args: any[]) => Component<any, any, any>, O extends object>(
112
154
  this: T,
113
- object: object | ((proto: InstanceType<T>) => object),
114
- ): T;
155
+ object: O & ComponentLifecycle & ThisType<InstanceType<T> & O>,
156
+ ): ExtendedComponent<T, O>;
115
157
 
116
158
  /**
117
159
  * Mount the component into the DOM.
@@ -157,6 +199,12 @@ declare class Component<P = {}, S = any, M = any> extends View<M> {
157
199
  ...expressions: any[]
158
200
  ): typeof Component<P, S, M>;
159
201
 
202
+ /**
203
+ * A unique key to identify the component, merged from options.
204
+ * Components with keys are recycled when the same key is found in the previous render.
205
+ */
206
+ key?: string;
207
+
160
208
  /**
161
209
  * Props passed from the parent component, stored as a `Model` for reactive updates.
162
210
  * Accessible directly (`this.props.foo`) or via `Model` API (`this.props.get('foo')`).
@@ -184,7 +232,7 @@ declare class Component<P = {}, S = any, M = any> extends View<M> {
184
232
 
185
233
  /**
186
234
  * Tagged template helper bound to the component instance.
187
- * Returns a `Partial` that preserves structure for position-based recycling.
235
+ * Returns a `ComponentPartial` that preserves structure for position-based recycling.
188
236
  * String literals are marked as safe HTML automatically.
189
237
  *
190
238
  * @example
@@ -192,7 +240,7 @@ declare class Component<P = {}, S = any, M = any> extends View<M> {
192
240
  * return this.partial`<header><${Title}>${this.model.title}</${Title}></header>`;
193
241
  * }
194
242
  */
195
- partial(strings: TemplateStringsArray, ...expressions: any[]): Partial;
243
+ partial(strings: TemplateStringsArray, ...expressions: any[]): ComponentPartial;
196
244
 
197
245
  /**
198
246
  * Subscribes to a `change` event on a model or emitter and invokes `onChange`.
package/types/View.d.ts CHANGED
@@ -37,6 +37,14 @@ export interface ViewOptions<M = any> {
37
37
  * }
38
38
  */
39
39
  export default class View<M = any> extends Emitter {
40
+ /**
41
+ * Counter for generating unique IDs for view instances.
42
+ * For server-side rendering, reset it to `0` on every request (see `resetUid`) so the
43
+ * generated IDs match those on the client, enabling seamless hydration of components.
44
+ * @default 0
45
+ */
46
+ static uid: number;
47
+
40
48
  /**
41
49
  * Escape HTML entities in a string.
42
50
  * Use this method to sanitize user-generated content before inserting it into the DOM.
@@ -74,8 +82,8 @@ export default class View<M = any> extends Emitter {
74
82
  /** Child views. Destroyed automatically when the parent is destroyed. */
75
83
  children: View[];
76
84
 
77
- /** Whether `destroy()` has been called on this view. */
78
- destroyed: boolean;
85
+ /** Whether `destroy()` has been called on this view. `undefined` until then. */
86
+ destroyed?: boolean;
79
87
 
80
88
  /**
81
89
  * Functions run on `destroy()`. Push cleanup callbacks here for external subscriptions
@@ -124,7 +132,7 @@ export default class View<M = any> extends Emitter {
124
132
  * Add a view as a child. Children are stored in `this.children` and destroyed when the parent is destroyed.
125
133
  * @return The child view for chaining.
126
134
  */
127
- addChild(child: View): View;
135
+ addChild<C extends View>(child: C): C;
128
136
 
129
137
  /** Call `destroy()` on children views. */
130
138
  destroyChildren(): void;
package/types/index.d.ts CHANGED
@@ -6,10 +6,12 @@ export {
6
6
  ComponentOptions,
7
7
  ComponentReservedOptions,
8
8
  SafeHTML,
9
- Partial,
9
+ ComponentPartial,
10
10
  EventHandler,
11
11
  RenderExpression,
12
12
  Props,
13
13
  State,
14
14
  ComponentModel,
15
+ ComponentLifecycle,
16
+ ExtendedComponent,
15
17
  } from './Component.js';