A small Direct DOM library for creating user interfaces.
- Small. Between 3.8 and 6.4 kB (minified and brotlied). Zero external dependencies*.
- Direct DOM. Less CPU and memory usage compared to Virtual DOM.
- Designed for best Tree-Shaking: only the code you use is included in your bundle.
- TypeScript-first.
import { signal } from 'nanoviews/store'
import { div, a, img, h1, button, p, mount } from 'nanoviews'
function App() {
const $counter = signal(0)
return div()(
a({ href: 'https://vitejs.dev', target: '_blank' })(
img({ src: './vite.svg', class: 'logo', alt: 'Vite logo' })
),
a({ href: 'https://github.com/TrigenSoftware/nanoviews', target: '_blank' })(
img({ src: './nanoviews.svg', class: 'logo nanoviews', alt: 'Nanoviews logo' })
),
h1()('Vite + Nanoviews'),
div({ class: 'card' })(
button({
onClick() {
$counter($counter() + 1)
}
})('count is ', $counter)
),
p({ class: 'read-the-docs' })('Click on the Vite and Nanoviews logos to learn more')
)
}
mount(App, document.querySelector('#app'))
Install • Reactivity • Basic markup • Effect attributes • Components • Logic methods • Special methods • Why?
pnpm add -D nanoviews
# or
npm i -D nanoviews
# or
yarn add -D nanoviews
Nanoviews is using Kida under the hood for reactivity. Kida is a signal library inspired by Nano Stores and was build specially for Nanoviews.
import { signal } from 'nanoviews/store' // or import { signal } from 'kida'
import { fragment, input, p } from 'nanoviews'
const $text = signal('')
fragment(
input({
onInput(event) {
$text(event.target.value)
}
}),
p()($text)
)
Basicly, under the hood, reactivity works something like that:
import { signal, effect } from 'kida'
const $text = signal('')
const textNode = document.createTextNode('')
effect(() => {
textNode.data = $text()
})
Nanoviews provides a set of methods for creating HTML elements with the specified attributes and children. Every method creates a DOM node.
Child can be an another DOM node, primitive value (string, number, boolean, null
or undefined
) or signal with primitive. Attributes also can be a primitive value or signal.
import { signal } from 'nanoviews/store'
import { ul, li } from 'nanoviews'
const $boolean = signal(true)
const list = ul({ class: 'list' })(
li()('String value'),
li()('Number value', 42),
li()('Boolean value', $boolean)
)
// `list` is HTMLUListElement instance
mount
is a method that mounts the component to the specified container.
import { signal } from 'nanoviews/store'
import { div, h1, p, mount } from 'nanoviews'
function App() {
return div()(
h1()('Nanoviews App'),
p()('Hello World!')
)
}
mount(App, document.querySelector('#app'))
Effect attributes are special attributes that can control element's behavior.
ref$
is an effect attribute that can provide a reference to the DOM node.
import { signal } from 'nanoviews/store'
import { div, ref$ } from 'nanoviews'
const $ref = signal(null)
div({
[ref$]: $ref
})(
'Target element'
)
style$
is an effect attribute that manages the style of the element.
import { signal } from 'nanoviews/store'
import { button, style$ } from 'nanoviews'
const $color = signal('white')
button({
[style$]: {
color: $color,
backgroundColor: 'black'
}
})(
'Click me'
)
autoFocus$
is an effect attribute that sets the auto focus on the element.
import { input, autoFocus$ } from 'nanoviews'
input({
type: 'text',
[autoFocus$]: true
})
value$
is an effect attribute that manages the value of text inputs.
import { signal } from 'nanoviews/store'
import { textarea, value$ } from 'nanoviews'
const $review = signal('')
textarea({
name: 'review',
[value$]: $review
})(
'Write your review here'
)
checked$
is an effect attribute that manages the checked state of checkboxes and radio buttons.
import { signal } from 'nanoviews/store'
import { input, checked$, Indeterminate } from 'nanoviews'
const $checked = signal(false)
input({
type: 'checkbox',
[checked$]: $checked
})
Also you can manage indeterminate state of checkboxes:
$checked(Indeterminate)
selected$
is an effect attribute that manages the selected state of select's options.
import { signal } from 'nanoviews/store'
import { select, option, selected$ } from 'nanoviews'
const $selected = signal('mid')
select({
name: 'player-pos',
[selected$]: $selected
})(
option({
value: 'carry'
})('Yatoro'),
option({
value: 'mid',
})('Larl'),
option({
value: 'offlane'
})('Collapse'),
option({
value: 'support'
})('Mira'),
option({
value: 'full-support',
})('Miposhka'),
)
Multiple select:
const $selected = signal(['mid', 'carry'])
select({
name: 'player-pos',
[selected$]: $selected
})(
option({
value: 'carry'
})('Yatoro'),
option({
value: 'mid',
})('Larl'),
option({
value: 'offlane'
})('Collapse'),
option({
value: 'support'
})('Mira'),
option({
value: 'full-support',
})('Miposhka'),
)
files$
is an effect attribute that can provide the files of file inputs.
import { signal } from 'nanoviews/store'
import { input, files$ } from 'nanoviews'
const $files = signal([])
input({
type: 'file',
[files$]: $files
})
Components are the building blocks of any application. These units are reusable and can be combined to create more complex applications.
Components are functions that return primitive or DOM node:
function MyComponent() {
return div()('Hello, Nanoviews!')
}
effect
is a method that add effects to the component.
import { div, effect } from 'nanoviews'
function MyComponent() {
effect(() => {
console.log('Mounted')
return () => {
console.log('Unmounted')
}
})
return div()('Hello, Nanoviews!')
}
Also you can use effect
with signals:
import { div, effect } from 'nanoviews'
const $timeout = signal(1000)
function MyComponent() {
let intervalId
effect(() => {
intervalId = setInterval(() => {
console.log('Tick')
}, $timeout())
return () => {
clearInterval(intervalId)
}
})
return div()('Hello, Nanoviews!')
}
children$
is a method that creates optional children receiver.
import { div, children$ } from 'nanoviews'
function MyComponent(props) {
return children$(children => div(props)(
'My component children: ',
...children || ['empty']
))
}
MyComponent() // <div>My component children: empty</div>
MyComponent()('Hello, Nanoviews!') // <div>My component children: Hello, Nanoviews!</div>
slots$
is a method to receive slots and rest children.
import { main, header, footer, slot$, slots$ } from 'nanoviews'
function LayoutHeader(text) {
return slot$(LayoutHeader, text)
}
function LayoutFooter(text) {
return slot$(LayoutFooter, text)
}
function Layout() {
return slots$(
[LayoutHeader, LayoutFooter],
(headerSlot, footerSlot, children) => main()(
header()(headerSlot),
...children,
footer()(footerSlot)
)
)
}
Layout()(
LayoutHeader('Header content'),
LayoutFooter('Footer content'),
'Main content'
)
Slot's content can be anything, including functions, that can be used to render lists:
import { ul, li, b, slot$, slots$, for$ } from 'nanoviews'
function ListItem(renderItem) {
return slot$(ListItem, renderItem)
}
function List(items) {
return slots$(
[ListItem],
(listItemSlot) => ul()(
for$(items)(
item => li()(
listItemSlot(item.name)
)
)
)
)
}
List([
{ id: 0, name: 'chopper' },
{ id: 1, name: 'magixx' },
{ id: 2, name: 'zont1x' },
{ id: 3, name: 'donk' },
{ id: 4, name: 'sh1ro' },
{ id: 5, name: 'hally' }
])(
ListItem(name => b()('Player: ', name))
)
context$
is a method that can provide a context to the children.
import { signal } from 'nanoviews/store'
import { div, context$, provide, inject } from 'nanoviews'
function ThemeContext() {
return signal('light') // default value
}
function MyComponent() {
const $theme = inject(ThemeContext)
return div()(
'Current theme: ',
$theme
)
}
function App() {
const $theme = signal('dark')
return context$(
[provide(ThemeContext, $theme)],
() => MyComponent()
)
}
App() // <div>Current theme: dark</div>
[!NOTE] Nanoviews contexts are based on Kida's dependency injection system.
if$
is a method that can render different childs based on the condition.
import { signal } from 'nanoviews/store'
import { if$, div, p } from 'nanoviews'
const $show = signal(false)
if$($show)(
() => div()('Hello, Nanoviews!')
)
const $toggle = signal(false)
if$($toggle)(
() => p()('Toggle is true'),
() => div()('Toggle is false')
)
switch$
is a method like if$
but with multiple conditions.
import { signal } from 'nanoviews/store'
import { switch$, case$, default$, div, p } from 'nanoviews'
const $state = signal('loading')
switch$(state)(
case$('loading', () => b()('Loading')),
case$('error', () => b()('Error')),
default$(() => 'Success')
)
for$
is a method that can iterate over an array to render a fragment of elements.
import { signal, record } from 'nanoviews/store'
import { for$, trackById, ul, li } from 'nanoviews'
const $players = signal([
{ id: 0, name: 'chopper' },
{ id: 1, name: 'magixx' },
{ id: 2, name: 'zont1x' },
{ id: 3, name: 'donk' },
{ id: 4, name: 'sh1ro' },
{ id: 5, name: 'hally' }
])
ul()(
for$($players, trackById)(
$player => li()(
record($player).$name
)
)
)
There are exported predefined trackById
function to track by id
property and trackBy(key)
function to create a tracker for specified key.
portal$
is a method that can render a DOM node in a different place in the DOM.
import { div, portal$ } from 'nanoviews'
portal$(
document.body,
div()('I am in the body!')
)
throw$
is a helper to throw an error in expressions.
import { ul, children$, throw$ } from 'nanoviews'
function MyComponent() {
return children$((children = throw$(new Error('Children are required'))) => ul()(children))
}
fragment
is a method that creates a fragment with the specified children.
import { signal } from 'nanoviews/store'
import { fragment, effect } from 'nanoviews'
function TickTak() {
const $tick = signal(0)
effect(() => {
const id = setInterval(() => {
$tick($tick() + 1)
}, 1000)
return () => clearInterval(id)
})
return fragment('Tick tak: ', $tick)
}
dangerouslySetInnerHtml
is a method that sets the inner HTML of the element. It is used for inserting HTML from a source that may not be trusted.
import { div, dangerouslySetInnerHtml } from 'nanoviews'
dangerouslySetInnerHtml(
div({ id: 'rendered-md' }),
'<p>Some text</p>'
)
shadow
is a method that attaches a shadow DOM to the specified element.
import { div, shadow } from 'nanoviews'
shadow(
div({ id: 'custom-element' }),
{
mode: 'open'
}
)(
'Nanoviews can shadow DOM!'
)
Nanoviews and Kida are small libraries and designed to be tree-shakable. So apps using Nanoviews and Kida can be smaller even than using SolidJS or Svelte!
Example | Nanoviews | SolidJS | Svelte |
---|---|---|---|
Vite Demo | 7.66 kB / gzip: 3.22 kB source code |
8.93 kB / gzip: 3.73 kB source code |
12.73 kB / gzip: 5.54 kB kB source code |
Weather | + Kida 17.27 kB / gzip: 6.96 kB source code |
+ nanostores + @nanostores/solid 25.23 kB / gzip: 9.80 kB source code |
+ nanostores 26.80 kB / gzip: 10.84 kB source code |
Nanoviews is not fastest library: SolidJS and Svelte are faster, but performance is close to them. Anyway, Nanoviews is faster than React 🙂.