@manyducks.co/dolla 2.0.0 β†’ 3.0.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.
Files changed (112) hide show
  1. package/README.md +133 -284
  2. package/dist/core/context.d.ts +22 -146
  3. package/dist/core/debug.d.ts +19 -0
  4. package/dist/core/index.d.ts +15 -16
  5. package/dist/core/markup/helpers.d.ts +34 -0
  6. package/dist/core/markup/html.d.ts +3 -0
  7. package/dist/core/{nodes β†’ markup/nodes}/dom.d.ts +5 -4
  8. package/dist/core/markup/nodes/dynamic.d.ts +16 -0
  9. package/dist/core/markup/nodes/element.d.ts +14 -0
  10. package/dist/core/markup/nodes/portal.d.ts +15 -0
  11. package/dist/core/markup/nodes/repeat.d.ts +21 -0
  12. package/dist/core/markup/nodes/view.d.ts +17 -0
  13. package/dist/core/markup/scheduler.d.ts +1 -0
  14. package/dist/core/markup/types.d.ts +62 -0
  15. package/dist/core/markup/utils.d.ts +22 -0
  16. package/dist/core/ref.d.ts +6 -12
  17. package/dist/core/root.d.ts +36 -0
  18. package/dist/core/signals.d.ts +46 -76
  19. package/dist/core/symbols.d.ts +2 -0
  20. package/dist/core-BLkJ-xuh.js +242 -0
  21. package/dist/core-BLkJ-xuh.js.map +1 -0
  22. package/dist/http/index.d.ts +21 -33
  23. package/dist/http.js +89 -149
  24. package/dist/http.js.map +1 -1
  25. package/dist/index.js +4 -174
  26. package/dist/jsx-dev-runtime.d.ts +4 -3
  27. package/dist/jsx-dev-runtime.js +12 -9
  28. package/dist/jsx-dev-runtime.js.map +1 -1
  29. package/dist/jsx-runtime.d.ts +5 -4
  30. package/dist/jsx-runtime.js +17 -12
  31. package/dist/jsx-runtime.js.map +1 -1
  32. package/dist/router/index.d.ts +4 -3
  33. package/dist/router/router.d.ts +19 -162
  34. package/dist/router/store.d.ts +12 -0
  35. package/dist/router/types.d.ts +152 -0
  36. package/dist/router/utils.d.ts +99 -0
  37. package/dist/router/utils.test.d.ts +1 -0
  38. package/dist/router.js +428 -5
  39. package/dist/router.js.map +1 -1
  40. package/dist/signals-CMJPGr_M.js +354 -0
  41. package/dist/signals-CMJPGr_M.js.map +1 -0
  42. package/dist/translate/index.d.ts +82 -0
  43. package/dist/translate.js +125 -0
  44. package/dist/translate.js.map +1 -0
  45. package/dist/types.d.ts +21 -39
  46. package/dist/utils.d.ts +41 -29
  47. package/dist/utils.test.d.ts +1 -0
  48. package/dist/view-cBN-hn_T.js +360 -0
  49. package/dist/view-cBN-hn_T.js.map +1 -0
  50. package/dist/virtual/index.d.ts +1 -0
  51. package/dist/virtual/list.d.ts +53 -0
  52. package/package.json +19 -16
  53. package/dist/core/app.d.ts +0 -24
  54. package/dist/core/env.d.ts +0 -3
  55. package/dist/core/hooks.d.ts +0 -70
  56. package/dist/core/logger.d.ts +0 -42
  57. package/dist/core/logger.test.d.ts +0 -0
  58. package/dist/core/markup.d.ts +0 -82
  59. package/dist/core/markup.test.d.ts +0 -0
  60. package/dist/core/nodes/_markup.d.ts +0 -36
  61. package/dist/core/nodes/dynamic.d.ts +0 -22
  62. package/dist/core/nodes/element.d.ts +0 -27
  63. package/dist/core/nodes/portal.d.ts +0 -18
  64. package/dist/core/nodes/repeat.d.ts +0 -27
  65. package/dist/core/nodes/view.d.ts +0 -25
  66. package/dist/core/views/default-crash-view.d.ts +0 -25
  67. package/dist/core/views/for.d.ts +0 -21
  68. package/dist/core/views/fragment.d.ts +0 -7
  69. package/dist/core/views/portal.d.ts +0 -16
  70. package/dist/core/views/show.d.ts +0 -25
  71. package/dist/fragment-BahD_BJA.js +0 -7
  72. package/dist/fragment-BahD_BJA.js.map +0 -1
  73. package/dist/i18n/index.d.ts +0 -134
  74. package/dist/i18n.js +0 -309
  75. package/dist/i18n.js.map +0 -1
  76. package/dist/index-DRJlxs-Q.js +0 -535
  77. package/dist/index-DRJlxs-Q.js.map +0 -1
  78. package/dist/index.js.map +0 -1
  79. package/dist/logger-Aqi9m1CF.js +0 -565
  80. package/dist/logger-Aqi9m1CF.js.map +0 -1
  81. package/dist/markup-8jNhoqDe.js +0 -1089
  82. package/dist/markup-8jNhoqDe.js.map +0 -1
  83. package/dist/router/hooks.d.ts +0 -2
  84. package/dist/router/router.utils.d.ts +0 -93
  85. package/dist/typeChecking-5kmX0ulW.js +0 -65
  86. package/dist/typeChecking-5kmX0ulW.js.map +0 -1
  87. package/dist/typeChecking.d.ts +0 -95
  88. package/docs/buildless.md +0 -132
  89. package/docs/components.md +0 -238
  90. package/docs/hooks.md +0 -356
  91. package/docs/http.md +0 -178
  92. package/docs/i18n.md +0 -220
  93. package/docs/index.md +0 -10
  94. package/docs/markup.md +0 -136
  95. package/docs/mixins.md +0 -176
  96. package/docs/ref.md +0 -77
  97. package/docs/router.md +0 -281
  98. package/docs/setup.md +0 -137
  99. package/docs/signals.md +0 -262
  100. package/docs/stores.md +0 -113
  101. package/docs/views.md +0 -356
  102. package/notes/atomic.md +0 -452
  103. package/notes/elimination.md +0 -33
  104. package/notes/observable.md +0 -180
  105. package/notes/scratch.md +0 -565
  106. package/notes/splitting.md +0 -5
  107. package/notes/views.md +0 -195
  108. package/vite.config.js +0 -22
  109. /package/dist/core/{hooks.test.d.ts β†’ markup/html.test.d.ts} +0 -0
  110. /package/dist/core/{ref.test.d.ts β†’ markup/utils.test.d.ts} +0 -0
  111. /package/dist/router/{router.utils.test.d.ts β†’ matcher.test.d.ts} +0 -0
  112. /package/dist/{typeChecking.test.d.ts β†’ router/router.test.d.ts} +0 -0
package/docs/http.md DELETED
@@ -1,178 +0,0 @@
1
- # The HTTP Client
2
-
3
- Every app needs to talk to a server at some point, and while you _could_ just use the browser's built-in `fetch`, it's kinda basic. You end up writing the same boilerplate code over and over again for things like setting headers and parsing JSON. It's a vibe killer.
4
-
5
- That's why Dolla comes with its own `http` client right out of the box. It's a smart wrapper around `fetch` that makes your life way easier.
6
-
7
- **Why you'll actually wanna use it:**
8
-
9
- - **Automatic JSON:** It automatically stringifies your request bodies to JSON and parses JSON responses. No more `.then(res => res.json())` chains\!
10
- - **Middleware:** This is the main event. You can set up "middleware" functions that run on _every single request_. This is clutch for adding auth tokens, logging, or handling errors in one central place.
11
- - **Clean API:** It's just simple. `http.get('/users')` is way cleaner than a big `fetch` call.
12
- - **Smart Error Handling:** It automatically throws an error for bad responses (like 404s or 500s), so you don't have to check `res.ok` yourself.
13
-
14
- ## Basic Requests
15
-
16
- The `http` object is a global singleton, so you just import it and go. It has methods for all the common HTTP verbs.
17
-
18
- ```jsx
19
- import { http } from "@manyducks.co/dolla/http";
20
- import { useSignal, useMount } from "@manyducks.co/dolla";
21
-
22
- function UserList() {
23
- const [$users, setUsers] = useSignal([]);
24
-
25
- useMount(async () => {
26
- // Just call the method you need!
27
- const res = await http.get("/api/users");
28
- // The body is already parsed JSON!
29
- setUsers(res.body);
30
- });
31
-
32
- return (
33
- <ul>
34
- <For each={$users}>{(user) => <li>{() => user().name}</li>}</For>
35
- </ul>
36
- );
37
- }
38
- ```
39
-
40
- Here are the main methods you'll use:
41
-
42
- - `http.get(uri, options?)`
43
- - `http.post(uri, options?)`
44
- - `http.put(uri, options?)`
45
- - `http.patch(uri, options?)`
46
- - `http.delete(uri, options?)`
47
-
48
- ## Passing Options to a Request
49
-
50
- For anything more than a simple GET request, you'll need to pass an `options` object. This is where you put your request body, headers, and query params.
51
-
52
- ### Sending a `body`
53
-
54
- When you use `post`, `put`, or `patch`, you'll probably wanna send some data. Just put your JavaScript object in the `body` property. Dolla will automatically set the `Content-Type` to `application/json` and `JSON.stringify` it for you.
55
-
56
- ```jsx
57
- const handleCreateUser = async () => {
58
- const newUser = { name: "Alice", email: "alice@example.com" };
59
-
60
- try {
61
- const res = await http.post("/api/users", {
62
- body: newUser,
63
- });
64
- console.log("User created!", res.body);
65
- } catch (error) {
66
- console.error("Failed to create user:", error);
67
- }
68
- };
69
- ```
70
-
71
- ### Adding `headers`
72
-
73
- You can pass a plain object for your headers.
74
-
75
- ```jsx
76
- const res = await http.get("/api/some-protected-route", {
77
- headers: {
78
- "X-Custom-Header": "MyValue",
79
- },
80
- });
81
- ```
82
-
83
- ### Adding `query` Params
84
-
85
- Don't mess with building query strings by hand. Just pass an object to the `query` property and Dolla will build the URL for you.
86
-
87
- ```jsx
88
- // This will make a request to /api/users?sort=name&limit=10
89
- const res = await http.get("/api/users", {
90
- query: {
91
- sort: "name",
92
- limit: 10,
93
- },
94
- });
95
- ```
96
-
97
- ## Middleware: The Secret Sauce
98
-
99
- This is the best part. Middleware is a function that can intercept _every single request_ before it goes out. You can inspect it, change it, or even stop it. It's perfect for stuff you need to do all the time.
100
-
101
- You add middleware with `http.use(myMiddleware)`.
102
-
103
- ### Example: The Auth Header
104
-
105
- This is the classic use case. You need to add a `Bearer` token to every request that goes to your API. Instead of adding it to every single call, you just set up a middleware once.
106
-
107
- ```jsx
108
- import { http } from "@manyducks.co/dolla/http";
109
-
110
- // This middleware will run for EVERY http call
111
- http.use(async (req, next) => {
112
- // Check if the request is going to our API
113
- if (req.url.pathname.startsWith("/api/")) {
114
- const token = localStorage.getItem("auth_token");
115
- if (token) {
116
- // Add the auth header!
117
- req.headers.set("Authorization", `Bearer ${token}`);
118
- }
119
- }
120
-
121
- // This part is super important! You HAVE to call next()
122
- // to let the request continue on its way.
123
- await next();
124
- });
125
-
126
- // Now, this request will automatically have the auth header.
127
- const profile = await http.get("/api/me");
128
- ```
129
-
130
- The `http.use()` function also returns a function that you can call to **remove** that specific middleware later if you need to.
131
-
132
- ## Handling Responses
133
-
134
- When a request is successful, you get a response object back with all the info you need:
135
-
136
- - `res.body`: The response body, already parsed for you (usually as JSON).
137
- - `res.status`: The HTTP status code (e.g., `200`).
138
- - `res.statusText`: The status text (e.g., `"OK"`).
139
- - `res.headers`: A `Headers` object with all the response headers.
140
- - `res.url`: The final `URL` object of the request.
141
- - `res.method`: The HTTP method that was used.
142
-
143
- ## Error Handling
144
-
145
- Dolla's HTTP client makes error handling way easier. If a request gets a response with a status code in the 400s or 500s (like a `404 Not Found` or `500 Server Error`), it will automatically **throw an error**.
146
-
147
- This means you can use a standard `try...catch` block to handle both network failures and bad HTTP responses in the same place.
148
-
149
- The error it throws is a special `HTTPResponseError`. The cool part is that the error object itself has a `.response` property, so you can still inspect the full response body, headers, and status, even on a failed request.
150
-
151
- ```jsx
152
- const fetchUserData = async (userId) => {
153
- try {
154
- const res = await http.get(`/api/users/${userId}`);
155
- console.log("Got user:", res.body);
156
- } catch (error) {
157
- // Check if it's an error from the server
158
- if (error instanceof HTTPResponseError) {
159
- console.error(`Server responded with ${error.response.status}`);
160
- console.error("Response body:", error.response.body); // You can still read the error body!
161
- } else {
162
- // It was probably a network error or something else
163
- console.error("An unexpected error occurred:", error.message);
164
- }
165
- }
166
- };
167
-
168
- fetchUserData(999); // Let's pretend this user doesn't exist
169
- // Console will log: "Server responded with 404"
170
- ```
171
-
172
- ---
173
-
174
- End.
175
-
176
- - [πŸ—‚οΈ Docs](./index.md)
177
- - [🏠 README](../README.md)
178
- - [πŸ¦† That's a lot of ducks.](https://www.manyducks.co)
package/docs/i18n.md DELETED
@@ -1,220 +0,0 @@
1
- # Dolla's i18n (aka making your app speak other languages)
2
-
3
- Aight, so your app is probably in English by default. But what if you wanna go global? That's where internationalization (or "i18n" if you're lazy) comes in. It's the whole process of making your app work in different languages.
4
-
5
- You _could_ just use a giant object and some `if` statements, but that's a one-way ticket to spaghetti code city. It's a whole vibe killer.
6
-
7
- That's why Dolla comes with its own i18n system right out of the box.
8
-
9
- **Why it's lowkey the GOAT:**
10
-
11
- - **It's Reactive AF:** The main `t()` function you use to get text returns a **signal**. That means if the user switches languages, your entire UI just updates. Automatically. No cap, it's kinda magic.
12
- - **Smart String Templates:** You can put variables, format numbers, handle plurals, and all that jazz right in your translation strings. It's super powerful.
13
- - **Load Translations How You Want:** You can hardcode them, load them from a JSON file, or even fetch them from an API. You do you.
14
- - **Batteries Included:** It comes with formatters for numbers, dates, and lists, all using the browser's smart `Intl` stuff so it's always correct for whatever language you're in.
15
-
16
- ## Setting It Up
17
-
18
- First things first, you gotta tell Dolla about the languages you support. You do this once when your app starts up, using `i18n.setup()`. It's super important to do this _before_ you mount your app so all the text is ready to go.
19
-
20
- ```jsx
21
- import { createApp } from "@manyducks.co/dolla";
22
- import { i18n } from "@manyducks.co/dolla/i18n";
23
- import { App } from "./App.jsx";
24
-
25
- const app = createApp(App);
26
-
27
- // Set up all your languages
28
- i18n
29
- .setup({
30
- // Tell it what language to start with. 'auto' is smart and will try to match the user's browser language.
31
- locale: "auto",
32
- translations: [
33
- // You can define strings right here...
34
- {
35
- locale: "en",
36
- strings: {
37
- greeting: "Hello, world!",
38
- nav: {
39
- home: "Home",
40
- about: "About Us",
41
- },
42
- },
43
- },
44
- // ...or tell it to fetch a JSON file for another language.
45
- {
46
- locale: "es",
47
- path: "/translations/es.json",
48
- },
49
- // ...or even give it a function that fetches them from an API!
50
- {
51
- locale: "ja",
52
- fetch: () => fetch("/api/translations/ja").then((res) => res.json()),
53
- },
54
- ],
55
- })
56
- .then(() => {
57
- // Only mount the app AFTER the translations are loaded!
58
- app.mount(document.body);
59
- });
60
- ```
61
-
62
- ## The `t()` function: Your New Bestie
63
-
64
- This is the main function you'll be using. You import `t` and call it with a key to get the translated string.
65
-
66
- ```jsx
67
- import { t } from "@manyducks.co/dolla/i18n";
68
-
69
- function Greeting() {
70
- // This returns a signal, so it will automatically update if the language changes!
71
- return <h1>{t("greeting")}</h1>;
72
- }
73
-
74
- function Navbar() {
75
- return (
76
- <nav>
77
- <a href="/">{t("nav.home")}</a>
78
- <a href="/about">{t("nav.about")}</a>
79
- </nav>
80
- );
81
- }
82
- ```
83
-
84
- If a translation is missing, it won't crash your app. It'll just show something like `[MISSING: your.key]`, so you know you forgot something.
85
-
86
- ## Smart Strings: Variables and Formatting
87
-
88
- This is where the magic happens. You can put placeholders in your strings and pass values to the `t()` function.
89
-
90
- ### Basic Variables
91
-
92
- Just wrap your variable name in double curly braces: `{{variableName}}`. The best part? You can pass signals as values, and the text will update automatically when the signal changes\!
93
-
94
- ```jsx
95
- // In your translation file:
96
- {
97
- "welcome_message": "Welcome back, {{username}}!"
98
- }
99
-
100
- // In your component:
101
- const [$username, setUsername] = useSignal("Alice");
102
-
103
- // Because you're passing a signal, this will be reactive!
104
- const $welcomeText = t("welcome_message", { username: $username });
105
-
106
- // $welcomeText() is "Welcome back, Alice!"
107
- // If you call setUsername("Bob"), $welcomeText() automatically becomes "Welcome back, Bob!"
108
- ```
109
-
110
- ### Formatting Numbers, Dates, and More\!
111
-
112
- You can add formatters to your variables using a pipe `|`. Dolla comes with a few built-in ones.
113
-
114
- - **`number`**: For formatting numbers (prices, percentages, etc.).
115
- - **`datetime`**: For formatting dates and times.
116
- - **`list`**: For formatting a list of things with commas and "and".
117
-
118
- These formatters take options that are the same as the browser's `Intl` APIs, so they're super powerful. And yes, they're fully reactive with signals too.
119
-
120
- ```jsx
121
- // In your translation file:
122
- {
123
- "product_price": "Price: {{price | number(style: currency, currency: USD)}}"
124
- }
125
-
126
- // In your component:
127
- const [$price, setPrice] = useSignal(49.99);
128
-
129
- // This text will update automatically if you call setPrice() later!
130
- const $priceText = t("product_price", { price: $price });
131
- ```
132
-
133
- ### Custom Formatters
134
-
135
- You can even make your own formatters with `i18n.addFormat()`.
136
-
137
- ```js
138
- // Set this up once when your app starts
139
- i18n.addFormat("uppercase", (locale, value) => {
140
- return String(value).toUpperCase();
141
- });
142
-
143
- // In your translation file:
144
- { "shout": "HEY, {{name | uppercase}}!" }
145
-
146
- // In your component:
147
- t("shout", { name: "Bob" }); // Renders: "HEY, BOB!"
148
- ```
149
-
150
- ## Plurals and Context: Next-Level i18n
151
-
152
- Languages are weird, especially with plurals. Dolla's i18n system has your back.
153
-
154
- ### Pluralization (`count`)
155
-
156
- You can create different versions of a string based on a `count` you pass in. Dolla uses the standard pluralization rules for whatever language you're in (`one`, `other`, `few`, `many`, etc.). And you guessed itβ€”if you pass a signal for the count, the string will automatically switch between singular and plural\!
157
-
158
- Just add `_one`, `_other`, etc., to your keys.
159
-
160
- ```jsx
161
- // In your translation file:
162
- {
163
- "message_count_one": "You have 1 new message.",
164
- "message_count_other": "You have {{count}} new messages."
165
- }
166
-
167
- // In your component:
168
- const [$messageCount, setMessageCount] = useSignal(1);
169
-
170
- // This will automatically switch between the singular and plural versions!
171
- const $messageText = t("message_count", { count: $messageCount });
172
-
173
- // $messageText() is "You have 1 new message."
174
- // after setMessageCount(5), $messageText() becomes "You have 5 new messages."
175
- ```
176
-
177
- ### Context (`context`)
178
-
179
- Sometimes you need a different translation for the same word based on the situation. For example, a "profile" button might be different for a user vs. a company.
180
-
181
- ```js
182
- // In your translation file:
183
- {
184
- "edit_profile": "Edit Profile",
185
- "edit_profile_company": "Edit Company Profile"
186
- }
187
-
188
- // In your component:
189
- const userType = "company";
190
- t("edit_profile", { context: userType }); // Renders: "Edit Company Profile"
191
- t("edit_profile"); // Renders: "Edit Profile"
192
- ```
193
-
194
- ## Using Formatters Directly
195
-
196
- Sometimes you just wanna format a number or a date without a full translation string. The `i18n` object has helper functions for that too, and they're also reactive\!
197
-
198
- ```jsx
199
- import { i18n } from "@manyducks.co/dolla/i18n";
200
-
201
- function ProductDetails({ product }) {
202
- const $price = i18n.number(() => product().price, { style: "currency", currency: "USD" });
203
- const $lastUpdated = i18n.dateTime(() => product().updatedAt, { dateStyle: "long" });
204
-
205
- return (
206
- <div>
207
- <p>Price: {$price}</p>
208
- <p>Last Updated: {$lastUpdated}</p>
209
- </div>
210
- );
211
- }
212
- ```
213
-
214
- ---
215
-
216
- End.
217
-
218
- - [πŸ—‚οΈ Docs](./index.md)
219
- - [🏠 README](../README.md)
220
- - [πŸ¦† That's a lot of ducks.](https://www.manyducks.co)
package/docs/index.md DELETED
@@ -1,10 +0,0 @@
1
- # Dolla Docs
2
-
3
- > Write table of contents (see files in this folder in the meantime)
4
-
5
- ---
6
-
7
- End.
8
-
9
- - [🏠 README](../README.md)
10
- - [πŸ¦† That's a lot of ducks.](https://www.manyducks.co)
package/docs/markup.md DELETED
@@ -1,136 +0,0 @@
1
- # Dolla Internals: What the Heck is Markup?
2
-
3
- Aight, so you've been writing Views and they spit out JSX and everything just magically shows up on the page. But what's _actually_ happening under the hood? If you're the kind of dev who likes to pop the hood and see how the engine works, this doc is for you.
4
-
5
- We're gonna talk about **Markup**. This is some deep-level Dolla lore, so buckle up.
6
-
7
- ## So, what even IS Markup?
8
-
9
- When you write this in your View:
10
-
11
- ```jsx
12
- <div>Hello, {$name}!</div>
13
- ```
14
-
15
- That JSX doesn't just become HTML. Before it ever touches the DOM, it gets turned into a tree of JavaScript objects. We call these objects **Markup Nodes**. Think of it like Dolla's own private version of the DOM, but way smarter and built with reactivity in mind from the ground up.
16
-
17
- This tree of Markup Nodes is what Dolla actually uses to create, update, and destroy the real HTML elements on your page.
18
-
19
- ### The `MarkupNode` Abstract Class
20
-
21
- At the very bottom of it all is the `MarkupNode` abstract class. Every single thing in the Markup tree, from a simple piece of text to a whole component, is a class that extends `MarkupNode`. It's the blueprint that guarantees every node knows how to do a few basic things:
22
-
23
- - `mount(parent, after?)`: Put me on the page.
24
- - `unmount(skipDOM?)`: Take me off the page.
25
- - `move(parent, after?)`: Move me without a full unmount/remount.
26
- - `getRoot()`: Give me my main HTML element.
27
- - `isMounted()`: Am I currently on the page?
28
-
29
- Understanding this is key: your entire app is just a tree of these objects calling these methods on each other.
30
-
31
- ## Why should I care?
32
-
33
- Honestly? 99% of the time, you don't need to. This is all internal stuff. But understanding it can help you debug weird issues and really get why Dolla is so fast. It's also just kinda cool to know how your tools work, fr.
34
-
35
- ## The Different Flavors of Markup Nodes
36
-
37
- There are a few different types of nodes in the Markup tree, each with a specific job.
38
-
39
- ### `ElementNode`
40
-
41
- This is the main character. Every time you write a `<div>`, `<p>`, or `<button>`, Dolla creates an `ElementNode`. This is the object that holds onto the real HTML element and is responsible for all the cool stuff you can do with props.
42
-
43
- ### `DOMNode`
44
-
45
- This is the simplest one. It's a lightweight wrapper for any plain ol' DOM node. This includes simple text\! When you write `<div>Hello</div>`, the word "Hello" becomes a native browser `Text` node, which then gets wrapped in a Dolla `DOMNode`. It's the leaf at the very bottom of the tree.
46
-
47
- ### `DynamicNode`
48
-
49
- This is the secret sauce for reactivity. When you put a signal directly in your JSX, like `<p>{$count}</p>`, Dolla wraps it in a `DynamicNode`. Its job is to listen to the signal and swap out its content whenever the signal's value changes.
50
-
51
- ### `RepeatNode`
52
-
53
- This is the powerhouse behind the `<For>` component. It's way smarter than a simple loop and uses a key-based diffing algorithm to make the absolute minimum number of changes to the DOM when the list updates.
54
-
55
- ### `ViewNode`
56
-
57
- When you use one of your own components, like `<MyComponent />`, Dolla creates a `ViewNode`. Its job is to call your component's function, set up a new `Context` for it, and then manage the tree of Markup Nodes that your component returns.
58
-
59
- ### `PortalNode`
60
-
61
- This is the node for the `<Portal>` component. It's special because it mounts its children to a completely different part of the DOM, and it has to handle its own cleanup.
62
-
63
- Here's the tea: normally, when a parent element gets yeeted off the page, all its kids go with it automatically. Dolla uses this for a speed boostβ€”it tells the child nodes "don't worry about removing yourselves, I got this." But a Portal's content is living its best life somewhere else in the DOM, like at the end of `<body>`. So when the Portal's _logical_ parent gets removed, its content is left stranded. That's why the `PortalNode` has to do its own cleanup and manually remove its content from wherever it was teleported to. It can't rely on the parent's cleanup crew.
64
-
65
- ## The Context Tree: The Brains of the Operation
66
-
67
- So how does all this stuff actually connect? The secret is the **Context Tree**.
68
-
69
- Think of it like this: when Dolla builds your Markup tree, it builds a second, invisible tree right alongside it. Not every node gets its own context, though. Only **`ViewNode`s** and **`ElementNode`s** create a new child context. The other node types, like `DynamicNode` or `RepeatNode`, just hitch a ride on their parent's context.
70
-
71
- This parallel tree is the entire brain of a Dolla component.
72
-
73
- ### How `useStore` and `useContext` Work
74
-
75
- Each context object has a link to its parent. This is the whole magic trick.
76
-
77
- When you call `useStore(MyStore)`, Dolla is like, "Aight, lemme check the _current_ context for this store... nope. Lemme ask my parent... nope. Lemme ask _their_ parent..." It just keeps climbing up this context tree until it finds the store or hits the top.
78
-
79
- And how do hooks like `useContext` know which context they're in? When a `ViewNode` is about to run your component function, it basically shouts "YO, I'M THE ACTIVE CONTEXT NOW\!" into a global, private variable. Then your function runs, all your hooks grab that global context, and when your function is done, the `ViewNode` sets it back to what it was before. It's a clever little trick that makes hooks feel like magic.
80
-
81
- ### The Lifecycle Connection
82
-
83
- The `Context` object is also a tiny state machine that moves through lifecycle states:
84
-
85
- `Unmounted` -\> `WillMount` -\> `DidMount` -\> `WillUnmount` -\> `DidUnmount` -\> `Disposed`
86
-
87
- When a `MarkupNode`'s `mount()` method is called, it's actually just telling its own `Context` object to emit the `WillMount` event, then it does its DOM stuff, then it tells the context to emit `DidMount`. The `useMount` hook you use in your component is really just a simple wrapper for `context.onLifecycleTransition("didMount", callback)`.
88
-
89
- **What's cool for a power user:**
90
-
91
- - **Bound Lifecycles:** When you create a Store or a Mixin, its context is "bound" to the parent View's context. This means when the View's context gets the `DidMount` event, the Store's context automatically gets it too. That's how hooks inside Stores and Mixins just work without any extra setup.
92
- - **Smart Effects:** The `useEffect` hook has to be called in the main body of your component function. It tells the context to queue up the effect to run at `WillMount`. For advanced use cases, you can use the lower-level `context.effect()` method from inside a lifecycle hook (like `useMount`), and the context is smart enough to run it immediately since the component is already mounted.
93
-
94
- ## Putting It All Together
95
-
96
- So, when you write a View like this:
97
-
98
- ```jsx
99
- function MyView() {
100
- const [$users, setUsers] = useSignal([{ id: 1, name: "Alice" }]);
101
-
102
- return (
103
- <div class="container">
104
- <h1>Users</h1>
105
- <For each={$users} key={(u) => u.id}>
106
- {($user) => <p>{() => $user().name}</p>}
107
- </For>
108
- </div>
109
- );
110
- }
111
- ```
112
-
113
- Here's a simplified version of the Markup tree Dolla builds internally. Remember, each of these nodes has its own `Context` object, linked to its parent's.
114
-
115
- ```
116
- ElementNode(div)
117
- - ElementNode(h1)
118
- - DOMNode(Text("Users"))
119
- - RepeatNode(for the $users signal)
120
- - ViewNode(for the <p> component instance)
121
- - ElementNode(p)
122
- - DynamicNode(for the user's name)
123
- - DOMNode(Text("Alice"))
124
- ```
125
-
126
- When you add a new user to the `$users` signal, only the `RepeatNode` gets notified. It then creates a new `ViewNode` for the new user, which in turn creates its own little tree (and its own child context), and mounts it to the DOM. Nothing else on the page has to re-render.
127
-
128
- That's the power of the Markup tree. It's a fully reactive, fine-grained representation of your UI that allows for incredibly efficient updates. And now you know what's really going on under the hood.
129
-
130
- ---
131
-
132
- End.
133
-
134
- - [πŸ—‚οΈ Docs](./index.md)
135
- - [🏠 README](../README.md)
136
- - [πŸ¦† That's a lot of ducks.](https://www.manyducks.co)