npm package: @st-lib/render
Description: Lightweight HTML rendering library.
Author: padreramnt
Latest version: 1.0.11

# Lightweight HTML rendering library.

> ESNext module, please use [webpack](https://webpack.js.org/) / [rollup](https://rollupjs.org/guide/en/) / [parcel](https://parceljs.org/) / etc.


## Additional materials

1. [@st-lib/render-html](https://www.npmjs.com/package/@st-lib/render-html),
2. [@st-lib/render-svg](https://www.npmjs.com/package/@st-lib/render-svg),
3. [@st-lib/render-mathml](https://www.npmjs.com/package/@st-lib/render-mathml),
4. [@st-lib/render-events](https://www.npmjs.com/package/@st-lib/render-events),
4. [@st-lib/render-with-state](https://www.npmjs.com/package/@st-lib/render-with-state).

## Concepts.

1. The library uses a context-oriented approach to describing the structure of HTML.
2. The purpose of the library is NOT to work with the state of the application, use third-party libraries for this.
3. The library does not create intermediate representations of the document structure (Virtual DOM), but instead updates the document directly.

## Basic usage.

Use `render` function for render to document.

```ts
export interface Renderer {
    createElement(tagName: string, options?: ElementCreationOptions): Element;
    createElementNS(namespaceURI: string | null, tagName: string, options?: ElementCreationOptions): Element;
    createTextNode(data: string): Text;
    createComment(data: string): Comment;
    createAttribute(localName: string): Attr;
    createAttributeNS(namespace: string | null, qualifiedName: string): Attr;
}
export declare function render<T extends Element>(target: T | null, content: (ref: T) => void, renderer?: Renderer): void;
```

Use `rerender` function for render element with last used content callback and renderer

```ts
render(document.body, () => {
	let i = 0
	element(null, 'button', ref => {
		// see https://www.npmjs.com/package/@st-lib/render-events
		onClick(() => {
			i++
			rerender(ref)
		})
		text(null, i)
	})
})
```

### Element

Create element node with specified key, tag name, namespace URI, creation options and content rendering function or raw HTML string

> UPDATE: added customized built-in elements support.

```ts
export declare function element<T extends Element>(key: any, tag: string | [string, (string | null)?, (ElementCreationOptions | null)?], content?: ElementContentDescriptor<T> | false | null | undefined): T | null;
```

___Key___: accepts any type: `number`, `string`, `symbol` or `object`. Passing `null` or `undefined` as key will be replaced with current key order.

___Tag___: accepts `tagName: string` or turple` [tagName: string, namespaceURI?: string | null, options?: ElementCreationOptions | null] `

___Content___: content rendering function `(ref: T extends Element) => void` or raw HTML string

Returns the created element node or `null` if the node is not created.

#### Example:

```ts
import { render, element } from '@st-lib/render'

window.onload = () => {
	render(document.body, () => {
		element(null, 'header')
		element(null, 'main', () => {
			element(null, 'article')
			element(null, 'article')
			element(null, 'article')
			element(null, 'article')
			// svg support
			element(null, ['svg', 'http://www.w3.org/2000/svg'], () => {
				element(null, ['a', 'http://www.w3.org/2000/svg'])
			})
			// custom element
			element(null, 'custom-element')
			// cusomized built-in
			element(null, ['form', null, { is: 'custom-form' }])
		})
		element(null, 'footer')
		//
	})
}

/*
<body>
	<header></header>
	<main>
		<article></article>
		<article></article>
		<article></article>
		<article></article>
		<svg>
			<a />
		</svg>
		<custom-element></custom-element>
		<form is='custom-form'></form>
	</main>
	<footer></footer>
</body>
*/
```

### Attributes

Update current rendering element attributes

```ts
// Setting single attribute
export declare function attr(name: string, value: OptionalAttrValue, namespaceURI?: string | null): void | null;
```

```ts
// Setting several attributes in one operation
export declare type AttrsMapEntry = AttrValue | [OptionalAttrValue, (string | null)?];
export declare type OptionalAttrMapEntry = AttrsMapEntry | false | null | undefined;
export declare type AttrsMap = Record<string, OptionalAttrMapEntry>;
export declare function attrs<T extends AttrsMap>(inp: T): void;
```

___name___: `string`, the name of attribute .

___value___: `string | number | false | null | undefined`, the value of attribute
* if `null | undefined`: do nothing
* else if `false`: delete attribute
* else: set attribute to `String(value)`

___namespaceURI___: `string | null | undefined`

> Setting several attributes in one operation has corresponding optimizations.

> Note: unused attributes will __NOT__ be removed.

#### Example:

```ts
import { render, element, text, attr, attrs } from '@st-lib/render'

window.onload = () => {
	render(document.body, () => {
		element(null, 'form', () => {
			// set all attributes with one operation
			attrs({
				action: '/some/url',
				method: 'post',
				enctype: 'multipart/form-data',
				'delete-attribute': false,
				'skipped-attribute-1': null,
				'skipped-attribute-2': undefined,
			})
			element(null, 'input', () => {
				// make input required
				attr('required', '') // only string, number and "falselike types" allowed
			})
			element(null, 'button', () => {
				text(null, 'submit')
			})
		})
	})
}

/*
<body>
	<form action="/some/url" method="post" enctype="multipart/form-data">
		<input required />
		<button>submit</button>
	</form>
</body>
*/
```

### Text

Create text node with specified key and string or number value.

```ts
export declare function text(key: any, value: OptionalTextValue): Text | null;
```

___Key___: accepts any type: `number`, `string`, `symbol` or `object`. Passing `null` or `undefined` as key will be replaced with current call order.

___Value___: accepts `string` or `number`. Passing `false`, `null` or `undefined` does not emit text node.

Returns the created text node or `null` if the node is not created.

> Passing `NaN` as value will emit console warning.

#### Example:

```ts
import { render, element, text } from '@st-lib/render'

window.onload = () => {
	render(document.body, () => {
		element(null, 'header')
		element(null, 'main', () => {
			element(null, 'article', () => {
				text(null, 'Lorem ipsum dolor sit amet.')
			})
			element(null, 'article', () => {
				text(null, 'Lorem ipsum dolor sit amet.')
			})
			element(null, 'article', () => {
				text(null, 'Lorem ipsum dolor sit amet.')
			})
			element(null, 'article', () => {
				text(null, 'Lorem ipsum dolor sit amet.')
			})
		})
		element(null, 'footer')
	})
}

/*
<body>
	<header></header>
	<main>
		<article>Lorem ipsum dolor sit amet.</article>
		<article>Lorem ipsum dolor sit amet.</article>
		<article>Lorem ipsum dolor sit amet.</article>
		<article>Lorem ipsum dolor sit amet.</article>
	</main>
	<footer></footer>
</body>
*/
```

> Normalization of text nodes is not supported! Use string interpolation.

```ts
import { render, text } from '@st-lib/render'

window.onload = () => {
	render(document.body, () => {
		// SSR invalid
		const t1 = text(null, 'Lorem ipsum dolor sit amet,')
		const t2 = text(null, ' consectetur adipiscing elit. Vivamus ac.')
		console.log(t1 !== t2) // true
		console.log(t1.textContent === 'Lorem ipsum dolor sit amet,') // true
		console.log(t2.textContent === ' consectetur adipiscing elit. Vivamus ac.') // true
	})
}

/*
<body>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ac.</body>
*/
```

### Comment

Create comment node with specified key and string or number value.

```ts
export declare function comment(key: any, value: OptionalCommentValue): Comment | null | undefined;
```

___Key___: accepts any type: `number`, `string`, `symbol` or `object`. Passing `null` or `undefined` as key will be replaced with current call order.

___Value___: accepts `string` or `number`. Passing `false`, `null` or `undefined` does not emit comment node.

Returns the created comment node or `null` if the node is not created.

> Passing `NaN` as value will emit console warning.

#### Example:
```ts
import { render, comment } from '@st-lib/render'

window.onload = () => {
	render(document.body, () => {
		// <!-- creates comment node --!>
		comment(null, 'creates comment node')
	})
}
```

## Lifecycle hooks

> All lifecycle hooks work in client side rendering, after render stage.

### Created

Called after new element created or used existing sililar element (with same tag name and namespase URI).

```ts
export declare function created<T extends Element>(elementCreatedCallback: ElementCreatedCallback<T>): void;
```

Returned function used as `elementRemovedCallback`.

#### Example:

```ts
import { render, element, created } from '@st-lib/render'
window.onload = () => {
	render(document.body, () => {
		element(null, 'div', divElement => {
			created(createdElement => {
				console.log('element "div" created', createdElement)
				return removedElement => {
					console.log('element "div" removed', removedElement, divElement === createdElement && divElement === removedElement /* true */)
				}
			})
		})
	})
}
```

### Updated

Called when an element is rendered again.

```ts
export declare function updated<T extends Element>(elementUpdatedCallback: ElementUpdatedCallback<T>): void;
```

Returned function used as `elementCleanupCallback`

#### Example:

```ts
import { render, element, updated } from '@st-lib/render'

function App() {
	element(null, 'div', divElement => {
		updated(updatedElement => {
			console.log('element has beed updated', updatedElement)
			return cleanupElement => {
				console.log('cleanup', cleanupElement, divElement === updatedElement && divElement === cleanupElement /* true */)
			}
		})
	})
}

window.onload = () => {
	// no console output
	render(document.body, App)
	setTimeout(() => {
		// emits console output
		render(document.body, App)
	})
}
```

### Removed

Called before an element will be removed from document. See `created`

```ts
export declare function removed<T extends Element>(elementRemovedCallback: ElementRemovedCallback<T>): void;
```

#### Example:

```ts
import { render, element, removed } from '@st-lib/render'

function elementRemovedCallback() {
	console.log('removed')
}

window.onload = () => {
	render(document.body, () => {
		element(0, 'div', divElement => {
			removed(elementRemovedCallback) // same as created(() => elementRemovedCallback)
		})
		element(0, 'span') // replace element <0.div> with <0.span>
	})
}
```

```ts
import { render, element, created, removed } from '@st-lib/render'

function elementRemovedCallback() {
	console.log('will be called once')
}

window.onload = () => {
	render(document.body, () => {
		element(null, 'div', divElement => {
			created(() => elementRemovedCallback)
			removed(elementRemovedCallback)
		})
	})
}
```

## Render stage hooks

### Linking

Called instantly during rendering.

Returned function used as `elementCleanupCallback`.

```ts
export declare function linking<T extends Element>(elementReferenceCallback: ElementLinkCallback<T>): void;
```

#### Example:

```ts
import { render, element, linking } from '@st-lib/render'

window.onload = () => {
	render(document.body, () => {
		element(null, 'button', () => {
			linking(buttonElement => {
				function onClickListener(e) {
					console.log('click', e)
				}
				buttonElement.addEventListener('click', onClickListener, true)
				return () => {
					buttonElement.removeEventListener('click', onClickListener, true)
				}
			})
		})
	})
}
```

### Cleanup

Called every time before rendering. See `linking` or `update`.

```ts
export declare function cleanup<T extends Element>(elementCleanupCallback: ElementCleanupCallback<T>): void;
```


## SSR (Server Side Rendering)

Use `write` function for render to string.

```ts
export declare function write(content: (ref: null) => void): string;
```

#### Example:

```ts
// App.js
import { element, text } from '@st-lib/render'

function onClickListener(e) {
	console.log('click', e)
}

export default function App() {
	element(null, 'header')
	element(null, 'main', () => {
		element(null, 'article', () => {
			text(null, 'Lorem ipsum dolor sit amet.')
		})
		element(null, 'article', () => {
			text(null, 'Lorem ipsum dolor sit amet.')
		})
		element(null, 'article', () => {
			text(null, 'Lorem ipsum dolor sit amet.')
		})
		element(null, 'article', () => {
			text(null, 'Lorem ipsum dolor sit amet.')
		})
		element(null, 'button', () => {
			linking(btn => {
				btn.addEventListener('click', onClickListener, true)
				return () => btn.removeEventListener('click', onClickListener, true)
			})
		})
	})
	element(null, 'footer')
}
```

```ts
// client.js
import { render } from '@st-lib/render'
import App from './App'

window.onload = () => {
	render(document.body, App)
}
```

```ts
// server.js
import { write } from '@st-lib/render'
import { createServer } from 'http'
import App from './App'

const server = createServer((_, res) => {
	res.setHeader('Content-Type', 'text/html')
	res.write(`
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>@st-lib/render SSR example</title>
</head>
<body>${write(App)}<script src="/client.js"></script></body>
</html>`)
	res.end()
})

server.listen(3000)
```