ajo 0.1.11 → 0.1.12

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/dist/html.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const{entries:$,hasOwn:g}=Object,m=e=>typeof e!="string"&&typeof(e==null?void 0:e[Symbol.iterator])=="function",y=e=>[...u(e)].join(""),u=function*(e){for(e of f(e))if(typeof e=="string")yield p(e);else{const{nodeName:t,key:r,skip:l,memo:n,ref:a,children:o,...c}=e;let i="";for(const[s,d]of $(c))s.startsWith("set:")||d==null||d===!1||(i+=d===!0?`${i} ${s}`:`${i} ${s}="${p(String(d))}"`);v.has(t)?yield`<${t}${i}>`:l?yield`<${t}${i}></${t}>`:typeof o=="string"?yield`<${t}${i}>${o}</${t}>`:(yield`<${t}${i}>`,yield*u(o),yield`</${t}>`)}},f=function*(e,t={value:""},r=!0){for(e of m(e)?e:[e])if(!(e==null||typeof e=="boolean"))if(g(e,"nodeName")){const{value:l}=t,{nodeName:n}=e,a=typeof n;if(l&&(yield l,t.value=""),a==="function")if(n.constructor.name==="GeneratorFunction"){const o={},c={};for(const[i,s]of $(e))i!=="is"&&(i==="children"?c.children=s:i.startsWith("arg:")?c[i.slice(4)]=s:o[i]=s);o.nodeName=e.is??n.is??"div",o.children=k(n,c),yield o}else delete e.nodeName,yield*f(n(e),t,!1);else a==="string"&&(yield e)}else m(e)?yield*f(e,t,!1):t.value+=e;r&&t.value&&(yield t.value)},v=new Set("area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(",")),p=e=>e.replace(/[&<>"']/g,t=>`&#${t.charCodeAt(0)};`),k=(e,t)=>{let r,l;try{const n=e.call(r={$args:t,*[Symbol.iterator](){for(;;)yield t},refresh(){},next(){l=y(n.next().value)},throw(a){l=y(n.throw(a).value)},return(){n.return()}},t);r.next()}catch(n){r.throw(n)}finally{r.return()}return l};exports.html=u;exports.render=y;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const{entries:p,hasOwn:$}=Object,u=e=>typeof e!="string"&&typeof(e==null?void 0:e[Symbol.iterator])=="function",d=e=>[...f(e)].join(""),f=function*(e){for(e of y(e))if(typeof e=="string")yield m(e);else{const{nodeName:t,skip:r,children:i=""}=e;let n="";for(const[l,o]of p(e))k.has(l)||l.startsWith("set:")||o==null||o===!1||(n+=o===!0?`${n} ${l}`:`${n} ${l}="${m(String(o))}"`);g.has(t)?yield`<${t}${n}>`:r?yield`<${t}${n}></${t}>`:typeof i=="string"?yield`<${t}${n}>${i}</${t}>`:(yield`<${t}${n}>`,yield*f(i),yield`</${t}>`)}},y=function*(e,t={value:""},r=!0){for(e of u(e)?e:[e])if(!(e==null||typeof e=="boolean"))if($(e,"nodeName")){const{value:i}=t,{nodeName:n}=e,l=typeof n;if(i&&(yield i,t.value=""),l==="function")if(n.constructor.name==="GeneratorFunction"){const o={},a={};for(const[s,c]of p(e))s==="children"?a.children=c:s.startsWith("arg:")?a[s.slice(4)]=c:o[s]=c;o.nodeName=n.is??"div",o.children=v(n,a),yield o}else delete e.nodeName,yield*y(n(e),t,!1);else l==="string"&&(yield e)}else u(e)?yield*y(e,t,!1):t.value+=e;r&&t.value&&(yield t.value)},g=new Set("area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(",")),k=new Set("nodeName,key,skip,memo,ref,children".split(",")),m=e=>e.replace(/[&<>"']/g,t=>`&#${t.charCodeAt(0)};`),v=(e,t)=>{let r,i;try{const n=e.call(r={$args:t,*[Symbol.iterator](){for(;;)yield t},refresh(){},next(){i=d(n.next().value)},throw(l){i=d(n.throw(l).value)},return(){n.return()}},t);r.next()}catch(n){r.throw(n)}finally{r.return()}return i};exports.html=f;exports.render=d;
package/dist/html.js CHANGED
@@ -1,34 +1,34 @@
1
- const { entries: p, hasOwn: g } = Object, f = (e) => typeof e != "string" && typeof (e == null ? void 0 : e[Symbol.iterator]) == "function", u = (e) => [...$(e)].join(""), $ = function* (e) {
2
- for (e of y(e))
1
+ const { entries: m, hasOwn: $ } = Object, y = (e) => typeof e != "string" && typeof (e == null ? void 0 : e[Symbol.iterator]) == "function", f = (e) => [...p(e)].join(""), p = function* (e) {
2
+ for (e of d(e))
3
3
  if (typeof e == "string")
4
- yield m(e);
4
+ yield u(e);
5
5
  else {
6
- const { nodeName: t, key: r, skip: l, memo: n, ref: a, children: o, ...c } = e;
7
- let i = "";
8
- for (const [s, d] of p(c))
9
- s.startsWith("set:") || d == null || d === !1 || (i += d === !0 ? `${i} ${s}` : `${i} ${s}="${m(String(d))}"`);
10
- k.has(t) ? yield `<${t}${i}>` : l ? yield `<${t}${i}></${t}>` : typeof o == "string" ? yield `<${t}${i}>${o}</${t}>` : (yield `<${t}${i}>`, yield* $(o), yield `</${t}>`);
6
+ const { nodeName: t, skip: r, children: i = "" } = e;
7
+ let n = "";
8
+ for (const [l, o] of m(e))
9
+ k.has(l) || l.startsWith("set:") || o == null || o === !1 || (n += o === !0 ? `${n} ${l}` : `${n} ${l}="${u(String(o))}"`);
10
+ g.has(t) ? yield `<${t}${n}>` : r ? yield `<${t}${n}></${t}>` : typeof i == "string" ? yield `<${t}${n}>${i}</${t}>` : (yield `<${t}${n}>`, yield* p(i), yield `</${t}>`);
11
11
  }
12
- }, y = function* (e, t = { value: "" }, r = !0) {
13
- for (e of f(e) ? e : [e])
12
+ }, d = function* (e, t = { value: "" }, r = !0) {
13
+ for (e of y(e) ? e : [e])
14
14
  if (!(e == null || typeof e == "boolean"))
15
- if (g(e, "nodeName")) {
16
- const { value: l } = t, { nodeName: n } = e, a = typeof n;
17
- if (l && (yield l, t.value = ""), a === "function")
15
+ if ($(e, "nodeName")) {
16
+ const { value: i } = t, { nodeName: n } = e, l = typeof n;
17
+ if (i && (yield i, t.value = ""), l === "function")
18
18
  if (n.constructor.name === "GeneratorFunction") {
19
- const o = {}, c = {};
20
- for (const [i, s] of p(e))
21
- i !== "is" && (i === "children" ? c.children = s : i.startsWith("arg:") ? c[i.slice(4)] = s : o[i] = s);
22
- o.nodeName = e.is ?? n.is ?? "div", o.children = v(n, c), yield o;
19
+ const o = {}, a = {};
20
+ for (const [s, c] of m(e))
21
+ s === "children" ? a.children = c : s.startsWith("arg:") ? a[s.slice(4)] = c : o[s] = c;
22
+ o.nodeName = n.is ?? "div", o.children = w(n, a), yield o;
23
23
  } else
24
- delete e.nodeName, yield* y(n(e), t, !1);
24
+ delete e.nodeName, yield* d(n(e), t, !1);
25
25
  else
26
- a === "string" && (yield e);
26
+ l === "string" && (yield e);
27
27
  } else
28
- f(e) ? yield* y(e, t, !1) : t.value += e;
28
+ y(e) ? yield* d(e, t, !1) : t.value += e;
29
29
  r && t.value && (yield t.value);
30
- }, k = new Set("area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(",")), m = (e) => e.replace(/[&<>"']/g, (t) => `&#${t.charCodeAt(0)};`), v = (e, t) => {
31
- let r, l;
30
+ }, g = new Set("area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(",")), k = new Set("nodeName,key,skip,memo,ref,children".split(",")), u = (e) => e.replace(/[&<>"']/g, (t) => `&#${t.charCodeAt(0)};`), w = (e, t) => {
31
+ let r, i;
32
32
  try {
33
33
  const n = e.call(r = {
34
34
  $args: t,
@@ -39,10 +39,10 @@ const { entries: p, hasOwn: g } = Object, f = (e) => typeof e != "string" && typ
39
39
  refresh() {
40
40
  },
41
41
  next() {
42
- l = u(n.next().value);
42
+ i = f(n.next().value);
43
43
  },
44
- throw(a) {
45
- l = u(n.throw(a).value);
44
+ throw(l) {
45
+ i = f(n.throw(l).value);
46
46
  },
47
47
  return() {
48
48
  n.return();
@@ -54,9 +54,9 @@ const { entries: p, hasOwn: g } = Object, f = (e) => typeof e != "string" && typ
54
54
  } finally {
55
55
  r.return();
56
56
  }
57
- return l;
57
+ return i;
58
58
  };
59
59
  export {
60
- $ as html,
61
- u as render
60
+ p as html,
61
+ f as render
62
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ajo",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "ajo is a JavaScript view library for building user interfaces",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
package/readme.md CHANGED
@@ -265,7 +265,7 @@ In Ajo, there are several special attributes (`key`, `skip`, `memo`, and `ref`)
265
265
  - **Behavior:** When an element is mounted or updated, the `ref` callback is called with the DOM element as an argument. This allows you to store a reference to it for later use, such as focusing an input or measuring dimensions.
266
266
  - **Example:** `h('input', { ref: el => (this.inputNode = el) })` - stores a reference to the input element.
267
267
 
268
- ## Stateful components
268
+ ## Stateful Components
269
269
 
270
270
  Stateful components in Ajo are defined using generator functions. These components are designed with a minimalistic API for controlling rendering and state updates. They are equipped with several lifecycle methods that allow for advanced control over component behavior, error handling, and rendering processes.
271
271
 
@@ -349,6 +349,40 @@ function* ChatComponent({ user = 'Anonymous', room }) { // Receive arguments ini
349
349
  }
350
350
  ```
351
351
 
352
+ ### Default Rendering Behavior
353
+
354
+ By default, when a stateful component is rendered in Ajo, it wraps its yielded content within a `<div>` element. This `<div>` becomes the context of the component's generator function, referred to as `this` within the function.
355
+
356
+ For example:
357
+
358
+ ```javascript
359
+ function* MyComponent() {
360
+ // The 'this' variable here refers to the default 'div' wrapper element
361
+ }
362
+ ```
363
+ > This default behavior ensures a consistent and predictable wrapper for your component's content, making it easier to manage and style.
364
+
365
+ ### Customizing the Wrapper Element
366
+
367
+ While the default wrapper is a `<div>`, Ajo provides the flexibility to change this to any other HTML element type. This is particularly useful for semantic correctness or when integrating with existing HTML structures, especially in SSR scenarios.
368
+
369
+ To specify a different element type for the wrapper, set the `is` property on the Generator Function of your component. For example:
370
+
371
+ ```javascript
372
+ function* MyCustomRow() {
373
+ // The 'this' variable here refers to the default 'tr' wrapper element
374
+ }
375
+
376
+ MyCustomRow.is = 'tr'
377
+ ```
378
+ > This code will instruct Ajo to render a `<tr>` element instead of the default `<div>`. This capability is crucial for rendering and hydrating any type of HTML element when using stateful components.
379
+
380
+ #### SSR and Hydration
381
+
382
+ In the context of Server-Side Rendering (SSR), this feature allows Ajo to gracefully hydrate existing SSR-generated DOM elements. It means that Ajo can extend the functionality of built-in browser DOM elements without relying on Web Components or standards like Declarative Shadow DOM.
383
+
384
+ This approach provides a streamlined, efficient method for enhancing and manipulating built-in elements, offering a more practical solution compared to the complexities of Web Components.
385
+
352
386
  ## Lifecycle methods
353
387
 
354
388
  Stateful components in Ajo are equipped with several methods that allow for advanced control over component behavior, error handling, and rendering processes. These methods are called lifecycle methods and are invoked at different stages of the component's lifecycle.
@@ -580,6 +614,115 @@ function* ChildComponent({ data, onEvent }) {
580
614
 
581
615
  This `arg:` prefixed attribute system in Ajo enhances the clarity and readability of component composition. It makes the intent of passing down arguments more explicit, reducing confusion between HTML attributes, and other special properties. This is especially beneficial in complex applications where components have multiple responsibilities and interact with both their children and the DOM.
582
616
 
617
+ ## Server-Side Rendering (SSR)
618
+
619
+ Ajo supports Server-Side Rendering (SSR), enabling components to be rendered to HTML in server-side JavaScript environments. This feature enhances the capabilities of Ajo for projects requiring SEO-friendly pages and faster initial page loads.
620
+
621
+ ### SSR Implementation with `ajo/html`
622
+
623
+ For SSR in Ajo, use the `render` and `html` functions from the `ajo/html` module. These functions are designed to convert a virtual DOM tree into an HTML string or HTML chunks for streaming, suitable for server-side environments.
624
+
625
+ ### Client-Side Hydration
626
+
627
+ Once HTML is rendered on the server, it can be hydrated on the client-side by Ajo to become interactive. The client-side `render` function can render into the root element containing the SSR-generated DOM.
628
+
629
+ ### Example Usage
630
+
631
+ Server-side:
632
+
633
+ ```javascript
634
+ import express from 'express'
635
+ import { render } from 'ajo/html'
636
+ import { App } from './components'
637
+
638
+ const app = express()
639
+
640
+ app.get('/', (req, res) => {
641
+
642
+ const html = render(<App />)
643
+
644
+ res.send(`
645
+ <!DOCTYPE html>
646
+ <html>
647
+ <head>
648
+ <title>My App</title>
649
+ </head>
650
+ <body>
651
+ <div id="root">${html}</div>
652
+ </body>
653
+ </html>`)
654
+ })
655
+
656
+ app.listen(3000)
657
+ ```
658
+
659
+ Client-side:
660
+
661
+ ```javascript
662
+ import { render } from 'ajo'
663
+ import { App } from './components'
664
+
665
+ // Hydrate the #root element with the server-rendered DOM
666
+ render(<App />, document.getElementById('root'))
667
+ ```
668
+
669
+ ### Streaming HTML Chunks
670
+
671
+ The `html` function is designed to be used with various streaming technologies. Its output can be seamlessly integrated into different streaming environments, offering developers the flexibility to choose the streaming solution that best fits their project requirements.
672
+
673
+ This streaming capability is useful for progressively rendering content, enhancing Time to First Paint (TTFP) and user experience. It allows browsers to begin rendering content as soon as the initial chunks arrive.
674
+
675
+ ## SSR API
676
+
677
+ ### `render(h: Any): String`
678
+
679
+ Renders a virtual DOM tree (`h`) into a complete HTML string.
680
+
681
+ #### Parameters:
682
+
683
+ - **h** (Any): The virtual DOM tree to render. This can be any value, a simple string, or a virtual DOM tree created by the `h` function.
684
+
685
+ #### Returns:
686
+
687
+ - A string representation of the rendered HTML.
688
+
689
+ #### Example Usage:
690
+
691
+ ```javascript
692
+ import { render } from 'ajo/html'
693
+ import { App } from './components'
694
+
695
+ const html = render(<App />)
696
+ // The `html` can be sent as part of an HTTP response
697
+ ```
698
+
699
+ ### `html(h: Any): Generator`
700
+
701
+ A generator function that iterates through a virtual DOM tree, yielding HTML strings.
702
+
703
+ #### Parameters:
704
+
705
+ - **h** (Any): The virtual DOM tree to iterate through.
706
+
707
+ #### Yield:
708
+
709
+ - Yields HTML strings corresponding to each node in the virtual DOM.
710
+
711
+ #### Usage:
712
+
713
+ - Suitable for streaming HTML chunks to the client, compatible with any streaming technology.
714
+
715
+ #### Example Usage:
716
+
717
+ ```javascript
718
+ import { html } from 'ajo/html'
719
+ import { App } from './components'
720
+
721
+ for (const chunk of html(<App />)) {
722
+ stream.push(chunk)
723
+ }
724
+ ```
725
+
583
726
  ## Acknowledgments
584
727
  Ajo takes heavy inspiration from [Incremental DOM](https://github.com/google/incremental-dom) and [Crank.js](https://github.com/bikeshaving/crank)
585
728