@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.
- package/README.md +133 -284
- package/dist/core/context.d.ts +22 -146
- package/dist/core/debug.d.ts +19 -0
- package/dist/core/index.d.ts +15 -16
- package/dist/core/markup/helpers.d.ts +34 -0
- package/dist/core/markup/html.d.ts +3 -0
- package/dist/core/{nodes → markup/nodes}/dom.d.ts +5 -4
- package/dist/core/markup/nodes/dynamic.d.ts +16 -0
- package/dist/core/markup/nodes/element.d.ts +14 -0
- package/dist/core/markup/nodes/portal.d.ts +15 -0
- package/dist/core/markup/nodes/repeat.d.ts +21 -0
- package/dist/core/markup/nodes/view.d.ts +17 -0
- package/dist/core/markup/scheduler.d.ts +1 -0
- package/dist/core/markup/types.d.ts +62 -0
- package/dist/core/markup/utils.d.ts +22 -0
- package/dist/core/ref.d.ts +6 -12
- package/dist/core/root.d.ts +36 -0
- package/dist/core/signals.d.ts +46 -76
- package/dist/core/symbols.d.ts +2 -0
- package/dist/core-BLkJ-xuh.js +242 -0
- package/dist/core-BLkJ-xuh.js.map +1 -0
- package/dist/http/index.d.ts +21 -33
- package/dist/http.js +89 -149
- package/dist/http.js.map +1 -1
- package/dist/index.js +4 -174
- package/dist/jsx-dev-runtime.d.ts +4 -3
- package/dist/jsx-dev-runtime.js +12 -9
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +5 -4
- package/dist/jsx-runtime.js +17 -12
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/router/index.d.ts +4 -3
- package/dist/router/router.d.ts +19 -162
- package/dist/router/store.d.ts +12 -0
- package/dist/router/types.d.ts +152 -0
- package/dist/router/utils.d.ts +99 -0
- package/dist/router/utils.test.d.ts +1 -0
- package/dist/router.js +428 -5
- package/dist/router.js.map +1 -1
- package/dist/signals-CMJPGr_M.js +354 -0
- package/dist/signals-CMJPGr_M.js.map +1 -0
- package/dist/translate/index.d.ts +82 -0
- package/dist/translate.js +125 -0
- package/dist/translate.js.map +1 -0
- package/dist/types.d.ts +21 -39
- package/dist/utils.d.ts +41 -29
- package/dist/utils.test.d.ts +1 -0
- package/dist/view-cBN-hn_T.js +360 -0
- package/dist/view-cBN-hn_T.js.map +1 -0
- package/dist/virtual/index.d.ts +1 -0
- package/dist/virtual/list.d.ts +53 -0
- package/package.json +19 -16
- package/dist/core/app.d.ts +0 -24
- package/dist/core/env.d.ts +0 -3
- package/dist/core/hooks.d.ts +0 -70
- package/dist/core/logger.d.ts +0 -42
- package/dist/core/logger.test.d.ts +0 -0
- package/dist/core/markup.d.ts +0 -82
- package/dist/core/markup.test.d.ts +0 -0
- package/dist/core/nodes/_markup.d.ts +0 -36
- package/dist/core/nodes/dynamic.d.ts +0 -22
- package/dist/core/nodes/element.d.ts +0 -27
- package/dist/core/nodes/portal.d.ts +0 -18
- package/dist/core/nodes/repeat.d.ts +0 -27
- package/dist/core/nodes/view.d.ts +0 -25
- package/dist/core/views/default-crash-view.d.ts +0 -25
- package/dist/core/views/for.d.ts +0 -21
- package/dist/core/views/fragment.d.ts +0 -7
- package/dist/core/views/portal.d.ts +0 -16
- package/dist/core/views/show.d.ts +0 -25
- package/dist/fragment-BahD_BJA.js +0 -7
- package/dist/fragment-BahD_BJA.js.map +0 -1
- package/dist/i18n/index.d.ts +0 -134
- package/dist/i18n.js +0 -309
- package/dist/i18n.js.map +0 -1
- package/dist/index-DRJlxs-Q.js +0 -535
- package/dist/index-DRJlxs-Q.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger-Aqi9m1CF.js +0 -565
- package/dist/logger-Aqi9m1CF.js.map +0 -1
- package/dist/markup-8jNhoqDe.js +0 -1089
- package/dist/markup-8jNhoqDe.js.map +0 -1
- package/dist/router/hooks.d.ts +0 -2
- package/dist/router/router.utils.d.ts +0 -93
- package/dist/typeChecking-5kmX0ulW.js +0 -65
- package/dist/typeChecking-5kmX0ulW.js.map +0 -1
- package/dist/typeChecking.d.ts +0 -95
- package/docs/buildless.md +0 -132
- package/docs/components.md +0 -238
- package/docs/hooks.md +0 -356
- package/docs/http.md +0 -178
- package/docs/i18n.md +0 -220
- package/docs/index.md +0 -10
- package/docs/markup.md +0 -136
- package/docs/mixins.md +0 -176
- package/docs/ref.md +0 -77
- package/docs/router.md +0 -281
- package/docs/setup.md +0 -137
- package/docs/signals.md +0 -262
- package/docs/stores.md +0 -113
- package/docs/views.md +0 -356
- package/notes/atomic.md +0 -452
- package/notes/elimination.md +0 -33
- package/notes/observable.md +0 -180
- package/notes/scratch.md +0 -565
- package/notes/splitting.md +0 -5
- package/notes/views.md +0 -195
- package/vite.config.js +0 -22
- /package/dist/core/{hooks.test.d.ts → markup/html.test.d.ts} +0 -0
- /package/dist/core/{ref.test.d.ts → markup/utils.test.d.ts} +0 -0
- /package/dist/router/{router.utils.test.d.ts → matcher.test.d.ts} +0 -0
- /package/dist/{typeChecking.test.d.ts → router/router.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,151 +1,179 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 💲 @manyducks.co/dolla
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Dolla is a research framework for trying out ideas. The goal is to create a full-featured framework that, first and foremost, provides the best developer experience possible out of the box. Low resource usage and small code size are secondary objectives. It's more than a toy and less than a production-ready workhorse. It's a labor of love. Use at your own joy and peril.
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
- 📦
|
|
10
|
-
- 🖥️
|
|
11
|
-
- 💾
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- 🐕 It comes with its own [**HTTP client**](./docs/http.md) so you can stop installing axios. It's got middleware too, so adding auth headers to every request is easy. We stan.
|
|
16
|
-
- 📍 A lowkey [**i18n system**](./docs/i18n.md). Just yeet your translations into a JSON file and the `t` function pulls them. Simple.
|
|
17
|
-
- 🍳 And the biggest flex? The build system is optional. You can [write your JSX like always](./docs/setup.md), or just [use tagged template literals](./docs/buildless.md) straight in the browser with [HTM](https://github.com/developit/htm). It's a whole vibe.
|
|
8
|
+
- 🚥 [**Signals**](./docs/reactivity.md) for pinpoint DOM updates.
|
|
9
|
+
- 📦 Reusable components in two types:
|
|
10
|
+
- 🖥️ **Views** for reusable UI elements.
|
|
11
|
+
- 💾 **Stores** for sharing state across many views.
|
|
12
|
+
- 🔀 A client-side [**router**](./src/router/README.md) with nested routes, auth guards, async data loading and more.
|
|
13
|
+
- 📍 A simple [**i18n system**](./src/translate/README.md). Put your translated strings in a JSON file and access them with the `t` function in your views.
|
|
14
|
+
- 🍳 The build step is optional. You can use a bundler (like Vite) and [write JSX](./docs/jsx.md), or skip the build step and use `html` tagged templates.
|
|
18
15
|
|
|
19
|
-
##
|
|
16
|
+
## Shut up and show me
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
Here's an app that displays "Hello World" or "Goodbye World" and a button to toggle which message is displayed.
|
|
22
19
|
|
|
23
20
|
```jsx
|
|
24
|
-
import {
|
|
21
|
+
import { html, createAtom, createRoot, compose } from "@manyducks.co/dolla";
|
|
25
22
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
const [$count, setCount] = useSignal(0);
|
|
29
|
-
|
|
30
|
-
function increment() {
|
|
31
|
-
setCount((current) => current + 1);
|
|
32
|
-
}
|
|
23
|
+
function Hello() {
|
|
24
|
+
const [value, setValue] = createAtom(false);
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
// to get a signal's value, just call it like a function. easy.
|
|
37
|
-
console.log("Count is: " + $count());
|
|
38
|
-
});
|
|
26
|
+
const word = compose(() => (value() ? "Hello" : "Goodbye"));
|
|
39
27
|
|
|
40
|
-
return
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{/* 4. Using signals with helpers like <Show> is a total breeze. */}
|
|
46
|
-
<Show when={() => $count() > 100}>
|
|
47
|
-
<p>Whoa, that's a lotta clicks!</p>
|
|
48
|
-
</Show>
|
|
49
|
-
|
|
50
|
-
<div>
|
|
51
|
-
<button onClick={increment}>+1</button>
|
|
52
|
-
{/* ... other buttons */}
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
28
|
+
return html`
|
|
29
|
+
<p>${word} World</p>
|
|
30
|
+
<button onClick=${() => setValue((current) => !current)}>Toggle</button>
|
|
31
|
+
`;
|
|
56
32
|
}
|
|
57
|
-
```
|
|
58
33
|
|
|
59
|
-
|
|
34
|
+
createRoot(document.body).mount(Hello);
|
|
35
|
+
```
|
|
60
36
|
|
|
61
|
-
|
|
37
|
+
And here's a counter with a lot more going on, plus some comments to explain what's happening.
|
|
62
38
|
|
|
63
|
-
|
|
64
|
-
|
|
39
|
+
```jsx
|
|
40
|
+
import { html, createAtom, createRoot, onMount, onCleanup, onEffect, showIf } from "@manyducks.co/dolla";
|
|
65
41
|
|
|
66
|
-
|
|
42
|
+
function Counter() {
|
|
43
|
+
// An atom is the basic building block of dynamic state.
|
|
44
|
+
// It consists of a getter function and a setter function, returned as a tuple:
|
|
45
|
+
const [count, setCount] = createAtom(0);
|
|
46
|
+
|
|
47
|
+
// Atoms can be composed to derive state from one or more other states.
|
|
48
|
+
// Composed states update automatically with the values of any atoms they call.
|
|
49
|
+
const isALot = compose(() => count() > 100);
|
|
50
|
+
|
|
51
|
+
// Composed states are lazy-computed if dependencies have changed.
|
|
52
|
+
isALot(); // computes the value; returns false
|
|
53
|
+
isALot(); // returns cached value (count has not changed)
|
|
54
|
+
isALot(); // returns cached value (count has not changed)
|
|
55
|
+
|
|
56
|
+
// Hooks can bind logic to the component lifecycle or store and access data on the context.
|
|
57
|
+
// They always take the Context object as a first argument by convention.
|
|
58
|
+
onMount(this, () => {
|
|
59
|
+
console.log("I'll be called when Counter is on the page");
|
|
60
|
+
|
|
61
|
+
// You can call hooks wherever and whenever you want as long as you have a Context object to pass.
|
|
62
|
+
onCleanup(this, () => {
|
|
63
|
+
console.log("I'll be called when Counter is no longer on the page");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
// Effects run side-effect code in response to state changes.
|
|
68
|
+
// Just like `compose`, the effect tracks getters called within and re-runs when values change.
|
|
69
|
+
onEffect(this, () => {
|
|
70
|
+
console.log("count has changed:", count());
|
|
71
|
+
});
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
// Getters can be dropped into the DOM where dynamic values are needed,
|
|
74
|
+
// either as children or as HTML attributes. DOM nodes will update in sync with state changes.
|
|
75
|
+
return html`
|
|
76
|
+
<div>
|
|
77
|
+
<p>Count: ${count}</p>
|
|
71
78
|
|
|
72
|
-
|
|
73
|
-
- **Manual Tracking (if you're feeling extra)**: If you _really_ want to, you can give it an array of signals to watch. Then it'll _only_ pay attention to those.
|
|
79
|
+
<button disabled=${isALot} onClick=${() => setCount((current) => current + 1)}>Increment</button>
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
${showIf(isALot, html`<p>That's a lot!</p>`)}
|
|
82
|
+
</div>
|
|
83
|
+
`;
|
|
84
|
+
// ^ You can use view helpers like `showIf`, `hideIf` and `forEach` for control flow in templates.
|
|
85
|
+
}
|
|
76
86
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const [$name, setName] = useSignal("Dolla");
|
|
80
|
-
|
|
81
|
-
// AUTOMATIC: Runs if $count OR $name changes. Simple.
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
console.log(`Yo ${$name()}, the count is ${$count()}`);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// MANUAL: This ONLY runs when $count changes.
|
|
87
|
-
// We're using $name() in here, but the effect is like "I don't see it" lol.
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
console.log(`Yo ${$name()}, the count is ${$count()}`);
|
|
90
|
-
}, [$count]);
|
|
87
|
+
// A root will create and mount an instance of a view onto a DOM node.
|
|
88
|
+
createRoot(document.body).mount(Counter);
|
|
91
89
|
```
|
|
92
90
|
|
|
93
|
-
|
|
91
|
+
A few points to notice:
|
|
94
92
|
|
|
95
|
-
|
|
93
|
+
- The component function runs only once when the component is initialized (no re-renders).
|
|
94
|
+
- _All_ changes at runtime are a result of atoms being set.
|
|
95
|
+
- All DOM updates are synchronous with state changes. You can use [`batch`](./docs/reactivity.md) to process several changes as one.
|
|
96
|
+
- Getters are tracked in `compose` and `createEffect`/`onEffect` callbacks. You can use [`peek`](./docs/reactivity.md) to opt-out of tracking.
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
## Dependent data flow
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
Apps are built by composing atoms into ever more complex data structures, eventually attaching the fingers of the monstrosity to some switches and levers that can manipulate DOM nodes.
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
Imagine your state as a hamster. Your job is to create a mech suit around the hamster so when he twitches his paw his 10 ton fist takes a chunk out of a mountainside. This is no ordinary hamster; it's your app's state, and the mountainside is the DOM. And the mech suit is your code. Built out of Dolla parts. Yes.
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
Now that everything is clear, here's what that looks like in practice. Only a single number changes, but three values are displayed in sync with the original number. A few small state changes cause large changes visible to the user. It's easy to understand what data is important.
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
```tsx
|
|
107
|
+
import { html, createRoot, createAtom, compose } from "@manyducks.co/dolla";
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const [$count, setCount] = useSignal(0);
|
|
109
|
+
function Converter() {
|
|
110
|
+
// Just one source value that changes.
|
|
111
|
+
const [celsius, setCelsius] = createAtom(0);
|
|
111
112
|
|
|
112
|
-
//
|
|
113
|
-
|
|
113
|
+
// Depends on `celsius`; updates when `celsius` updates.
|
|
114
|
+
const fahrenheit = compose(() => {
|
|
115
|
+
return (celsius() * 9) / 5 + 32;
|
|
116
|
+
});
|
|
114
117
|
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
// Depends on `fahrenheit`; updates when `fahrenheit` updates.
|
|
119
|
+
const description = compose(() => {
|
|
120
|
+
const f = fahrenheit();
|
|
121
|
+
if (f <= 32) return "Freezing ❄️";
|
|
122
|
+
if (f >= 90) return "Hot! 🔥";
|
|
123
|
+
return "Moderate 🌤️";
|
|
124
|
+
});
|
|
117
125
|
|
|
118
|
-
return
|
|
126
|
+
return html`
|
|
127
|
+
<div>
|
|
128
|
+
<input
|
|
129
|
+
type="number"
|
|
130
|
+
value=${celsius}
|
|
131
|
+
oninput=${(e) => {
|
|
132
|
+
// Set by user input.
|
|
133
|
+
setCelsius(e.target.valueAsNumber);
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
<p>Celsius: ${celsius}°C</p>
|
|
138
|
+
<p>Fahrenheit: ${fahrenheit}°F</p>
|
|
139
|
+
<p>Condition: ${description}</p>
|
|
140
|
+
</div>
|
|
141
|
+
`;
|
|
119
142
|
}
|
|
120
|
-
```
|
|
121
143
|
|
|
122
|
-
|
|
144
|
+
createRoot(document.body).mount(Converter);
|
|
145
|
+
```
|
|
123
146
|
|
|
124
|
-
###
|
|
147
|
+
### Stores for shared state
|
|
125
148
|
|
|
126
|
-
|
|
149
|
+
Dolla comes with a really easy way to share state across views in the same subtree.
|
|
127
150
|
|
|
128
151
|
```jsx
|
|
152
|
+
// Define a Store function:
|
|
129
153
|
function CounterStore() {
|
|
130
|
-
const [
|
|
154
|
+
const [value, setValue] = createAtom(0);
|
|
131
155
|
|
|
132
|
-
//
|
|
156
|
+
// We can define our own functions to control how the state gets changed.
|
|
133
157
|
const increment = () => setValue((current) => current + 1);
|
|
134
158
|
const decrement = () => setValue((current) => current - 1);
|
|
135
159
|
|
|
136
|
-
|
|
160
|
+
// This object is accessible to other components that use this store.
|
|
161
|
+
return { value, increment, decrement };
|
|
137
162
|
}
|
|
138
163
|
```
|
|
139
164
|
|
|
140
|
-
You
|
|
165
|
+
You work with stores using the `addStore` and `getStore` hooks. When you add a store to a part of your app, any component inside can now use it.
|
|
166
|
+
|
|
167
|
+
> Like an umbrella; it provides shade (state) to those under it, but not next to or above it.
|
|
141
168
|
|
|
142
169
|
```jsx
|
|
143
170
|
function App() {
|
|
144
|
-
//
|
|
145
|
-
const counter =
|
|
171
|
+
// Creates one instance of CounterStore and returns it for immediate use.
|
|
172
|
+
const counter = addStore(this, CounterStore);
|
|
146
173
|
|
|
147
174
|
return (
|
|
148
175
|
<div>
|
|
176
|
+
{/* Child views inherit context and therefore access to stores above them in the view tree. */}
|
|
149
177
|
<CounterView />
|
|
150
178
|
<button onClick={counter.increment}>Increment</button>
|
|
151
179
|
</div>
|
|
@@ -153,203 +181,24 @@ function App() {
|
|
|
153
181
|
}
|
|
154
182
|
|
|
155
183
|
function CounterView() {
|
|
156
|
-
//
|
|
157
|
-
const counter =
|
|
158
|
-
return <p>Current value: {counter
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
[More on stores.](./docs/stores.md)
|
|
163
|
-
|
|
164
|
-
### 3\. Mixins: Reusable superpowers
|
|
165
|
-
|
|
166
|
-
**Mixins** are a super cool way to add reusable behaviors to your HTML elements. A mixin is just a function you can slap onto any element, and it can have its own state and lifecycle. It's perfect for stuff like logging, animations, or whatever else you can dream up.
|
|
167
|
-
|
|
168
|
-
```jsx
|
|
169
|
-
function logLifecycle() {
|
|
170
|
-
// A mixin is just a function...
|
|
171
|
-
return (element) => {
|
|
172
|
-
// ...that takes an element and can use hooks inside.
|
|
173
|
-
const context = useContext();
|
|
174
|
-
useMount(() => context.log("element mounted!", element));
|
|
175
|
-
useUnmount(() => context.log("element unmounted!"));
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Then you can use it in any View.
|
|
180
|
-
function MyComponent() {
|
|
181
|
-
return (
|
|
182
|
-
<div>
|
|
183
|
-
{/* Just call it in the `mixin` prop. */}
|
|
184
|
-
<h1 mixin={logLifecycle()}>I'll log when I show up and leave.</h1>
|
|
185
|
-
|
|
186
|
-
{/* You can even use an array of 'em! */}
|
|
187
|
-
<p mixin={[logLifecycle(), otherMixin()]}>Me too!</p>
|
|
188
|
-
</div>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
[More on mixins.](./docs/mixins.md)
|
|
194
|
-
|
|
195
|
-
### 4\. So, what's this "Context" thing anyway?
|
|
196
|
-
|
|
197
|
-
**Context** is basically the glue that holds all your components together. Think of it like a family tree. Every component has its own context, but it's linked to its parent. This lets you do some cool stuff:
|
|
198
|
-
|
|
199
|
-
- **Finding Things**: When you do `useStore(CounterStore)`, Dolla just climbs up the family tree from your component until it finds where you provided the store. It's how some component deep in your app can get state from way up top.
|
|
200
|
-
- **Helpful Tools**: The context itself has some neat tricks. The logging methods (`.log()`, `.warn()`, etc.) automatically know your component's name, which is awesome for debugging. There's even a `.crash()` that'll just stop the app and show an error page if things go really wrong. You can get and set the component's name with `context.getName()` and `context.setName()`.
|
|
201
|
-
- **Deep-level State**: Context has its own key-value state system with `setState()` and `getState()`. The framework uses this a bunch. It's there if you wanna get wild, but tbh, for your app's state, **you should probably just use Stores.** They're way easier.
|
|
202
|
-
|
|
203
|
-
## Batteries Included: All The Stuff You Get\! 🧰
|
|
204
|
-
|
|
205
|
-
Dolla isn't just for rendering. We threw in a bunch of tools so you can stop hunting around on npm.
|
|
206
|
-
|
|
207
|
-
### A Router that Doesn't Suck
|
|
208
|
-
|
|
209
|
-
Dolla has a router for making multi-page apps. It just works. Back/forward buttons, bookmarks, all that jazz. It's also smart and always picks the _most specific_ route, so you don't get weird bugs based on the order you write your routes.
|
|
210
|
-
|
|
211
|
-
#### Route Patterns
|
|
212
|
-
|
|
213
|
-
- **Static**: `/dashboard/settings`
|
|
214
|
-
- **Number Param** (only matches numbers): `/users/{#id}`
|
|
215
|
-
- **Anything Param**: `/users/{name}`
|
|
216
|
-
- **Wildcard**: `/files/*`
|
|
217
|
-
|
|
218
|
-
#### Setting it Up
|
|
219
|
-
|
|
220
|
-
```jsx
|
|
221
|
-
import { createApp } from "@manyducks.co/dolla";
|
|
222
|
-
import { createRouter } from "@manyducks.co/dolla/router";
|
|
223
|
-
import { ThingIndex, ThingDetails, ThingEdit } from "./views.js";
|
|
224
|
-
|
|
225
|
-
const router = createRouter({
|
|
226
|
-
routes: [
|
|
227
|
-
{
|
|
228
|
-
path: "/things",
|
|
229
|
-
view: null, // a null view just groups routes
|
|
230
|
-
routes: [
|
|
231
|
-
{ path: "/", view: ThingIndex }, // matches `/things`
|
|
232
|
-
{ path: "/{#id}", view: ThingDetails }, // matches `/things/123`
|
|
233
|
-
{ path: "/{#id}/edit", view: ThingEdit }, // matches `/things/123/edit`
|
|
234
|
-
],
|
|
235
|
-
},
|
|
236
|
-
{ path: "*", redirect: "/things" }, // catch-all
|
|
237
|
-
],
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const app = createApp(router);
|
|
241
|
-
app.mount(document.body);
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
#### Using It
|
|
245
|
-
|
|
246
|
-
Just use the `useRouter()` hook.
|
|
247
|
-
|
|
248
|
-
```jsx
|
|
249
|
-
import { useEffect } from "@manyducks.co/dolla";
|
|
250
|
-
import { useRouter } from "@manyducks.co/dolla/router";
|
|
251
|
-
|
|
252
|
-
function ThingDetails() {
|
|
253
|
-
const router = useRouter();
|
|
254
|
-
const { $params } = router; // get reactive params
|
|
255
|
-
|
|
256
|
-
useEffect(() => {
|
|
257
|
-
console.log("Current thing ID:", $params().id);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
function goToNext() {
|
|
261
|
-
const nextId = $params().id + 1;
|
|
262
|
-
router.go(`/things/${nextId}`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return (
|
|
266
|
-
<div>
|
|
267
|
-
<p>Viewing thing #{$params().id}</p>
|
|
268
|
-
<button onClick={goToNext}>View Next Thing</button>
|
|
269
|
-
</div>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### A Built-in HTTP Client
|
|
275
|
-
|
|
276
|
-
Dolla has a little `http` client for API calls. It automatically parses JSON and has a sick middleware system.
|
|
277
|
-
|
|
278
|
-
```jsx
|
|
279
|
-
import { http } from "@manyducks.co/dolla/http";
|
|
280
|
-
|
|
281
|
-
// Automatically add an auth header to all API calls
|
|
282
|
-
http.use(async (req, next) => {
|
|
283
|
-
if (req.url.pathname.startsWith("/api/")) {
|
|
284
|
-
req.headers.set("authorization", `Bearer ${localStorage.getItem("api-key")}`);
|
|
285
|
-
}
|
|
286
|
-
await next(); // don't forget this part!
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
const res = await http.get("/api/users");
|
|
290
|
-
console.log(res.body); // already parsed JSON, leggo
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Internationalization (i18n)
|
|
294
|
-
|
|
295
|
-
Wanna make your app speak different languages? We got you. Dolla's i18n stuff is super simple.
|
|
296
|
-
|
|
297
|
-
The best part? `t()` gives you back a **signal**. So if the user switches languages, your whole app just updates. Automatically. It's kinda magic.
|
|
298
|
-
|
|
299
|
-
```jsx
|
|
300
|
-
import { createApp, useSignal } from "@manyducks.co/dolla";
|
|
301
|
-
import { i18n, t } from "@manyducks.co/dolla/i18n";
|
|
302
|
-
|
|
303
|
-
function CounterView() {
|
|
304
|
-
return <button>{t("buttonLabel")}</button>;
|
|
184
|
+
// Returns the same instance of CounterStore.
|
|
185
|
+
const counter = getStore(this, CounterStore);
|
|
186
|
+
return <p>Current value: {counter.value}</p>;
|
|
305
187
|
}
|
|
306
|
-
|
|
307
|
-
const app = createApp(CounterView);
|
|
308
|
-
|
|
309
|
-
// Set up your languages before you start the app
|
|
310
|
-
i18n
|
|
311
|
-
.setup({
|
|
312
|
-
locale: "en",
|
|
313
|
-
translations: [
|
|
314
|
-
{ locale: "en", strings: { buttonLabel: "Click me!" } },
|
|
315
|
-
{ locale: "ja", strings: { buttonLabel: "押してね!" } },
|
|
316
|
-
],
|
|
317
|
-
})
|
|
318
|
-
.then(() => app.mount(document.body));
|
|
319
188
|
```
|
|
320
189
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
### vs. React
|
|
324
|
-
|
|
325
|
-
- **No More Re-Renders (Fr!)**: This is the big one. In React, when you call `setCount(1)`, your _entire component function runs all over again_. React then has to figure out what changed with the VDOM. In Dolla, your component function only runs once, ever. When you call `setCount(1)`, Dolla just goes and changes the text in that one `<p>` tag. That's it. This makes it way faster and easier to reason about.
|
|
326
|
-
- **Goodbye, `useCallback`**: Because React re-renders all the time, it creates new functions and objects on every single render. This is why you have to wrap everything in `useCallback` and `useMemo` so you don't cause a chain reaction of re-renders in your child components. Since Dolla components don't re-render, you can just pass a regular function as a prop without a single worry. No more referential equality drama.
|
|
327
|
-
- **Easier Effects**: You know that annoying dependency array? Gone. Unless you, like, _want_ to use it for manual control. This means no more stale closure bugs.
|
|
328
|
-
- **State Management Included**: `Stores` are built in, so you can probably skip installing Redux or Zustand and all the boilerplate that comes with them.
|
|
329
|
-
|
|
330
|
-
### vs. Angular
|
|
331
|
-
|
|
332
|
-
- **Way Less Boilerplate**: Angular is a whole ceremony. Modules, decorators, dependency injection... it's a lot. Dolla is just functions. You write a function, it becomes a component. It's a much more direct vibe.
|
|
333
|
-
- **Stores are like Services, but chill**: Ngl, our `Stores` were lowkey inspired by Angular's services. It's the same idea of providing a thing in one place and using it somewhere else. But instead of all the module and decorator ceremony, you just use a couple of simple hooks.
|
|
334
|
-
- **Signals First, Not an Add-on**: Yeah, Angular has signals now, but the whole framework was built on a different system (Zone.js). Dolla was born with signals as its main character, so the whole DX is built around them. It's not an optional extra, it's the whole point.
|
|
190
|
+
### What are stores good for?
|
|
335
191
|
|
|
336
|
-
|
|
192
|
+
- Authentication state
|
|
193
|
+
- Caching data between router pages
|
|
194
|
+
- Avoiding prop drilling for local state
|
|
337
195
|
|
|
338
|
-
|
|
339
|
-
- **Clearer Updates**: In Dolla, you `setCount(1)`. You know what's happening. In Svelte, `count += 1` is kinda magic.
|
|
340
|
-
- **Consistent Vibe**: Everything in Dolla uses the same hook style. It all feels the same.
|
|
196
|
+
## Extras
|
|
341
197
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
Okay, Solid is sick, ngl. It uses signals too. The main difference is the vibe. Solid is like getting a super-tuned car engine. **Dolla is the whole car.** With heated seats and a good sound system.
|
|
345
|
-
|
|
346
|
-
Choose **SolidJS** if you wanna build your car from scratch.
|
|
347
|
-
|
|
348
|
-
Choose **Dolla** if you wanna just get in and drive.
|
|
349
|
-
|
|
350
|
-
---
|
|
198
|
+
Now that you've seen how to wire up a Dolla app, here are a few things to try:
|
|
351
199
|
|
|
352
|
-
|
|
200
|
+
- Add a [router](./src/router/README.md) to create an SPA
|
|
201
|
+
- Add [language translations](./src/translate/README.md) to go international.
|
|
353
202
|
|
|
354
203
|
---
|
|
355
204
|
|