1.0.11 • Published 4 years ago

@st-lib/render v1.0.11

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

Lightweight HTML rendering library.

ESNext module, please use webpack / rollup / parcel / etc.

Additional materials

  1. @st-lib/render-html,
  2. @st-lib/render-svg,
  3. @st-lib/render-mathml,
  4. @st-lib/render-events,
  5. @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.

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

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.

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:

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

// Setting single attribute
export declare function attr(name: string, value: OptionalAttrValue, namespaceURI?: string | null): void | null;
// 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:

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.

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:

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.

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.

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:

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).

export declare function created<T extends Element>(elementCreatedCallback: ElementCreatedCallback<T>): void;

Returned function used as elementRemovedCallback.

Example:

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.

export declare function updated<T extends Element>(elementUpdatedCallback: ElementUpdatedCallback<T>): void;

Returned function used as elementCleanupCallback

Example:

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

export declare function removed<T extends Element>(elementRemovedCallback: ElementRemovedCallback<T>): void;

Example:

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>
	})
}
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.

export declare function linking<T extends Element>(elementReferenceCallback: ElementLinkCallback<T>): void;

Example:

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.

export declare function cleanup<T extends Element>(elementCleanupCallback: ElementCleanupCallback<T>): void;

SSR (Server Side Rendering)

Use write function for render to string.

export declare function write(content: (ref: null) => void): string;

Example:

// 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')
}
// client.js
import { render } from '@st-lib/render'
import App from './App'

window.onload = () => {
	render(document.body, App)
}
// 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)
1.0.11

4 years ago

1.0.10

4 years ago

1.0.9

4 years ago

1.0.8

4 years ago

1.0.7

4 years ago

1.0.6

4 years ago

1.0.5

4 years ago

1.0.4

4 years ago

1.0.3

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago