Skip to content

Instantly share code, notes, and snippets.

@skanne
Last active July 22, 2024 10:25
Show Gist options
  • Save skanne/3a8104173c61b39a7507640a445752b7 to your computer and use it in GitHub Desktop.
Save skanne/3a8104173c61b39a7507640a445752b7 to your computer and use it in GitHub Desktop.
hyperapp-dom

Hyperapp DOM

This gist offers another build-step-less way to construct Hyperapp views by using the powers of JavaScript proxies (see Proxy - JavaScript | MDN) and the ability to "create" dedicated hyperscript functions per needed tag on the fly by destructuring the imported proxy. Text nodes are implicit - just place a string as a child. Dashed custom elements and web components are also supported - just camel-case them when destructuring. Lists of children can be passed as an array or as individual arguments, or as a mix of both.

import dom from "./dom.js"

const { main, h1, input, ul, button, myCustomElement, myWebComponent } = dom

The signature of the destructured hyperscript functions is flexible. See the following examples:

// just a `<br>`
br() 

// a `<span>` with a simple text node
strong('I am 💪') 

// render `<main id="app" class="--running"></main>`
main({ id: 'app', class: { '--running': true } }) 

// render `<ul><li>one</li><li>two</li><li>three</li></ul>`
ul(li('one'), li('two'), li('three'))

// render `<ul lang="sme"><li>okta</li><li>guokte</li><li>golbma</li></ul>`
ul({ lang: 'sme' }, li('okta'), li('guokte'), li('golbma'))

// equivalent ways to declare children (all render `<ol><li>Firstly</li><li>Secondly</li><li>Thirdly</li></ol>`):
ol(li('Firstly'), li('Secondly'), li('Thirdly'))

ol([ li('Firstly'), li('Secondly'), li('Thirdly') ])

ol(li('Firstly'), [ li('Secondly'), li('Thirdly') ])

ol(li('Firstly'), [ li('Secondly'), [ li('Thirdly') ] ])

const items = [ li('Firstly'), li('Secondly'), li('Thirdly') ]
ol(...items)

// render `<p>Hello, <em>Sven</em>!</p>` for `state = { name: 'Sven' }`
p('Hello, ', em(state.name), '!') 

// render `<my-custom-element></my-custom-element>` and `<my-web-component></my-web-component>`
myCustomElement()
myWebComponent()

// using the imported `dom` proxy/object
dom.hr() // renders `<hr>`
dom['my-web-component']() // renders `<my-web-component></my-web-component>`
dom[buttonOrLink](attrs, 'Click me') // renders `<a href='#'>Click me</a>` for `buttonOrLink = 'a'` and `attrs = { href: '#' }`
import { h, text } from 'hyperapp'
const TEXT_TYPES = ['string', 'number']
const mapChildren = child => (TEXT_TYPES.includes(typeof child) ? text(child) : child)
const dasherize = str =>
str.replace(/[A-Z](?:(?=[^A-Z])|[A-Z]*(?=[A-Z][^A-Z]|$))/g, (s, i) => (i > 0 ? '-' : '') + s.toLowerCase())
export default new Proxy(
{},
{
get: (cache, tag) => {
if (!cache[tag]) {
const tagName = dasherize(tag)
cache[tag] = (firstArg, ...restArgs) =>
typeof firstArg === 'object' && !Array.isArray(firstArg) && typeof firstArg.props !== 'object'
? h(tagName, firstArg, restArgs.flatMap(mapChildren))
: h(tagName, {}, [firstArg, ...restArgs].flatMap(mapChildren))
cache[tag].tagName = tagName
}
return cache[tag]
},
}
)
<script type="module">
import { app } from "https://unpkg.com/hyperapp"
import dom from "./dom.js"
const { main, h1, input, ul, li, button } = dom
const AddTodo = state => ({
...state,
value: "",
todos: state.todos.concat(state.value),
})
const NewValue = (state, event) => ({
...state,
value: event.target.value,
})
app({
init: { todos: ["Learn Hyperapp"], value: "" },
view: ({ todos, value }) =>
main(
h1("To do list"),
input({ type: "text", oninput: NewValue, value }),
ul(todos.map(todo => li(todo))),
button({ onclick: AddTodo }, "New!"),
),
node: document.getElementById("app"),
})
</script>
<main id="app"></main>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment