dalila 1.4.1 → 1.4.2

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 (60) hide show
  1. package/package.json +1 -1
  2. package/scripts/dev-server.cjs +154 -3
  3. package/dist/context/auto-scope.d.ts +0 -167
  4. package/dist/context/auto-scope.js +0 -381
  5. package/dist/context/context.d.ts +0 -111
  6. package/dist/context/context.js +0 -283
  7. package/dist/context/index.d.ts +0 -2
  8. package/dist/context/index.js +0 -2
  9. package/dist/context/raw.d.ts +0 -2
  10. package/dist/context/raw.js +0 -2
  11. package/dist/core/dev.d.ts +0 -7
  12. package/dist/core/dev.js +0 -14
  13. package/dist/core/for.d.ts +0 -42
  14. package/dist/core/for.js +0 -311
  15. package/dist/core/index.d.ts +0 -14
  16. package/dist/core/index.js +0 -14
  17. package/dist/core/key.d.ts +0 -33
  18. package/dist/core/key.js +0 -83
  19. package/dist/core/match.d.ts +0 -22
  20. package/dist/core/match.js +0 -175
  21. package/dist/core/mutation.d.ts +0 -55
  22. package/dist/core/mutation.js +0 -128
  23. package/dist/core/persist.d.ts +0 -63
  24. package/dist/core/persist.js +0 -371
  25. package/dist/core/query.d.ts +0 -72
  26. package/dist/core/query.js +0 -184
  27. package/dist/core/resource.d.ts +0 -299
  28. package/dist/core/resource.js +0 -924
  29. package/dist/core/scheduler.d.ts +0 -111
  30. package/dist/core/scheduler.js +0 -243
  31. package/dist/core/scope.d.ts +0 -74
  32. package/dist/core/scope.js +0 -171
  33. package/dist/core/signal.d.ts +0 -88
  34. package/dist/core/signal.js +0 -451
  35. package/dist/core/store.d.ts +0 -130
  36. package/dist/core/store.js +0 -234
  37. package/dist/core/virtual.d.ts +0 -26
  38. package/dist/core/virtual.js +0 -277
  39. package/dist/core/watch-testing.d.ts +0 -13
  40. package/dist/core/watch-testing.js +0 -16
  41. package/dist/core/watch.d.ts +0 -81
  42. package/dist/core/watch.js +0 -353
  43. package/dist/core/when.d.ts +0 -23
  44. package/dist/core/when.js +0 -124
  45. package/dist/index.d.ts +0 -4
  46. package/dist/index.js +0 -4
  47. package/dist/internal/watch-testing.d.ts +0 -1
  48. package/dist/internal/watch-testing.js +0 -8
  49. package/dist/router/index.d.ts +0 -1
  50. package/dist/router/index.js +0 -1
  51. package/dist/router/route.d.ts +0 -23
  52. package/dist/router/route.js +0 -48
  53. package/dist/router/router.d.ts +0 -23
  54. package/dist/router/router.js +0 -169
  55. package/dist/runtime/bind.d.ts +0 -59
  56. package/dist/runtime/bind.js +0 -336
  57. package/dist/runtime/index.d.ts +0 -10
  58. package/dist/runtime/index.js +0 -9
  59. package/dist/simple.d.ts +0 -11
  60. package/dist/simple.js +0 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -250,17 +250,168 @@ function injectBindings(html, requestPath) {
250
250
  }
251
251
  </script>`;
252
252
 
253
- // For user projects, just inject the import map (user provides their own script)
253
+ // For user projects, inject import map + HMR script
254
254
  if (!isDalilaRepo) {
255
255
  let output = addLoadingAttributes(html);
256
256
 
257
257
  // FOUC prevention CSS
258
258
  const foucPreventionCSS = ` <style>[d-loading]{visibility:hidden}</style>`;
259
259
 
260
+ // Smart HMR script for user projects
261
+ const hmrScript = `
262
+ <script type="module">
263
+ // HMR: Use native bind() HMR support
264
+ let disposeBinding = null;
265
+
266
+ const rebind = async () => {
267
+ const hmrContext = window.__dalila_hmr_context;
268
+ if (!hmrContext) {
269
+ console.warn('[HMR] Cannot rebind: bind() not called yet');
270
+ return;
271
+ }
272
+
273
+ try {
274
+ const { bind } = await import('${dalilaPath}/runtime/index.js');
275
+ const { root, ctx, options } = hmrContext;
276
+
277
+ // Dispose old bindings
278
+ if (disposeBinding) {
279
+ try { disposeBinding(); } catch (e) {}
280
+ }
281
+
282
+ // Rebind with preserved context
283
+ disposeBinding = bind(root, ctx, options);
284
+ console.log('[HMR] Rebound successfully');
285
+ } catch (e) {
286
+ console.error('[HMR] Rebind failed:', e);
287
+ }
288
+ };
289
+
290
+ const patchNode = (current, next) => {
291
+ if (!current || !next) return;
292
+
293
+ if (current.nodeType !== next.nodeType) {
294
+ current.replaceWith(next.cloneNode(true));
295
+ return;
296
+ }
297
+
298
+ if (current.nodeType === Node.TEXT_NODE) {
299
+ if (current.data !== next.data) current.data = next.data;
300
+ return;
301
+ }
302
+
303
+ if (current.nodeType === Node.ELEMENT_NODE) {
304
+ if (current.tagName !== next.tagName) {
305
+ current.replaceWith(next.cloneNode(true));
306
+ return;
307
+ }
308
+
309
+ const currentAttrs = current.getAttributeNames();
310
+ const nextAttrs = next.getAttributeNames();
311
+
312
+ currentAttrs.forEach((name) => {
313
+ if (!nextAttrs.includes(name)) current.removeAttribute(name);
314
+ });
315
+ nextAttrs.forEach((name) => {
316
+ const value = next.getAttribute(name);
317
+ if (current.getAttribute(name) !== value) {
318
+ if (value === null) {
319
+ current.removeAttribute(name);
320
+ } else {
321
+ current.setAttribute(name, value);
322
+ }
323
+ }
324
+ });
325
+
326
+ const currentChildren = Array.from(current.childNodes);
327
+ const nextChildren = Array.from(next.childNodes);
328
+ const max = Math.max(currentChildren.length, nextChildren.length);
329
+
330
+ for (let i = 0; i < max; i += 1) {
331
+ const currentChild = currentChildren[i];
332
+ const nextChild = nextChildren[i];
333
+
334
+ if (!currentChild && nextChild) {
335
+ current.appendChild(nextChild.cloneNode(true));
336
+ } else if (currentChild && !nextChild) {
337
+ currentChild.remove();
338
+ } else if (currentChild && nextChild) {
339
+ patchNode(currentChild, nextChild);
340
+ }
341
+ }
342
+ }
343
+ };
344
+
345
+ const refreshStyles = () => {
346
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
347
+ links.forEach((link) => {
348
+ const url = new URL(link.href);
349
+ url.searchParams.set('v', String(Date.now()));
350
+ link.href = url.toString();
351
+ });
352
+ };
353
+
354
+ const refreshMarkup = async () => {
355
+ const res = await fetch(window.location.pathname, { cache: 'no-store' });
356
+ const html = await res.text();
357
+ const doc = new DOMParser().parseFromString(html, 'text/html');
358
+
359
+ // Find root element (prefer #app, fallback to body)
360
+ const nextRoot = doc.querySelector('#app') || doc.body;
361
+ const currentRoot = document.querySelector('#app') || document.body;
362
+
363
+ if (nextRoot && currentRoot) {
364
+ patchNode(currentRoot, nextRoot);
365
+ console.log('[HMR] HTML patched');
366
+
367
+ // Rebind after patching
368
+ rebind();
369
+ }
370
+ };
371
+
372
+ if (window.__dalila_hmr) {
373
+ window.__dalila_hmr.close();
374
+ }
375
+ const hmr = new EventSource('/__hmr');
376
+ window.__dalila_hmr = hmr;
377
+
378
+ hmr.addEventListener('update', async (event) => {
379
+ let file = '';
380
+ try {
381
+ const payload = JSON.parse(event.data || '{}');
382
+ file = payload.file || '';
383
+ } catch {
384
+ file = '';
385
+ }
386
+
387
+ console.log('[HMR] File changed:', file);
388
+
389
+ if (file.endsWith('.css')) {
390
+ refreshStyles();
391
+ console.log('[HMR] CSS reloaded');
392
+ return;
393
+ }
394
+
395
+ if (file.endsWith('.html')) {
396
+ await refreshMarkup();
397
+ return;
398
+ }
399
+
400
+ // For TS/JS files, full reload is needed to re-import modules
401
+ console.log('[HMR] Reloading page...');
402
+ location.reload();
403
+ });
404
+
405
+ hmr.onerror = () => {
406
+ console.warn('[HMR] Connection lost, will retry...');
407
+ };
408
+ </script>`;
409
+
410
+ // Inject HMR + import map in HEAD (HMR must load before user scripts)
260
411
  if (output.includes('</head>')) {
261
- output = output.replace('</head>', `${foucPreventionCSS}\n${importMap}\n</head>`);
412
+ output = output.replace('</head>', `${foucPreventionCSS}\n${importMap}\n${hmrScript}\n</head>`);
262
413
  } else {
263
- output = `${foucPreventionCSS}\n${importMap}\n${output}`;
414
+ output = `${foucPreventionCSS}\n${importMap}\n${hmrScript}\n${output}`;
264
415
  }
265
416
 
266
417
  return output;
@@ -1,167 +0,0 @@
1
- /**
2
- * Auto-scoping Context API.
3
- *
4
- * Goal:
5
- * - Remove the need for explicit `withScope()` in common app code.
6
- *
7
- * Auto-scope rules:
8
- * - `provide()` outside a scope creates a global root scope (warns once in dev).
9
- * - `inject()` outside a scope never creates global state; it only reads an existing global root.
10
- *
11
- * Rationale:
12
- * - Apps often want "global-ish" DI without Provider pyramids.
13
- * - Still keep things lifecycle-safe: the global scope can be disposed on page unload.
14
- */
15
- import { type Scope } from '../core/scope.js';
16
- import { type Signal } from '../core/signal.js';
17
- import { createContext, type ContextToken, type TryInjectResult } from './context.js';
18
- export type AutoScopePolicy = "warn" | "throw" | "silent";
19
- export declare function setAutoScopePolicy(policy: AutoScopePolicy): void;
20
- /**
21
- * Provide with auto-scope.
22
- *
23
- * Semantics:
24
- * - Inside a scope: behaves like the raw `provide()`.
25
- * - Outside a scope: creates/uses the global root scope (warns once in dev).
26
- */
27
- export declare function provide<T>(token: ContextToken<T>, value: T): void;
28
- /**
29
- * Provide explicitly in the global root scope.
30
- *
31
- * Semantics:
32
- * - Always uses the detached global scope (creates if needed).
33
- * - Never warns.
34
- */
35
- export declare function provideGlobal<T>(token: ContextToken<T>, value: T): void;
36
- /**
37
- * Inject with auto-scope.
38
- *
39
- * Semantics:
40
- * - Inside a scope: behaves like raw `inject()`, but throws a more descriptive error.
41
- * - Outside a scope:
42
- * - If a global scope exists: reads from it (safe-by-default).
43
- * - If no global scope exists: throws with guidance (does NOT create global state).
44
- */
45
- export declare function inject<T>(token: ContextToken<T>): T;
46
- /**
47
- * Try to inject a context value with auto-scope.
48
- *
49
- * Semantics:
50
- * - Returns { found: true, value } when the token is found.
51
- * - Returns { found: false, value: undefined } when not found.
52
- * - Works both inside and outside scopes (reads global if exists).
53
- */
54
- export declare function tryInject<T>(token: ContextToken<T>): TryInjectResult<T>;
55
- /**
56
- * Inject explicitly from the global root scope.
57
- *
58
- * Semantics:
59
- * - Reads only the global root scope.
60
- * - Throws if no global scope exists yet.
61
- */
62
- export declare function injectGlobal<T>(token: ContextToken<T>): T;
63
- /**
64
- * Convenience helper: create a scope, run `fn` inside it, and return `{ result, dispose }`.
65
- *
66
- * Semantics:
67
- * - If `fn` throws, the scope is disposed and the error is rethrown.
68
- * - Caller owns disposal.
69
- */
70
- export declare function scope<T>(fn: () => T): {
71
- result: T;
72
- dispose: () => void;
73
- };
74
- /**
75
- * Provider helper that bundles:
76
- * - a dedicated provider scope
77
- * - a value created by `setup()`
78
- * - registration via `provide()`
79
- *
80
- * Useful for "feature modules" that want to expose a typed dependency with explicit lifetime.
81
- */
82
- export declare function createProvider<T>(token: ContextToken<T>, setup: () => T): {
83
- create: () => {
84
- value: T;
85
- dispose: () => void;
86
- };
87
- use: () => T;
88
- };
89
- /**
90
- * Returns the global root scope (if it exists).
91
- * Intended for debugging/advanced usage only.
92
- */
93
- export declare function getGlobalScope(): Scope | null;
94
- /**
95
- * Returns true if a global root scope exists.
96
- */
97
- export declare function hasGlobalScope(): boolean;
98
- /**
99
- * Resets the global root scope.
100
- * Intended for tests to ensure isolation between runs.
101
- */
102
- export declare function resetGlobalScope(): void;
103
- /**
104
- * Creates a reactive context that wraps a signal.
105
- *
106
- * This is the recommended way to share reactive state across scopes.
107
- * It combines the hierarchical lookup of Context with the reactivity of Signals.
108
- *
109
- * Example:
110
- * ```ts
111
- * const Theme = createSignalContext("theme", "dark");
112
- *
113
- * scope(() => {
114
- * // Parent scope: create and provide the signal
115
- * const theme = Theme.provide();
116
- *
117
- * effect(() => {
118
- * console.log("Theme:", theme()); // Reactive!
119
- * });
120
- *
121
- * theme.set("light"); // Updates propagate to all consumers
122
- * });
123
- *
124
- * // Child scope somewhere in the tree
125
- * scope(() => {
126
- * const theme = Theme.inject(); // Get the signal from parent
127
- * console.log(theme()); // "light"
128
- * });
129
- * ```
130
- */
131
- export interface SignalContext<T> {
132
- /**
133
- * Create a signal with the initial value and provide it in the current scope.
134
- * Returns the signal for immediate use.
135
- *
136
- * @param initialValue - Override the default initial value (optional)
137
- */
138
- provide: (initialValue?: T) => Signal<T>;
139
- /**
140
- * Inject the signal from an ancestor scope.
141
- * Throws if no provider exists in the scope hierarchy.
142
- */
143
- inject: () => Signal<T>;
144
- /**
145
- * Try to inject the signal from an ancestor scope.
146
- * Returns { found: true, signal } or { found: false, signal: undefined }.
147
- */
148
- tryInject: () => {
149
- found: true;
150
- signal: Signal<T>;
151
- } | {
152
- found: false;
153
- signal: undefined;
154
- };
155
- /**
156
- * The underlying context token (for advanced use cases).
157
- */
158
- token: ContextToken<Signal<T>>;
159
- }
160
- /**
161
- * Create a reactive context that wraps a signal.
162
- *
163
- * @param name - Debug name for the context
164
- * @param defaultValue - Default initial value for the signal
165
- */
166
- export declare function createSignalContext<T>(name: string, defaultValue: T): SignalContext<T>;
167
- export { createContext };