blokd 0.1.0-beta.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 +28 -0
- package/dist/LICENSE +21 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +48 -0
- package/dist/client.js.map +1 -0
- package/dist/core.d.ts +25 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +206 -0
- package/dist/core.js.map +1 -0
- package/dist/dom.d.ts +16 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +140 -0
- package/dist/dom.js.map +1 -0
- package/dist/hono.d.ts +49 -0
- package/dist/hono.d.ts.map +1 -0
- package/dist/hono.js +310 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-runtime.d.ts +45 -0
- package/dist/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx-runtime.js +264 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/resume.d.ts +46 -0
- package/dist/resume.d.ts.map +1 -0
- package/dist/resume.js +231 -0
- package/dist/resume.js.map +1 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +232 -0
- package/dist/server.js.map +1 -0
- package/dist/vite.d.ts +20 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +421 -0
- package/dist/vite.js.map +1 -0
- package/package.json +92 -0
- package/src/client.ts +50 -0
- package/src/core.ts +234 -0
- package/src/dom.ts +142 -0
- package/src/hono.ts +368 -0
- package/src/index.ts +7 -0
- package/src/jsx-runtime.ts +306 -0
- package/src/resume.ts +274 -0
- package/src/server.ts +241 -0
- package/src/vite.ts +413 -0
package/src/core.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
export type Accessor<T> = () => T;
|
|
2
|
+
export type Setter<T> = {
|
|
3
|
+
(value: T): T;
|
|
4
|
+
(updater: (previous: T) => T): T;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type SignalOptions<T> = {
|
|
8
|
+
/**
|
|
9
|
+
* Custom equality comparator. Set to false to always notify subscribers.
|
|
10
|
+
*/
|
|
11
|
+
equals?: false | ((previous: T, next: T) => boolean);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type Source = Set<Computation>;
|
|
15
|
+
type CleanupFn = () => void;
|
|
16
|
+
|
|
17
|
+
type Owner = {
|
|
18
|
+
owner: Owner | null;
|
|
19
|
+
owned: Set<Computation | Owner>;
|
|
20
|
+
cleanups: CleanupFn[];
|
|
21
|
+
disposed: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type Computation = Owner & {
|
|
25
|
+
fn: () => void;
|
|
26
|
+
deps: Source[];
|
|
27
|
+
running: boolean;
|
|
28
|
+
stale: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let currentObserver: Computation | null = null;
|
|
32
|
+
let currentOwner: Owner | null = null;
|
|
33
|
+
let batchDepth = 0;
|
|
34
|
+
let effectsEnabled = true;
|
|
35
|
+
let flushing = false;
|
|
36
|
+
const queue = new Set<Computation>();
|
|
37
|
+
|
|
38
|
+
function createOwner(parent: Owner | null): Owner {
|
|
39
|
+
const owner: Owner = { owner: parent, owned: new Set(), cleanups: [], disposed: false };
|
|
40
|
+
parent?.owned.add(owner);
|
|
41
|
+
return owner;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function removeFromParent(owner: Owner): void {
|
|
45
|
+
owner.owner?.owned.delete(owner);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cleanupDeps(comp: Computation): void {
|
|
49
|
+
for (const source of comp.deps) source.delete(comp);
|
|
50
|
+
comp.deps.length = 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runCleanups(owner: Owner): void {
|
|
54
|
+
const cleanups = owner.cleanups.splice(0);
|
|
55
|
+
for (let i = cleanups.length - 1; i >= 0; i--) cleanups[i]!();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function disposeOwner(owner: Owner): void {
|
|
59
|
+
if (owner.disposed) return;
|
|
60
|
+
owner.disposed = true;
|
|
61
|
+
for (const child of Array.from(owner.owned)) disposeOwner(child);
|
|
62
|
+
owner.owned.clear();
|
|
63
|
+
if ('deps' in owner) cleanupDeps(owner as Computation);
|
|
64
|
+
runCleanups(owner);
|
|
65
|
+
queue.delete(owner as Computation);
|
|
66
|
+
removeFromParent(owner);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createComputation(fn: () => void, parent: Owner | null): Computation {
|
|
70
|
+
const comp: Computation = {
|
|
71
|
+
owner: parent,
|
|
72
|
+
owned: new Set(),
|
|
73
|
+
cleanups: [],
|
|
74
|
+
disposed: false,
|
|
75
|
+
fn,
|
|
76
|
+
deps: [],
|
|
77
|
+
running: false,
|
|
78
|
+
stale: false
|
|
79
|
+
};
|
|
80
|
+
parent?.owned.add(comp);
|
|
81
|
+
return comp;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function runComputation(comp: Computation): void {
|
|
85
|
+
if (comp.disposed || comp.running) return;
|
|
86
|
+
comp.stale = false;
|
|
87
|
+
cleanupDeps(comp);
|
|
88
|
+
runCleanups(comp);
|
|
89
|
+
for (const child of Array.from(comp.owned)) disposeOwner(child);
|
|
90
|
+
comp.owned.clear();
|
|
91
|
+
|
|
92
|
+
const previousObserver = currentObserver;
|
|
93
|
+
const previousOwner = currentOwner;
|
|
94
|
+
currentObserver = comp;
|
|
95
|
+
currentOwner = comp;
|
|
96
|
+
comp.running = true;
|
|
97
|
+
try {
|
|
98
|
+
comp.fn();
|
|
99
|
+
} finally {
|
|
100
|
+
comp.running = false;
|
|
101
|
+
currentObserver = previousObserver;
|
|
102
|
+
currentOwner = previousOwner;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function schedule(comp: Computation): void {
|
|
107
|
+
if (comp.disposed) return;
|
|
108
|
+
comp.stale = true;
|
|
109
|
+
queue.add(comp);
|
|
110
|
+
if (batchDepth === 0) flush();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function flush(): void {
|
|
114
|
+
if (batchDepth > 0 || flushing) return;
|
|
115
|
+
flushing = true;
|
|
116
|
+
try {
|
|
117
|
+
let guard = 0;
|
|
118
|
+
while (queue.size > 0) {
|
|
119
|
+
if (++guard > 100000) throw new Error('Reactive update limit exceeded. Check for cyclic effects.');
|
|
120
|
+
const pending = Array.from(queue);
|
|
121
|
+
queue.clear();
|
|
122
|
+
for (const comp of pending) runComputation(comp);
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
flushing = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function shouldNotify<T>(previous: T, next: T, options?: SignalOptions<T>): boolean {
|
|
130
|
+
if (options?.equals === false) return true;
|
|
131
|
+
const equals = options?.equals ?? Object.is;
|
|
132
|
+
return !equals(previous, next);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function signal<T>(initial: T, options?: SignalOptions<T>): [Accessor<T>, Setter<T>] {
|
|
136
|
+
let value = initial;
|
|
137
|
+
const subscribers: Source = new Set();
|
|
138
|
+
|
|
139
|
+
const read: Accessor<T> = () => {
|
|
140
|
+
if (currentObserver && !subscribers.has(currentObserver)) {
|
|
141
|
+
subscribers.add(currentObserver);
|
|
142
|
+
currentObserver.deps.push(subscribers);
|
|
143
|
+
}
|
|
144
|
+
return value;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const write: Setter<T> = ((next: T | ((previous: T) => T)) => {
|
|
148
|
+
const nextValue = typeof next === 'function' ? (next as (previous: T) => T)(value) : next;
|
|
149
|
+
if (!shouldNotify(value, nextValue, options)) return value;
|
|
150
|
+
value = nextValue;
|
|
151
|
+
for (const comp of Array.from(subscribers)) schedule(comp);
|
|
152
|
+
return value;
|
|
153
|
+
}) as Setter<T>;
|
|
154
|
+
|
|
155
|
+
return [read, write];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function effect(fn: () => void): () => void {
|
|
159
|
+
if (!effectsEnabled) return () => undefined;
|
|
160
|
+
const comp = createComputation(fn, currentOwner);
|
|
161
|
+
runComputation(comp);
|
|
162
|
+
return () => disposeOwner(comp);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function cleanup(fn: CleanupFn): void {
|
|
166
|
+
if (!currentOwner) throw new Error('cleanup() must be called inside root(), effect(), render(), hydrate(), or a component render scope.');
|
|
167
|
+
currentOwner.cleanups.push(fn);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function memo<T>(fn: () => T, options?: SignalOptions<T>): Accessor<T> {
|
|
171
|
+
if (!effectsEnabled) return () => fn();
|
|
172
|
+
const [value, setValue] = signal<T>(undefined as T, options);
|
|
173
|
+
effect(() => setValue(fn()));
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function batch<T>(fn: () => T): T {
|
|
178
|
+
batchDepth++;
|
|
179
|
+
try {
|
|
180
|
+
return fn();
|
|
181
|
+
} finally {
|
|
182
|
+
batchDepth--;
|
|
183
|
+
flush();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function untrack<T>(fn: () => T): T {
|
|
188
|
+
const previous = currentObserver;
|
|
189
|
+
currentObserver = null;
|
|
190
|
+
try {
|
|
191
|
+
return fn();
|
|
192
|
+
} finally {
|
|
193
|
+
currentObserver = previous;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function root<T>(fn: (dispose: () => void) => T): T {
|
|
198
|
+
const owner = createOwner(currentOwner);
|
|
199
|
+
const previousOwner = currentOwner;
|
|
200
|
+
currentOwner = owner;
|
|
201
|
+
try {
|
|
202
|
+
return fn(() => disposeOwner(owner));
|
|
203
|
+
} finally {
|
|
204
|
+
currentOwner = previousOwner;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function getOwner(): unknown {
|
|
209
|
+
return currentOwner;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function runWithOwner<T>(owner: unknown, fn: () => T): T {
|
|
213
|
+
const previousOwner = currentOwner;
|
|
214
|
+
currentOwner = owner as Owner | null;
|
|
215
|
+
try {
|
|
216
|
+
return fn();
|
|
217
|
+
} finally {
|
|
218
|
+
currentOwner = previousOwner;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function runWithEffectsDisabled<T>(fn: () => T): T {
|
|
223
|
+
const previous = effectsEnabled;
|
|
224
|
+
effectsEnabled = false;
|
|
225
|
+
try {
|
|
226
|
+
return fn();
|
|
227
|
+
} finally {
|
|
228
|
+
effectsEnabled = previous;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function dispose(owner: unknown): void {
|
|
233
|
+
if (owner && typeof owner === 'object' && 'owned' in owner) disposeOwner(owner as Owner);
|
|
234
|
+
}
|
package/src/dom.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { cleanup, effect, root } from './core.js';
|
|
2
|
+
import { dynamic, type Renderable } from './jsx-runtime.js';
|
|
3
|
+
|
|
4
|
+
export { render, hydrate, dynamic } from './jsx-runtime.js';
|
|
5
|
+
|
|
6
|
+
export type ShowProps<T> = {
|
|
7
|
+
when: T | false | null | undefined;
|
|
8
|
+
fallback?: Renderable;
|
|
9
|
+
children?: Renderable | ((value: NonNullable<T>) => Renderable);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function Show<T>(props: ShowProps<T>): Renderable {
|
|
13
|
+
const resolve = () => {
|
|
14
|
+
const when = props.when;
|
|
15
|
+
if (when) return typeof props.children === 'function'
|
|
16
|
+
? (props.children as (value: NonNullable<T>) => Renderable)(when as NonNullable<T>)
|
|
17
|
+
: props.children;
|
|
18
|
+
return props.fallback ?? null;
|
|
19
|
+
};
|
|
20
|
+
if (typeof document === 'undefined') return resolve();
|
|
21
|
+
return dynamic(resolve);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ForProps<T> = {
|
|
25
|
+
each: readonly T[] | null | undefined;
|
|
26
|
+
by?: (item: T, index: number) => string | number;
|
|
27
|
+
fallback?: Renderable;
|
|
28
|
+
children: (item: T, index: () => number) => Renderable;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function For<T>(props: ForProps<T>): Renderable {
|
|
32
|
+
if (typeof document === 'undefined') {
|
|
33
|
+
const items = props.each ?? [];
|
|
34
|
+
if (items.length === 0) return props.fallback ?? null;
|
|
35
|
+
return items.map((item, index) => props.children(item, () => index));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!props.by) {
|
|
39
|
+
return dynamic(() => {
|
|
40
|
+
const items = props.each ?? [];
|
|
41
|
+
if (items.length === 0) return props.fallback ?? null;
|
|
42
|
+
return items.map((item, index) => props.children(item, () => index));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const start = document.createComment('bd-for');
|
|
47
|
+
const end = document.createComment('/bd-for');
|
|
48
|
+
const fragment = document.createDocumentFragment();
|
|
49
|
+
fragment.append(start, end);
|
|
50
|
+
|
|
51
|
+
type Row = { key: string | number; nodes: Node[]; dispose: () => void; item: T; index: () => number; setIndex: (value: number) => void };
|
|
52
|
+
const rows = new Map<string | number, Row>();
|
|
53
|
+
|
|
54
|
+
const normalizeNodes = (value: Renderable): Node[] => {
|
|
55
|
+
const host = document.createDocumentFragment();
|
|
56
|
+
// Dynamic child creation is delegated through the JSX runtime by appending into a disposable root.
|
|
57
|
+
root(dispose => {
|
|
58
|
+
rowsDisposers.push(dispose);
|
|
59
|
+
const append = (v: Renderable): void => {
|
|
60
|
+
if (v === null || v === undefined || v === false || v === true) return;
|
|
61
|
+
if (Array.isArray(v)) { for (const n of v) append(n); return; }
|
|
62
|
+
if (v instanceof Node) { host.appendChild(v); return; }
|
|
63
|
+
host.appendChild(document.createTextNode(String(v)));
|
|
64
|
+
};
|
|
65
|
+
append(value);
|
|
66
|
+
});
|
|
67
|
+
return Array.from(host.childNodes);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let rowsDisposers: Array<() => void> = [];
|
|
71
|
+
|
|
72
|
+
const clear = () => {
|
|
73
|
+
for (const row of rows.values()) row.dispose();
|
|
74
|
+
rows.clear();
|
|
75
|
+
let node = start.nextSibling;
|
|
76
|
+
while (node && node !== end) {
|
|
77
|
+
const next = node.nextSibling;
|
|
78
|
+
node.parentNode?.removeChild(node);
|
|
79
|
+
node = next;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
effect(() => {
|
|
84
|
+
const items = props.each ?? [];
|
|
85
|
+
if (items.length === 0) {
|
|
86
|
+
clear();
|
|
87
|
+
if (props.fallback !== undefined) {
|
|
88
|
+
const fallbackNodes = normalizeNodes(props.fallback);
|
|
89
|
+
for (const node of fallbackNodes) end.parentNode?.insertBefore(node, end);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const nextKeys = new Set<string | number>();
|
|
95
|
+
const orderedRows: Row[] = [];
|
|
96
|
+
|
|
97
|
+
items.forEach((item, index) => {
|
|
98
|
+
const key = props.by!(item, index);
|
|
99
|
+
nextKeys.add(key);
|
|
100
|
+
let row = rows.get(key);
|
|
101
|
+
if (!row || row.item !== item) {
|
|
102
|
+
row?.dispose();
|
|
103
|
+
for (const node of row?.nodes ?? []) node.parentNode?.removeChild(node);
|
|
104
|
+
let currentIndex = index;
|
|
105
|
+
let disposeRow: () => void = () => {};
|
|
106
|
+
const nodes = root(dispose => {
|
|
107
|
+
disposeRow = dispose;
|
|
108
|
+
const rendered = props.children(item, () => currentIndex);
|
|
109
|
+
const host = document.createDocumentFragment();
|
|
110
|
+
const append = (v: Renderable): void => {
|
|
111
|
+
if (v === null || v === undefined || v === false || v === true) return;
|
|
112
|
+
if (Array.isArray(v)) { for (const child of v) append(child); return; }
|
|
113
|
+
if (v instanceof Node) { host.appendChild(v); return; }
|
|
114
|
+
host.appendChild(document.createTextNode(String(v)));
|
|
115
|
+
};
|
|
116
|
+
append(rendered);
|
|
117
|
+
return Array.from(host.childNodes);
|
|
118
|
+
});
|
|
119
|
+
row = { key, item, nodes, dispose: disposeRow, index: () => currentIndex, setIndex: value => { currentIndex = value; } };
|
|
120
|
+
rows.set(key, row);
|
|
121
|
+
} else {
|
|
122
|
+
row.setIndex(index);
|
|
123
|
+
}
|
|
124
|
+
orderedRows.push(row);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
for (const [key, row] of Array.from(rows.entries())) {
|
|
128
|
+
if (!nextKeys.has(key)) {
|
|
129
|
+
row.dispose();
|
|
130
|
+
for (const node of row.nodes) node.parentNode?.removeChild(node);
|
|
131
|
+
rows.delete(key);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const row of orderedRows) {
|
|
136
|
+
for (const node of row.nodes) end.parentNode?.insertBefore(node, end);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
cleanup(clear);
|
|
141
|
+
return fragment;
|
|
142
|
+
}
|