@manyducks.co/dolla 2.0.0-alpha.9 → 2.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 +222 -512
- package/dist/core/app.d.ts +24 -0
- package/dist/core/context.d.ts +147 -0
- package/dist/core/env.d.ts +3 -0
- package/dist/core/hooks.d.ts +70 -0
- package/dist/core/hooks.test.d.ts +1 -0
- package/dist/core/index.d.ts +25 -0
- package/dist/core/logger.d.ts +42 -0
- package/dist/core/logger.test.d.ts +0 -0
- package/dist/core/markup.d.ts +82 -0
- package/dist/core/markup.test.d.ts +0 -0
- package/dist/core/nodes/_markup.d.ts +36 -0
- package/dist/core/nodes/dom.d.ts +13 -0
- package/dist/core/nodes/dynamic.d.ts +22 -0
- package/dist/core/nodes/element.d.ts +27 -0
- package/dist/core/nodes/portal.d.ts +18 -0
- package/dist/core/nodes/repeat.d.ts +27 -0
- package/dist/core/nodes/view.d.ts +25 -0
- package/dist/core/ref.d.ts +19 -0
- package/dist/core/ref.test.d.ts +1 -0
- package/dist/core/signals.d.ts +100 -0
- package/dist/core/signals.test.d.ts +1 -0
- package/dist/{views → core/views}/default-crash-view.d.ts +11 -4
- package/dist/core/views/for.d.ts +21 -0
- package/dist/core/views/fragment.d.ts +7 -0
- package/dist/core/views/portal.d.ts +16 -0
- package/dist/core/views/show.d.ts +25 -0
- package/dist/fragment-BahD_BJA.js +7 -0
- package/dist/fragment-BahD_BJA.js.map +1 -0
- package/dist/{modules/http.d.ts → http/index.d.ts} +3 -5
- package/dist/http.js +150 -0
- package/dist/http.js.map +1 -0
- package/dist/i18n/index.d.ts +134 -0
- package/dist/i18n.js +309 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index-DRJlxs-Q.js +535 -0
- package/dist/index-DRJlxs-Q.js.map +1 -0
- package/dist/index.js +160 -1414
- package/dist/index.js.map +1 -1
- package/dist/jsx-dev-runtime.d.ts +3 -2
- package/dist/jsx-dev-runtime.js +5 -12
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +4 -3
- package/dist/jsx-runtime.js +9 -15
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/logger-Aqi9m1CF.js +565 -0
- package/dist/logger-Aqi9m1CF.js.map +1 -0
- package/dist/markup-8jNhoqDe.js +1089 -0
- package/dist/markup-8jNhoqDe.js.map +1 -0
- package/dist/router/hooks.d.ts +2 -0
- package/dist/router/index.d.ts +3 -0
- package/dist/router/router.d.ts +166 -0
- package/dist/{routing.d.ts → router/router.utils.d.ts} +17 -3
- package/dist/router/router.utils.test.d.ts +1 -0
- package/dist/router.js +6 -0
- package/dist/router.js.map +1 -0
- package/dist/typeChecking-5kmX0ulW.js +65 -0
- package/dist/typeChecking-5kmX0ulW.js.map +1 -0
- package/dist/typeChecking.d.ts +2 -98
- package/dist/typeChecking.test.d.ts +1 -0
- package/dist/types.d.ts +97 -25
- package/dist/utils.d.ts +25 -3
- package/docs/buildless.md +132 -0
- package/docs/components.md +238 -0
- package/docs/hooks.md +356 -0
- package/docs/http.md +178 -0
- package/docs/i18n.md +220 -0
- package/docs/index.md +10 -0
- package/docs/markup.md +136 -0
- package/docs/mixins.md +176 -0
- package/docs/ref.md +77 -0
- package/docs/router.md +281 -0
- package/docs/setup.md +137 -0
- package/docs/signals.md +262 -0
- package/docs/stores.md +113 -0
- package/docs/views.md +356 -0
- package/index.d.ts +2 -2
- package/notes/atomic.md +452 -0
- package/notes/elimination.md +33 -0
- package/notes/observable.md +180 -0
- package/notes/scratch.md +346 -14
- package/notes/splitting.md +5 -0
- package/package.json +29 -15
- package/vite.config.js +5 -11
- package/build.js +0 -34
- package/dist/index.d.ts +0 -21
- package/dist/markup.d.ts +0 -108
- package/dist/modules/dolla.d.ts +0 -111
- package/dist/modules/i18n.d.ts +0 -59
- package/dist/modules/render.d.ts +0 -17
- package/dist/modules/router.d.ts +0 -152
- package/dist/nodes/cond.d.ts +0 -26
- package/dist/nodes/html.d.ts +0 -31
- package/dist/nodes/observer.d.ts +0 -29
- package/dist/nodes/outlet.d.ts +0 -22
- package/dist/nodes/portal.d.ts +0 -19
- package/dist/nodes/repeat.d.ts +0 -34
- package/dist/nodes/text.d.ts +0 -19
- package/dist/passthrough-9kwwjgWk.js +0 -1279
- package/dist/passthrough-9kwwjgWk.js.map +0 -1
- package/dist/state.d.ts +0 -101
- package/dist/view.d.ts +0 -65
- package/dist/views/passthrough.d.ts +0 -5
- package/notes/context-vars.md +0 -21
- package/notes/readme-scratch.md +0 -222
- package/notes/route-middleware.md +0 -42
- package/tests/state.test.js +0 -135
- /package/dist/{routing.test.d.ts → core/context.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -3,644 +3,354 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Alright, so Dolla is this new web framework. Ngl, it's pretty sick. It feels like you're writing React, which is cool, but it's also got this crazy fast reactivity thing going on under the hood. It’s built to feel super familiar, but like, way easier to figure out and it comes with all the stuff you actually need to build something.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
- ⚡️ [**Signals**](./docs/signals.md) make your UI updates hit different. Your DOM just refreshes instantly, it's lowkey magic.
|
|
9
|
+
- 📦 You got options for [components](./docs/components.md), three different vibes:
|
|
10
|
+
- 🖥️ [**Views**](./docs/views.md) are for the UI glow up. You know the drill.
|
|
11
|
+
- 💾 [**Stores**](./docs/stores.md) are for when your components need to share state without all the drama. We don't do prop drilling in this house.
|
|
12
|
+
- ✨ [**Mixins**](./docs/mixins.md) give your plain HTML elements some extra rizz. Slay.
|
|
13
|
+
- 🪝 [**Hooks**](./docs/hooks.md) let your components actually cook. They're your familiar, React-style toolkit for state (`useSignal`), lifecycle (`useMount`), and more.
|
|
14
|
+
- 🔀 The client-side [**router**](./docs/router.md) actually understands the assignment. Nested routes, middleware for gatekeeping pages (iykyk), preloading data so it's not laggy... it's all there.
|
|
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.
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
- 📦 Reusable components with [Views](#section-views).
|
|
12
|
-
- 🔀 Built in [routing]() with nested routes and middleware support (check login status, preload data, etc).
|
|
13
|
-
- 🐕 Built in [HTTP]() client with middleware support (set auth headers, etc).
|
|
14
|
-
- 📍 Built in [localization]() system (store translated strings in JSON files and call the `t` function to get them).
|
|
15
|
-
- 🍳 Build system optional. Write views in JSX or use `html` tagged template literals.
|
|
19
|
+
## Check it out: The Counter Example
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## State
|
|
20
|
-
|
|
21
|
-
### Basic State API
|
|
22
|
-
|
|
23
|
-
```jsx
|
|
24
|
-
import { createState, toState, valueOf, derive } from "@manyducks.co/dolla";
|
|
25
|
-
|
|
26
|
-
const [$count, setCount] = createState(72);
|
|
27
|
-
|
|
28
|
-
// Get value
|
|
29
|
-
$count.get(): // 72
|
|
30
|
-
|
|
31
|
-
// Replace the stored value with something else
|
|
32
|
-
setCount(300);
|
|
33
|
-
$count.get(); // 300
|
|
34
|
-
|
|
35
|
-
// You can also pass a function that takes the current value and returns a new one
|
|
36
|
-
setCount((current) => current + 1);
|
|
37
|
-
$count.get(); // 301
|
|
38
|
-
|
|
39
|
-
// Watch for changes to the value
|
|
40
|
-
const unwatch = $count.watch((value) => {
|
|
41
|
-
// This function is called immediately with the current value, then again each time the value changes.
|
|
42
|
-
});
|
|
43
|
-
unwatch(); // Stop watching for changes
|
|
44
|
-
|
|
45
|
-
// Returns the value of a state. If the value is not a state it is returned as is.
|
|
46
|
-
const count = valueOf($count);
|
|
47
|
-
const bool = valueOf(true);
|
|
48
|
-
|
|
49
|
-
// Creates a state from a value. If the value is already a state it is returned as is.
|
|
50
|
-
const $bool = toState(true);
|
|
51
|
-
const $anotherCount = toState($count);
|
|
52
|
-
|
|
53
|
-
// Derive a new state from one or more other states. Whenever $count changes, $doubled will follow.
|
|
54
|
-
const $doubled = derive([$count], (count) => count * 2);
|
|
55
|
-
const $sum = derive([$count, $doubled], (count, doubled) => count + doubled);
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
States also come in a settable variety that includes the setter on the same object. Sometimes you want to pass around a two-way binding and this is what SettableState is for.
|
|
21
|
+
The best way to get it is to just see it. If you've ever touched React, you'll know what's up, but peep the little things that make your life way easier.
|
|
59
22
|
|
|
60
23
|
```jsx
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
// Settable states have their setter included.
|
|
64
|
-
const $$value = createSettableState("Test");
|
|
65
|
-
$$value.set("New Value");
|
|
66
|
-
|
|
67
|
-
// They can also be split into a State and Setter
|
|
68
|
-
const [$value, setValue] = fromSettableState($$value);
|
|
24
|
+
import { useSignal, useEffect, createApp } from "@manyducks.co/dolla";
|
|
69
25
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Or discard the setter and make it read-only using the good old toState function:
|
|
74
|
-
const $value = toState($$value);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
You can also do weird proxy things like this:
|
|
78
|
-
|
|
79
|
-
```jsx
|
|
80
|
-
// Create an original place for the state to live
|
|
81
|
-
const [$value, setValue] = createState(5);
|
|
82
|
-
|
|
83
|
-
// Derive a state that doubles the value
|
|
84
|
-
const $doubled = derive([$value], (value) => value * 2);
|
|
85
|
-
|
|
86
|
-
// Create a setter that takes the doubled value and sets the original $value accordingly.
|
|
87
|
-
const setDoubled = createSetter($doubled, (next, current) => {
|
|
88
|
-
setValue(next / 2);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Bundle the derived state and setter into a SettableState to pass around.
|
|
92
|
-
const $$doubled = toSettableState($doubled, setDoubled);
|
|
93
|
-
|
|
94
|
-
// Setting the doubled state...
|
|
95
|
-
$$doubled.set(100);
|
|
96
|
-
|
|
97
|
-
// ... will be reflected everywhere.
|
|
98
|
-
$$doubled.get(); // 100
|
|
99
|
-
$doubled.get(); // 100
|
|
100
|
-
$value.get(); // 50
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Views [id="section-views"]
|
|
104
|
-
|
|
105
|
-
A basic view:
|
|
106
|
-
|
|
107
|
-
```js
|
|
108
|
-
import Dolla, { createState, html } from "@manyducks.co/dolla";
|
|
109
|
-
|
|
110
|
-
function Counter(props, ctx) {
|
|
111
|
-
const [$count, setCount] = createState(0);
|
|
26
|
+
function Counter() {
|
|
27
|
+
// 1. Make a reactive thingy, we call it a "signal".
|
|
28
|
+
const [$count, setCount] = useSignal(0);
|
|
112
29
|
|
|
113
30
|
function increment() {
|
|
114
|
-
setCount((
|
|
31
|
+
setCount((current) => current + 1);
|
|
115
32
|
}
|
|
116
33
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
Dolla.mount(document.body, Counter);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
If you've ever used React before (and chances are you have if you're interested in obscure frameworks like this one) this should look very familiar to you.
|
|
129
|
-
|
|
130
|
-
The biggest difference is that the Counter function runs only once when the component is mounted. All updates after that point are a direct result of `$count` being updated.
|
|
131
|
-
|
|
132
|
-
## Advanced Componentry
|
|
133
|
-
|
|
134
|
-
Component functions take two arguments; props and a `Context` object. Props are passed from parent components to child components, and `Context` is provided by the app.
|
|
135
|
-
|
|
136
|
-
> The following examples are shown in TypeScript for clarity. Feel free to omit the type annotations in your own code if you prefer vanilla JS.
|
|
137
|
-
|
|
138
|
-
### Props
|
|
139
|
-
|
|
140
|
-
Props are values passed down from parent components. These can be static values, signals, callbacks and anything else the child component needs to do its job.
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
import { type State, type Context, html } from "@manyducks.co/dolla";
|
|
144
|
-
|
|
145
|
-
type HeadingProps = {
|
|
146
|
-
$text: State<string>;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
function Heading(props: HeadingProps, c: Context) {
|
|
150
|
-
return html`<h1>${props.$text}</h1>`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function Layout() {
|
|
154
|
-
const [$text, setText] = signal("HELLO THERE!");
|
|
34
|
+
// 2. This effect just works. It knows to re-run when $count changes. No drama.
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
// to get a signal's value, just call it like a function. easy.
|
|
37
|
+
console.log("Count is: " + $count());
|
|
38
|
+
});
|
|
155
39
|
|
|
156
40
|
return (
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
41
|
+
<div>
|
|
42
|
+
{/* 3. In your HTML, just drop the signal right in. */}
|
|
43
|
+
<p>Counter: {$count}</p>
|
|
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>
|
|
160
55
|
);
|
|
161
56
|
}
|
|
162
57
|
```
|
|
163
58
|
|
|
164
|
-
###
|
|
165
|
-
|
|
166
|
-
```tsx
|
|
167
|
-
import { type State, type Context, html } from "@manyducks.co/dolla";
|
|
59
|
+
### 1\. `useSignal` - State that's actually simple
|
|
168
60
|
|
|
169
|
-
|
|
170
|
-
$text: State<string>;
|
|
171
|
-
};
|
|
61
|
+
You make state with `useSignal()`, and it gives you back a `[getter, setter]` pair, just like `useState` in React.
|
|
172
62
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// Log levels that get printed can be set at the app level.
|
|
63
|
+
- `$count`: This is the **signal**. We just use a `$` at the start by convention. Think of it as a reactive value you can just plop into your JSX.
|
|
64
|
+
- `setCount`: This is how you change the value. Works just like you'd think.
|
|
176
65
|
|
|
177
|
-
|
|
178
|
-
c.info("This is low priority info.");
|
|
179
|
-
c.log("This is normal priority info.");
|
|
180
|
-
c.warn("Hey! This could be serious.");
|
|
181
|
-
c.error("NOT GOOD! DEFINITELY NOT GOOD!!1");
|
|
66
|
+
When you need the value in your JS code (like in a `useEffect`), just call it like a function: `$count()`.
|
|
182
67
|
|
|
183
|
-
|
|
184
|
-
c.crash(new Error("STOP THE PRESSES! BURN IT ALL DOWN!!!"));
|
|
185
|
-
|
|
186
|
-
// The four lifecycle hooks:
|
|
187
|
-
|
|
188
|
-
// c.beforeMount(() => {
|
|
189
|
-
// c.info("Heading is going to be mounted. Good time to set things up.");
|
|
190
|
-
// });
|
|
191
|
-
|
|
192
|
-
c.onMount(() => {
|
|
193
|
-
c.info("Heading has just been mounted. Good time to access the DOM and finalize setup.");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
// c.beforeUnmount(() => {
|
|
197
|
-
// c.info("Heading is going to be unmounted. Good time to begin teardown.");
|
|
198
|
-
// });
|
|
199
|
-
|
|
200
|
-
c.onUnmount(() => {
|
|
201
|
-
c.info("Heading has just been unmounted. Good time to finalize teardown.");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// States can be watched by the component context.
|
|
205
|
-
// Watchers created this way are cleaned up automatically when the component unmounts.
|
|
206
|
-
|
|
207
|
-
c.watch(props.$text, (value) => {
|
|
208
|
-
c.warn(`text has changed to: ${value}`);
|
|
209
|
-
});
|
|
68
|
+
### 2\. Effects without the headache
|
|
210
69
|
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
```
|
|
70
|
+
`useEffect` and `useMemo` are here, but they're way more chill.
|
|
214
71
|
|
|
215
|
-
|
|
72
|
+
- **Automatic Tracking (by default\!)**: You literally don't have to do anything. Dolla just sees what signals you used and re-runs your code when they change.
|
|
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.
|
|
216
74
|
|
|
217
|
-
|
|
75
|
+
<!-- end list -->
|
|
218
76
|
|
|
219
77
|
```jsx
|
|
220
|
-
const [$count, setCount] =
|
|
221
|
-
|
|
222
|
-
// Set the value directly.
|
|
223
|
-
setCount(1);
|
|
224
|
-
setCount(2);
|
|
225
|
-
|
|
226
|
-
// Transform the previous value into a new one.
|
|
227
|
-
setCount((current) => current + 1);
|
|
228
|
-
|
|
229
|
-
// This can be used to create easy helper functions:
|
|
230
|
-
function increment(amount = 1) {
|
|
231
|
-
setCount((current) => current + amount);
|
|
232
|
-
}
|
|
233
|
-
increment();
|
|
234
|
-
increment(5);
|
|
235
|
-
increment(-362);
|
|
78
|
+
const [$count, setCount] = useSignal(0);
|
|
79
|
+
const [$name, setName] = useSignal("Dolla");
|
|
236
80
|
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// Watch for new values. Don't forget to call stop() to clean up!
|
|
241
|
-
const stop = $count.watch((current) => {
|
|
242
|
-
console.log(`count is now ${current}`);
|
|
81
|
+
// AUTOMATIC: Runs if $count OR $name changes. Simple.
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
console.log(`Yo ${$name()}, the count is ${$count()}`);
|
|
243
84
|
});
|
|
244
85
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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]);
|
|
249
91
|
```
|
|
250
92
|
|
|
251
|
-
|
|
93
|
+
### 3\. No VDOM, no problem
|
|
252
94
|
|
|
253
|
-
|
|
254
|
-
import { signal, derive } from "@manyducks.co/dolla";
|
|
95
|
+
Behind the scenes, Dolla isn't re-running your whole component all the time. Nah. It makes a direct connection from your signal to the exact spot in the HTML that uses it.
|
|
255
96
|
|
|
256
|
-
|
|
257
|
-
const [$index, setIndex] = signal(0);
|
|
97
|
+
When you `setCount(1)`, Dolla knows only the `<p>` tag and the `<Show>` component care. So it just updates those two things. It's like a ninja. This means no VDOM overhead and it's fast af by default.
|
|
258
98
|
|
|
259
|
-
|
|
260
|
-
const $selected = derive([$names, $index], (names, index) => names[index]);
|
|
99
|
+
## The Dolla Building Blocks
|
|
261
100
|
|
|
262
|
-
|
|
101
|
+
Dolla gives you a few types of components to keep your code from becoming a mess: **Views**, **Stores**, and **Mixins**. They're all connected by this thing called **context**, which you can grab with `useContext()`.
|
|
263
102
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
$selected.get(); // "Bon"
|
|
267
|
-
```
|
|
103
|
+
### 1\. Views: Your UI stuff
|
|
268
104
|
|
|
269
|
-
|
|
105
|
+
**Views** are your normal, everyday components for putting stuff on the screen. If you know React components, you're already a pro. They get `props`, use hooks, and return JSX.
|
|
270
106
|
|
|
271
107
|
```jsx
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const [$
|
|
275
|
-
const [$index, setIndex] = createState(0);
|
|
276
|
-
|
|
277
|
-
const [$selected, setSelected] = createProxyState([$names, $index], {
|
|
278
|
-
get(names, index) {
|
|
279
|
-
return names[index];
|
|
280
|
-
},
|
|
281
|
-
set(next, names, _) {
|
|
282
|
-
const index = names.indexOf(next);
|
|
283
|
-
if (index === -1) {
|
|
284
|
-
throw new Error("Name is not in the list!");
|
|
285
|
-
}
|
|
286
|
-
setIndex(index);
|
|
287
|
-
},
|
|
288
|
-
});
|
|
108
|
+
function ExampleView(props) {
|
|
109
|
+
const context = useContext();
|
|
110
|
+
const [$count, setCount] = useSignal(0);
|
|
289
111
|
|
|
290
|
-
|
|
291
|
-
|
|
112
|
+
// The logger automatically knows the component's name!
|
|
113
|
+
context.log("sup from ExampleView");
|
|
292
114
|
|
|
293
|
-
|
|
294
|
-
|
|
115
|
+
useMount(() => context.log("we're live!"));
|
|
116
|
+
useUnmount(() => context.log("aight, i'm out"));
|
|
295
117
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
$index.get(); // 1
|
|
118
|
+
return <div>{$count}</div>;
|
|
119
|
+
}
|
|
299
120
|
```
|
|
300
121
|
|
|
301
|
-
|
|
122
|
+
[More on views.](./docs/views.md)
|
|
302
123
|
|
|
303
|
-
|
|
124
|
+
### 2\. Stores: For your shared state
|
|
304
125
|
|
|
305
|
-
|
|
126
|
+
Got some state you need to use in a bunch of different places? **Stores** are for that. It's Dolla's built-in way to handle state so you don't have to go install another library.
|
|
306
127
|
|
|
307
128
|
```jsx
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
#### View Props
|
|
129
|
+
function CounterStore() {
|
|
130
|
+
const [$value, setValue] = useSignal(0);
|
|
314
131
|
|
|
315
|
-
|
|
132
|
+
// You can return functions to control how the state gets changed.
|
|
133
|
+
const increment = () => setValue((current) => current + 1);
|
|
134
|
+
const decrement = () => setValue((current) => current - 1);
|
|
316
135
|
|
|
317
|
-
|
|
318
|
-
import { html } from "@manyducks.co/dolla";
|
|
319
|
-
|
|
320
|
-
function ListView(props, ctx) {
|
|
321
|
-
return html`
|
|
322
|
-
<ul>
|
|
323
|
-
<${ListItemView} label="Squirrel" />
|
|
324
|
-
<${ListItemView} label="Chipmunk" />
|
|
325
|
-
<${ListItemView} label="Groundhog" />
|
|
326
|
-
</ul>
|
|
327
|
-
`;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function ListItemView(props, ctx) {
|
|
331
|
-
return html`<li>${props.label}</li>`;
|
|
136
|
+
return { $value, increment, decrement };
|
|
332
137
|
}
|
|
333
138
|
```
|
|
334
139
|
|
|
140
|
+
You "provide" a store to a part of your app, and any component inside can just grab it.
|
|
141
|
+
|
|
335
142
|
```jsx
|
|
336
|
-
function
|
|
143
|
+
function App() {
|
|
144
|
+
// Now, App and any components inside it can use CounterStore.
|
|
145
|
+
const counter = useStoreProvider(CounterStore);
|
|
146
|
+
|
|
337
147
|
return (
|
|
338
|
-
<
|
|
339
|
-
<
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
</ul>
|
|
148
|
+
<div>
|
|
149
|
+
<CounterView />
|
|
150
|
+
<button onClick={counter.increment}>Increment</button>
|
|
151
|
+
</div>
|
|
343
152
|
);
|
|
344
153
|
}
|
|
345
154
|
|
|
346
|
-
function
|
|
347
|
-
|
|
155
|
+
function CounterView() {
|
|
156
|
+
// Just ask for the store you need!
|
|
157
|
+
const counter = useStore(CounterStore);
|
|
158
|
+
return <p>Current value: {counter.$value}</p>;
|
|
348
159
|
}
|
|
349
160
|
```
|
|
350
161
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
### View Helpers
|
|
162
|
+
[More on stores.](./docs/stores.md)
|
|
354
163
|
|
|
355
|
-
|
|
164
|
+
### 3\. Mixins: Reusable superpowers
|
|
356
165
|
|
|
357
|
-
|
|
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.
|
|
358
167
|
|
|
359
168
|
```jsx
|
|
360
|
-
function
|
|
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() {
|
|
361
181
|
return (
|
|
362
182
|
<div>
|
|
363
|
-
{
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<ListItemView label="Squirrel" />
|
|
369
|
-
<ListItemView label="Chipmunk" />
|
|
370
|
-
<ListItemView label="Groundhog" />
|
|
371
|
-
</ul>,
|
|
372
|
-
|
|
373
|
-
// Visible when falsy
|
|
374
|
-
<span>List is hidden</span>,
|
|
375
|
-
)}
|
|
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>
|
|
376
188
|
</div>
|
|
377
189
|
);
|
|
378
190
|
}
|
|
379
191
|
```
|
|
380
192
|
|
|
381
|
-
|
|
193
|
+
[More on mixins.](./docs/mixins.md)
|
|
382
194
|
|
|
383
|
-
|
|
195
|
+
### 4\. So, what's this "Context" thing anyway?
|
|
384
196
|
|
|
385
|
-
|
|
386
|
-
function RepeatedListView() {
|
|
387
|
-
const $items = Dolla.toState(["Squirrel", "Chipmunk", "Groundhog"]);
|
|
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:
|
|
388
198
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
$items,
|
|
393
|
-
(item) => item, // Using the string itself as the key
|
|
394
|
-
($item, $index, ctx) => {
|
|
395
|
-
return <ListItemView label={$item} />;
|
|
396
|
-
},
|
|
397
|
-
)}
|
|
398
|
-
</ul>
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
```
|
|
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.
|
|
402
202
|
|
|
403
|
-
|
|
203
|
+
## Batteries Included: All The Stuff You Get\! 🧰
|
|
404
204
|
|
|
405
|
-
|
|
205
|
+
Dolla isn't just for rendering. We threw in a bunch of tools so you can stop hunting around on npm.
|
|
406
206
|
|
|
407
|
-
|
|
408
|
-
function PortalView() {
|
|
409
|
-
const content = (
|
|
410
|
-
<div class="modal">
|
|
411
|
-
<p>This is a modal.</p>
|
|
412
|
-
</div>
|
|
413
|
-
);
|
|
207
|
+
### A Router that Doesn't Suck
|
|
414
208
|
|
|
415
|
-
|
|
416
|
-
return portal(document.body, content);
|
|
417
|
-
}
|
|
418
|
-
```
|
|
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.
|
|
419
210
|
|
|
420
|
-
|
|
211
|
+
#### Route Patterns
|
|
421
212
|
|
|
422
|
-
|
|
213
|
+
- **Static**: `/dashboard/settings`
|
|
214
|
+
- **Number Param** (only matches numbers): `/users/{#id}`
|
|
215
|
+
- **Anything Param**: `/users/{name}`
|
|
216
|
+
- **Wildcard**: `/files/*`
|
|
423
217
|
|
|
424
|
-
####
|
|
218
|
+
#### Setting it Up
|
|
425
219
|
|
|
426
220
|
```jsx
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
221
|
+
import { createApp } from "@manyducks.co/dolla";
|
|
222
|
+
import { createRouter } from "@manyducks.co/dolla/router";
|
|
223
|
+
import { ThingIndex, ThingDetails, ThingEdit } from "./views.js";
|
|
430
224
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
+
});
|
|
440
239
|
|
|
441
|
-
|
|
442
|
-
|
|
240
|
+
const app = createApp(router);
|
|
241
|
+
app.mount(document.body);
|
|
443
242
|
```
|
|
444
243
|
|
|
445
|
-
####
|
|
244
|
+
#### Using It
|
|
446
245
|
|
|
447
|
-
|
|
448
|
-
function ExampleView(props, ctx) {
|
|
449
|
-
ctx.beforeConnect(() => {
|
|
450
|
-
// Do something before this view's DOM nodes are created.
|
|
451
|
-
});
|
|
246
|
+
Just use the `useRouter()` hook.
|
|
452
247
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
248
|
+
```jsx
|
|
249
|
+
import { useEffect } from "@manyducks.co/dolla";
|
|
250
|
+
import { useRouter } from "@manyducks.co/dolla/router";
|
|
456
251
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
252
|
+
function ThingDetails() {
|
|
253
|
+
const router = useRouter();
|
|
254
|
+
const { $params } = router; // get reactive params
|
|
460
255
|
|
|
461
|
-
|
|
462
|
-
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
console.log("Current thing ID:", $params().id);
|
|
463
258
|
});
|
|
464
259
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
#### Displaying Children
|
|
470
|
-
|
|
471
|
-
The context object has an `outlet` function that can be used to display children at a location of your choosing.
|
|
260
|
+
function goToNext() {
|
|
261
|
+
const nextId = $params().id + 1;
|
|
262
|
+
router.go(`/things/${nextId}`);
|
|
263
|
+
}
|
|
472
264
|
|
|
473
|
-
```js
|
|
474
|
-
function LayoutView(props, ctx) {
|
|
475
265
|
return (
|
|
476
|
-
<div
|
|
477
|
-
<
|
|
478
|
-
<
|
|
266
|
+
<div>
|
|
267
|
+
<p>Viewing thing #{$params().id}</p>
|
|
268
|
+
<button onClick={goToNext}>View Next Thing</button>
|
|
479
269
|
</div>
|
|
480
270
|
);
|
|
481
271
|
}
|
|
482
|
-
|
|
483
|
-
function ExampleView() {
|
|
484
|
-
// <h1> and <p> are displayed inside LayoutView's outlet.
|
|
485
|
-
return (
|
|
486
|
-
<LayoutView>
|
|
487
|
-
<h1>Hello</h1>
|
|
488
|
-
<p>This is inside the box.</p>
|
|
489
|
-
</LayoutView>
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
272
|
```
|
|
493
273
|
|
|
494
|
-
|
|
274
|
+
### A Built-in HTTP Client
|
|
495
275
|
|
|
496
|
-
|
|
276
|
+
Dolla has a little `http` client for API calls. It automatically parses JSON and has a sick middleware system.
|
|
497
277
|
|
|
498
278
|
```jsx
|
|
499
|
-
|
|
500
|
-
const { $someValue } = ctx.getStore(SomeStore);
|
|
279
|
+
import { http } from "@manyducks.co/dolla/http";
|
|
501
280
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
#### Routing
|
|
511
|
-
|
|
512
|
-
Dolla makes heavy use of client-side routing. You can define as many routes as you have views, and the URL
|
|
513
|
-
will determine which one the app shows at any given time. By building an app around routes, lots of things one expects
|
|
514
|
-
from a web app will just work; back and forward buttons, sharable URLs, bookmarks, etc.
|
|
515
|
-
|
|
516
|
-
Routes are matched by highest specificity regardless of the order they were registered.
|
|
517
|
-
This avoids some confusing situations that come up with order-based routers like that of `express`.
|
|
518
|
-
On the other hand, order-based routers can support regular expressions as patterns which Dolla's router cannot.
|
|
519
|
-
|
|
520
|
-
#### Route Patterns
|
|
521
|
-
|
|
522
|
-
Routes are defined with strings called patterns. A pattern defines the shape the URL path must match, with special
|
|
523
|
-
placeholders for variables that appear within the route. Values matched by those placeholders are parsed out and exposed
|
|
524
|
-
to your code (`router` store, `$params` readable). Below are some examples of patterns and how they work.
|
|
525
|
-
|
|
526
|
-
- Static: `/this/is/static` has no params and will match only when the route is exactly `/this/is/static`.
|
|
527
|
-
- Numeric params: `/users/{#id}/edit` has the named param `{#id}` which matches numbers only, such as `123` or `52`. The
|
|
528
|
-
resulting value will be parsed as a number.
|
|
529
|
-
- Generic params: `/users/{name}` has the named param `{name}` which matches anything in that position in the path. The
|
|
530
|
-
resulting value will be a string.
|
|
531
|
-
- Wildcard: `/users/*` will match anything beginning with `/users` and store everything after that in params
|
|
532
|
-
as `wildcard`. `*` is valid only at the end of a route.
|
|
533
|
-
|
|
534
|
-
Now, here are some route examples in the context of an app:
|
|
535
|
-
|
|
536
|
-
```js
|
|
537
|
-
import Dolla from "@manyducks.co/dolla";
|
|
538
|
-
import { PersonDetails, ThingIndex, ThingDetails, ThingEdit, ThingDelete } from "./views.js";
|
|
539
|
-
|
|
540
|
-
Dolla.router.setup({
|
|
541
|
-
routes: [
|
|
542
|
-
{ path: "/people/{name}", view: PersonDetails },
|
|
543
|
-
{
|
|
544
|
-
// A `null` component with subroutes acts as a namespace for those subroutes.
|
|
545
|
-
// Passing a view instead of `null` results in subroutes being rendered inside that view wherever `ctx.outlet()` is called.
|
|
546
|
-
path: "/things",
|
|
547
|
-
view: null,
|
|
548
|
-
routes: [
|
|
549
|
-
{ path: "/", view: ThingIndex }, // matches `/things`
|
|
550
|
-
{ path: "/{#id}", view: ThingDetails }, // matches `/things/{#id}`
|
|
551
|
-
{ path: "/{#id}/edit", view: ThingEdit }, // matches `/things/{#id}/edit`
|
|
552
|
-
{ path: "/{#id}/delete", view: ThingDelete }, // matches `/things/{#id}/delete`
|
|
553
|
-
],
|
|
554
|
-
},
|
|
555
|
-
],
|
|
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!
|
|
556
287
|
});
|
|
557
|
-
```
|
|
558
288
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
```js
|
|
564
|
-
function PersonDetails(props, ctx) {
|
|
565
|
-
// Info about the current route is exported as a set of Readables. Query params are also Writable through $$query:
|
|
566
|
-
const { $path, $pattern, $params, $query } = Dolla.router;
|
|
567
|
-
|
|
568
|
-
Dolla.router.back(); // Step back in the history to the previous route, if any.
|
|
569
|
-
Dolla.router.back(2); // Hit the back button twice.
|
|
289
|
+
const res = await http.get("/api/users");
|
|
290
|
+
console.log(res.body); // already parsed JSON, leggo
|
|
291
|
+
```
|
|
570
292
|
|
|
571
|
-
|
|
572
|
-
Dolla.router.forward(4); // Hit the forward button 4 times.
|
|
293
|
+
### Internationalization (i18n)
|
|
573
294
|
|
|
574
|
-
|
|
575
|
-
Dolla.router.go("https://www.example.com/another/site"); // Navigate to another domain entirely.
|
|
295
|
+
Wanna make your app speak different languages? We got you. Dolla's i18n stuff is super simple.
|
|
576
296
|
|
|
577
|
-
|
|
578
|
-
Dolla.router.go("/another/page", { prompt: true });
|
|
579
|
-
Dolla.router.go("/another/page", { prompt: "Are you sure you want to leave and go to /another/page?" });
|
|
580
|
-
Dolla.router.go("/another/page", { prompt: PromptView });
|
|
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.
|
|
581
298
|
|
|
582
|
-
|
|
583
|
-
|
|
299
|
+
```jsx
|
|
300
|
+
import { createApp, useSignal } from "@manyducks.co/dolla";
|
|
301
|
+
import { i18n, t } from "@manyducks.co/dolla/i18n";
|
|
584
302
|
|
|
585
|
-
|
|
586
|
-
return <
|
|
303
|
+
function CounterView() {
|
|
304
|
+
return <button>{t("buttonLabel")}</button>;
|
|
587
305
|
}
|
|
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));
|
|
588
319
|
```
|
|
589
320
|
|
|
590
|
-
##
|
|
321
|
+
## The Tea: How's Dolla Different?
|
|
591
322
|
|
|
592
|
-
|
|
593
|
-
// Middleware!
|
|
594
|
-
Dolla.http.use((request, next) => {
|
|
595
|
-
// Add auth header for all requests going to the API.
|
|
596
|
-
if (request.url.pathname.startsWith("/api")) {
|
|
597
|
-
request.headers.set("authorization", `Bearer ${authToken}`);
|
|
598
|
-
}
|
|
323
|
+
### vs. React
|
|
599
324
|
|
|
600
|
-
|
|
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.
|
|
601
329
|
|
|
602
|
-
|
|
330
|
+
### vs. Angular
|
|
603
331
|
|
|
604
|
-
|
|
605
|
-
|
|
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.
|
|
606
335
|
|
|
607
|
-
|
|
336
|
+
### vs. Svelte
|
|
608
337
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
338
|
+
- **It's Just JS**: Dolla is just functions and JSX. Svelte has its own `.svelte` file type and special syntax. Dolla just fits into the normal JS world.
|
|
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.
|
|
612
341
|
|
|
613
|
-
|
|
342
|
+
### vs. SolidJS
|
|
614
343
|
|
|
615
|
-
|
|
616
|
-
import Dolla, { html, t } from "@manyducks.co/dolla";
|
|
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.
|
|
617
345
|
|
|
618
|
-
|
|
619
|
-
const [$count, setCount] = Dolla.createState(0);
|
|
346
|
+
Choose **SolidJS** if you wanna build your car from scratch.
|
|
620
347
|
|
|
621
|
-
|
|
622
|
-
setCount((count) => count + 1);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
return html`
|
|
626
|
-
<div>
|
|
627
|
-
<p>Clicks: ${$count}</p>
|
|
628
|
-
<button onclick=${increment}>${t("buttonLabel")}</button>
|
|
629
|
-
</div>
|
|
630
|
-
`;
|
|
631
|
-
}
|
|
348
|
+
Choose **Dolla** if you wanna just get in and drive.
|
|
632
349
|
|
|
633
|
-
|
|
634
|
-
locale: "en",
|
|
635
|
-
translations: [
|
|
636
|
-
{ locale: "en", strings: { buttonLabel: "Click here to increment" } },
|
|
637
|
-
{ locale: "ja", strings: { buttonLabel: "ここに押して増加する" } },
|
|
638
|
-
],
|
|
639
|
-
});
|
|
350
|
+
---
|
|
640
351
|
|
|
641
|
-
|
|
642
|
-
```
|
|
352
|
+
For more detail [check out the Docs](./docs/index.md).
|
|
643
353
|
|
|
644
354
|
---
|
|
645
355
|
|
|
646
|
-
[🦆](https://www.manyducks.co)
|
|
356
|
+
[🦆 That's a lot of ducks.](https://www.manyducks.co)
|