|
16 | 16 | <b>purify.js</b> is a 1.0kB <i>(minified, gzipped)</i> 1.0kB DOM utility library, focusing on building reactive UI. 🚀 |
17 | 17 | </p> |
18 | 18 |
|
19 | | -# Features 🌟🚀 |
| 19 | +## Features 🌟🚀 |
20 | 20 |
|
21 | 21 | - 🔥 **Keeps you close to the DOM.** |
22 | 22 | - ✍️ `HTMLElement` builder allows you to differentiate between attributes and properties. |
23 | 23 | - 🌐 Builder doesn't only work with `HTMLElement`(s) but works with any `Node` instance including `ShadowRoot`, `DocumentFragment`, |
24 | 24 | `Document`... any `Node` type, including future ones. |
25 | 25 | - 🎩 Builder converts existing methods on the `Node` instance to builder pattern with `Proxy`. |
26 | 26 | - ⚡ **Signal implementation that makes sense and useable.** |
27 | | -- 🧙 **Signals are extendable,** allowing chaining with utilities like .pipe() and .derive() to build custom workflows.. |
| 27 | +- 🧙 **Signals are extendable,** allowing chaining with utilities like `.pipe()` and `.derive()` to build custom workflows. |
28 | 28 | - ✂️ Allows direct DOM manipulation. |
29 | 29 | - 📁 No special file extensions. |
30 | 30 | - 🔧 Only deal with `.ts` files, so use it with any existing formatting, linting, and other tools. |
31 | | -- ⚡ **No extra LSP and IDE extensions/plugins:** fast IDE responses, autocompletion, and no weird framework specific LSP issues. |
| 31 | +- ⚡ **No extra LSP and IDE extensions/plugins:** fast IDE responses, autocompletion, and no weird framework-specific LSP issues. |
32 | 32 | - ✅ **All verifiable TypeScript code.** |
33 | 33 |
|
34 | | ---- |
35 | | - |
36 | 34 | ## Compare 📏⚖️ |
37 | 35 |
|
38 | 36 | ### Size ⚡📊 |
|
47 | 45 | | ReactDOM 18.2.0 | 130.2kB | 42kB | |
48 | 46 | | Angular 17.1.0 | 310kB | 104kB | |
49 | 47 |
|
50 | | ---- |
51 | | - |
52 | 48 | ## Installation and Docs 📦🍙 |
53 | 49 |
|
54 | | -[jsr.io/@purifyjs/core](https://jsr.io/@purifyjs/core). |
| 50 | +[jsr.io/@purifyjs/core](https://jsr.io/@purifyjs/core) |
55 | 51 |
|
56 | | ---- |
| 52 | +## Guide 📖🥡 |
| 53 | + |
| 54 | +Coming soon. |
57 | 55 |
|
58 | 56 | ## Examples 🍤 |
59 | 57 |
|
@@ -188,97 +186,96 @@ class CounterElement extends WithLifecycle(HTMLElement) { |
188 | 186 | document.body.append(App().$node); |
189 | 187 | ``` |
190 | 188 |
|
191 | | -## Guide 📖🥡 |
192 | | - |
193 | | -Coming soon. |
194 | | - |
195 | 189 | ## Why Not JSX Templating? 🤔🍕 |
196 | 190 |
|
197 | 191 | - **Lack of Type Safety**: An `<img>` element created with JSX cannot have the `HTMLImageElement` type because all JSX elements must return |
198 | | - the same type. This causes issues if you expect a `HTMLImageElement` some where in the code but all JSX returns is `HTMLElement` or |
199 | | - something like `JSX.Element`. Also, it has some other issues related to the generics, discriminated unions and more. |
| 192 | + the same type. This causes issues if you expect an `HTMLImageElement` somewhere in the code but all JSX returns is `HTMLElement` or |
| 193 | + `JSX.Element`. It also has issues with generics, discriminated unions, and more. |
200 | 194 |
|
201 | 195 | - **Build Step Required**: JSX necessitates a build step, adding complexity to the development workflow. In contrast, **purify.js** avoids |
202 | 196 | this, enabling a simpler and more streamlined development process by working directly with native JavaScript and TypeScript. |
203 | 197 |
|
204 | | -- **Attributes vs. Properties**: In **purify.js**, I can differentiate between attributes and properties of an element while building it, |
| 198 | +- **Attributes vs. Properties**: In **purify.js**, you can differentiate between attributes and properties of an element while building it, |
205 | 199 | which is not currently possible with JSX. This distinction enhances clarity and control when defining element characteristics. |
206 | 200 |
|
207 | 201 | JSX is not part of this library natively, but a wrapper can be made quite easily. |
208 | 202 |
|
209 | 203 | ## Limitations ⚠️🦀 |
210 | 204 |
|
211 | | -- Since I use extended custom elements, safari doesn't support this yet, so if you care about safari for some reasons, use |
212 | | - [ungap/custom-elements](https://github.com/ungap/custom-elements) polyfill. You can follow support at |
| 205 | +- Since purify.js uses extended custom elements, **Safari doesn’t support this yet**. If you care about Safari for some reason, use the |
| 206 | + [ungap/custom-elements](https://github.com/ungap/custom-elements) polyfill. You can follow support status at |
213 | 207 | [caniuse](https://caniuse.com/mdn-html_global_attributes_is). |
214 | 208 |
|
215 | | - But I don't recommend that you support Safari.<br> _Don't suffer for Safari, let the Safari users suffer_ |
| 209 | + But **I don’t recommend that you support Safari.**\ |
| 210 | + _Don't suffer for Safari, let Safari users suffer._ |
216 | 211 |
|
217 | 212 | ## Future 🔮🦀 |
218 | 213 |
|
219 | | -- Right now, when a `Signal` is connected to DOM via `Builder`, we update all of the children of the `ParentNode` with |
| 214 | +- Right now, when a `Signal` is connected to the DOM via `Builder`, it updates all children of the `ParentNode` with |
220 | 215 | `ParentNode.prototype.replaceChildren()`. |
221 | 216 |
|
222 | | - This is obviously not that great, previously at `0.1.6` I was using a `<div>` element with the style `display:contents` to wrap a rendered |
223 | | - `Signal` on the DOM. This was also allowing me to follow it's lifecyle via `connectedCallback`/`disconnectedCallback` which was allowing |
224 | | - me to follow or unfollow the `Signal`, making cleanup easier. |
| 217 | + This is obviously not great. In version `0.1.6`, I was using a `<div>` element with `display:contents` to wrap a rendered `Signal` in the |
| 218 | + DOM. This allowed tracking its lifecycle via `connectedCallback`/`disconnectedCallback`, making cleanup easier. |
225 | 219 |
|
226 | | - But since we wrap it with an `HTMLElement` it was causing problems with CSS selectors, since now each `Signal` is an `HTMLElement` on the |
227 | | - DOM. |
| 220 | + However, wrapping it with an `HTMLElement` caused **CSS selector issues**, since each `Signal` became an actual `HTMLElement`. |
228 | 221 |
|
229 | | - So at `0.2.0` I made it so that all children of the `ParentNode` updates when a `Signal` child changes. Tho this issue can be escaped by |
230 | | - seperating things while writing the code. Or make use of things like `.replaceChild()`. Since all support `Signal`(s) now. |
| 222 | + So, in version `0.2.0`, I made it so that **all children** of a `ParentNode` update when a `Signal` child changes. This issue can be |
| 223 | + managed by structuring code carefully or using `.replaceChild()`, since all nodes now support `Signal`(s). |
231 | 224 |
|
232 | | - You might be saying "Why not just use comment nodes?": Yes, creating ranges with comment nodes is the traditional solution to this issue. |
233 | | - But it's not a native ranging solution, and the frameworks that use it break as soon as you mutate the DOM without the framework, which is |
234 | | - against the philosophy of the library. |
| 225 | + Some might ask, _"Why not just use comment nodes?"_ Yes, using comment nodes for tracking ranges is a traditional solution. But it’s not |
| 226 | + **a native ranging solution**, and frameworks that rely on it **break if the DOM is mutated manually**, which goes against this library’s |
| 227 | + philosophy. |
235 | 228 |
|
236 | | - So to solve the core of this issue JS needs a real `DocumentFragment` with persistent children. |
| 229 | + **The real solution?** JavaScript needs a **real** `DocumentFragment` with persistent children. |
237 | 230 |
|
238 | | - This proposal might solve this issue: |
| 231 | + A relevant proposal:\ |
239 | 232 | [DOM#739 Proposal: a DocumentFragment whose nodes do not get removed once inserted](https://github.com/whatwg/dom/issues/736). |
240 | 233 |
|
241 | | - In the proposal they propose making the fragment undetactable with `childNodes` or `children` which I am against and don't like at all. |
242 | | - `DocumentFragment` should be a `ParentNode` should have it's own children, and can be `ChildNode` of other `ParentNode`. Normal hierarchy, |
243 | | - no trasparency other than CSS. |
| 234 | + However, they propose making the fragment **undetectable** via `childNodes` or `children`, which I don’t support. A `DocumentFragment` |
| 235 | + should be a `ParentNode` with its own children, and it should behave hierarchically like any other `ParentNode`. |
244 | 236 |
|
245 | | - But it's a good start, but just by having a real, working as intended, `DocumentFragment` we are not done. |
| 237 | + But it’s a start. However, just **having a working DocumentFragment is not enough**. |
246 | 238 |
|
247 | | - Which brings be to the next point. |
| 239 | +- We also need a **native, synchronous, and easy way to follow the lifecycle of any `ChildNode`** (or at least `Element` and the proposed |
| 240 | + persistent `DocumentFragment`). |
248 | 241 |
|
249 | | -- We also need a native, sync and easy to use way to follow lifecycle of any DOM `ChildNode`, or at least all `Element` and this new |
250 | | - persistent `DocumentFragment`. Because without a lifecycle feature we can't bind a `Signal` to the DOM, start, stop/cleanup them |
251 | | - automatically. |
| 242 | + An open issue on this:\ |
| 243 | + [DOM#533 Make it possible to observe connected-ness of a node](https://github.com/whatwg/dom/issues/533). |
252 | 244 |
|
253 | | - An issue is open here [DOM#533 Make it possible to observe connected-ness of a node](https://github.com/whatwg/dom/issues/533). |
| 245 | + Right now, **Custom Elements are the only sync way** to track element lifecycle. This is why **purify.js heavily relies on them**. We |
| 246 | + auto-create Custom Elements via the `tags` proxy and `WithLifecycle` `HTMLElement` mixin. |
254 | 247 |
|
255 | | - But also, DOM already has a sync way to follow lifecycle of custom `HTMLElement`(s). And since this is the only way, at this time we |
256 | | - heavily relay on that. Currently we use auto created Custom Elements via `tags` proxy and `WithLifecycle` `HTMLElement` mixin. And allow |
257 | | - `Signal` related things only on those elements. |
| 248 | +- If the above feature is not introduced soon, we also keep an eye on this proposal:\ |
| 249 | + [webcomponents#1029 Proposal: Custom attributes for all elements, enhancements for more complex use cases](https://github.com/WICG/webcomponents/issues/1029). |
258 | 250 |
|
259 | | -- If this feature above doesn't come sooner we also keep an eye of this other proposal which has more attraction: |
260 | | - [webcomponents#1029 Proposal: Custom attributes for all elements, enhancements for more complex use cases](https://github.com/WICG/webcomponents/issues/1029) |
| 251 | + This **doesn’t solve the DocumentFragment issue** but improves and modularizes `HTMLElement` lifecycles. |
261 | 252 |
|
262 | | - This proposal doesn't fix the issue with `DocumentFragment`(s), but improves and makes `HTMLElement` based lifecycles more modular and DX |
263 | | - friendly. |
| 253 | + Currently, we use a mixin function called `WithLifecycle`, like this: |
264 | 254 |
|
265 | | - Right now, we have a mixing function called `WithLifecycle` which can be used like: |
266 | 255 | ```ts |
267 | 256 | WithLifecycle(HTMLElement); // or |
268 | 257 | WithLifecycle(HTMLDivElement); |
269 | 258 | ``` |
270 | | - It adds a lifecycle function called `$bind()` to any `HTMLElement` type. Which can later be extended by a custom element like |
| 259 | + |
| 260 | + It adds a `$bind()` lifecycle function to any `HTMLElement`. Later, it can be extended into a custom element: |
| 261 | + |
271 | 262 | ```ts |
272 | 263 | class MyElement extends WithLifecycle(HTMLElement) |
273 | 264 | ``` |
274 | | - Allowing you to create your own custom `HTMLElement` type with lifecycle. the `tags` proxy also uses `WithLifecycle` in combination with |
275 | | - `Builder` internally. so when you do `tags.div()` you are actually getting a `<div is="pure-div">` with a lifecycle. _But the `[is]` |
276 | | - attribute is not visible in the DOM since this element created by JS, not HTML_. |
277 | 265 |
|
278 | | - Anyway since this method requires you to decide if something is an element with lifecycle ahead of time, and also requires use to create |
279 | | - `pure-*` variant of native `HTMLElement` types in order to make them have lifecycle, it's kinda a lot. It makes sense. But it's kind of a |
280 | | - lot. |
| 266 | + This allows defining custom `HTMLElement` types with lifecycles. The `tags` proxy also uses `WithLifecycle` internally. |
| 267 | +
|
| 268 | + So when you do: |
| 269 | +
|
| 270 | + ```ts |
| 271 | + tags.div(); |
| 272 | + ``` |
| 273 | +
|
| 274 | + You’re actually getting a `<div is="pure-div">` with lifecycle tracking. **The `[is]` attribute is invisible in the DOM because the |
| 275 | + element is created via JavaScript, not HTML.** |
| 276 | +
|
| 277 | + However, since this method requires you to **decide lifecycle elements ahead of time**, it also means we must create **"pure-*" versions |
| 278 | + of native elements**. While it makes sense, it’s a bit cumbersome. |
281 | 279 |
|
282 | | - So this new custom attributes proposal can let us have lifecycle on any `Element` easily by simily adding an attribute to it. And this can |
283 | | - reshape a big portion of this codebase. And would make things connected to lifecyle of the `Element` more visible in the DOM. Which is |
284 | | - great. |
| 280 | + This is why the **custom attributes proposal** could significantly improve how lifecycles work. It would make lifecycle-related behavior |
| 281 | + **explicit in the DOM**, which is a big advantage. |
0 commit comments