Skip to content

Commit

Permalink
Merge branch 'main' into hydro
Browse files Browse the repository at this point in the history
  • Loading branch information
WebReflection committed Apr 13, 2024
2 parents ed8e7f9 + ae5ce9c commit 8fc5b96
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 157 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ cjs/*
dom.js
esm/init*.js
init*.js
worker.js
keyed.js
!esm/keyed.js
!esm/dom/keyed.js
Expand All @@ -27,4 +28,4 @@ signal.js
!esm/signal.js
!esm/render/signal.js
preactive.js
!test/preactive.js
!test/preactive.js
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* **[uhtml/keyed](https://cdn.jsdelivr.net/npm/uhtml/keyed.js)** with extras `{ Hole, render, html, svg, htmlFor, svgFor, attr }`, providing keyed utilities - read [keyed or not ?](https://webreflection.github.io/uhtml/#keyed-or-not-) paragraph to know more
* **[uhtml/node](https://cdn.jsdelivr.net/npm/uhtml/node.js)** with *same default* exports but it's for *one-off* nodes creation only so that no cache or updates are available and it's just an easy way to hook *uhtml* into your existing project for DOM creation (not manipulation!)
* **[uhtml/init](https://cdn.jsdelivr.net/npm/uhtml/init.js)** which returns a `document => uhtml/keyed` utility that can be bootstrapped with `uhtml/dom`, [LinkeDOM](https://github.com/WebReflection/linkedom), [JSDOM](https://github.com/jsdom/jsdom) for either *SSR* or *Workers* support
* **uhtml/ssr** which exports an utility that both SSR or Workers can use to parse and serve documents. This export provides same keyed utilities except the keyed feature is implicitly disabled as that's usually not desirable at all for SSR or rendering use cases, actually just an overhead. This might change in the future but for now I want to benchmark and see how competitive is `uhtml/ssr` out there. The `uhtml/dom` is also embedded in this export because the `Comment` class needs an override to produce a super clean output (at least until hydro story is up and running).
* **[uhtml/dom](https://cdn.jsdelivr.net/npm/uhtml/dom.js)** which returns a specialized *uhtml* compliant DOM environment that can be passed to the `uhtml/init` export to have 100% same-thing running on both client or Web Worker / Server. This entry exports `{ Document, DOMParser }` where the former can be used to create a new *document* while the latter one can parse well formed HTML or SVG content and return the document out of the box.
* **[uhtml/reactive](https://cdn.jsdelivr.net/npm/uhtml/reactive.js)** which allows usage of symbols within the optionally *keyed* render function. The only difference with other exports, beside exporting a `reactive` field instead of `render`, so that `const render = reactive(effect)` creates a reactive render per each library, is that the `render(where, () => what)`, with a function as second argument is mandatory when the rendered stuff has signals in it, otherwise these can't side-effect properly.
* **[uhtml/signal](https://cdn.jsdelivr.net/npm/uhtml/signal.js)** is an already bundled `uhtml/reactive` with `@webreflection/signal` in it, so that its `render` exported function is already reactive. This is the smallest possible bundle as it's ~3.3Kb but it's not nearly as complete, in terms of features, as *preact* signals are.
Expand All @@ -47,7 +48,7 @@ const {
### uhtml/preactive example

```js
import { render, html, signal } from 'uhtml/preactive';
import { render, html, signal, detach } from 'uhtml/preactive';

const count = signal(0);

Expand All @@ -56,4 +57,9 @@ render(document.body, () => html`
Clicks: ${count.value}
</button>
`);
```

// stop reacting to signals in the future
setTimeout(() => {
detach(document.body);
}, 10000);
```
7 changes: 5 additions & 2 deletions esm/reactive.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Hole, html, svg, htmlFor, svgFor, attr } from './keyed.js';

import reactive from './render/reactive.js';
import { attach, detach } from './render/reactive.js';

export { Hole, reactive, html, svg, htmlFor, svgFor, attr };
export { Hole, attach, detach, html, svg, htmlFor, svgFor, attr };

// TODO: mostly backward compatibility ... should this change?
export { attach as reactive };
6 changes: 3 additions & 3 deletions esm/reactive/preact.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from '@preact/signals-core';
export { Hole, html, svg, htmlFor, svgFor, attr } from '../reactive.js';
export { Hole, html, svg, htmlFor, svgFor, detach, attr } from '../reactive.js';

import { effect } from '@preact/signals-core';
import { reactive } from '../reactive.js';
export const render = reactive(effect);
import { attach } from '../reactive.js';
export const render = attach(effect);
6 changes: 3 additions & 3 deletions esm/reactive/signal.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from '@webreflection/signal';
export { Hole, html, svg, htmlFor, svgFor, attr } from '../reactive.js';
export { Hole, html, svg, htmlFor, svgFor, detach, attr } from '../reactive.js';

import { effect } from '@webreflection/signal';
import { reactive } from '../reactive.js';
export const render = reactive(effect);
import { attach } from '../reactive.js';
export const render = attach(effect);
46 changes: 30 additions & 16 deletions esm/render/reactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ const effects = new WeakMap;
*/
const onGC = dispose => dispose();

export default effect => {
let remove = true;

/**
* @param {Function} effect the reactive `effect` callback provided by a 3rd party library.
* @returns
*/
export const attach = effect => {
/**
* Render with smart updates within a generic container.
* If the `what` is a function, it automatically create
Expand All @@ -24,20 +30,28 @@ export default effect => {
* @returns {T}
*/
return (where, what) => {
let dispose = effects.get(where);
if (dispose) {
drop(dispose);
dispose();
}
if (typeof what === 'function') {
const wr = new WeakRef(where);
dispose = effect(() => { render(wr.deref(), what(), false) });
effects.set(where, dispose);
return create(dispose, onGC, { return: where });
}
else {
effects.delete(where);
return render(where, what, false);
}
remove = typeof what !== 'function';
detach(where);

if (remove) return render(where, what, false);
remove = true;

const wr = new WeakRef(where);
const dispose = effect(() => { render(wr.deref(), what(), false) });
effects.set(where, dispose);
return create(dispose, onGC, { return: where });
};
};

/**
* Allow manual cleanup of subscribed signals.
* @param {Element} where a reference container previously used to render signals.
*/
export const detach = where => {
const dispose = effects.get(where);
if (dispose) {
if (remove) effects.delete(where);
drop(dispose);
dispose();
}
};
1 change: 1 addition & 0 deletions esm/render/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const known = new WeakMap;
* @template T
* @param {T} where the DOM node where to render content
* @param {(() => Hole) | Hole} what the hole to render
* @param {boolean} check does a `typeof` check (internal usage).
* @returns
*/
export default (where, what, check) => {
Expand Down
3 changes: 2 additions & 1 deletion esm/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ export const gPD = (ref, prop) => {
return desc;
};


/* c8 ignore start */
/**
* @param {DocumentFragment} content
* @param {number[]} path
* @returns {Element}
*/
export const find = (content, path) => path.reduceRight(childNodesIndex, content);
const childNodesIndex = (node, i) => node.childNodes[i];
/* c8 ignore stop */
Loading

0 comments on commit 8fc5b96

Please sign in to comment.