jorgebucaran / hyperapp

1kB-ish JavaScript framework for building hypertext applications
MIT License
19.06k stars 781 forks source link

@hyperapp/html: use a Proxy? #1087

Open mindplay-dk opened 2 years ago

mindplay-dk commented 2 years ago

In modern browsers, can we use a Proxy instead of hard coding all the element types?

I'm using this right now:

    import { h } from "./lib/hyperapp.js";

    const { main, h1, input, ul, li, button } = new Proxy(h, {
      get(h, type) { return (props, children) => h(type, props, children) }
    });

One more line of code, and you have support for custom elements:

    const { hyperApp } = new Proxy(h, {
      get(h, type) {
        const name = type.replace(/([A-Z])/g, c => "-" + c.toLowerCase());
        return (props, children) => h(name, props, children)
      }
    });

    console.log(hyperApp({foo:"bar"}, ["baz"])); // type: "hyper-app"

Thoughts? :-)

icylace commented 2 years ago

In the past I've used a similar Proxy technique for creating element functions on-the-fly. It certainly feels cleaner. Your one-liner for custom elements support is pretty neat !

However, if I'm not mistaken using a Proxy comes at the cost of some performance, which to be fair is probably fine for a lot of cases.

My preference nowadays is to use h directly, though.

mindplay-dk commented 2 years ago

However, if I'm not mistaken using a Proxy comes at the cost of some performance, which to be fair is probably fine for a lot of cases.

In this case, the Proxy is just a factory for the element creation functions - there is no run-time performance overhead once you've generated your functions.

My only reservation is this only works in modern browsers and Proxy can't be polyfilled.

icylace commented 2 years ago

It's good to know the Proxy performance penalty is not a concern. That said, it looks like the primary advantage of this technique over the current technique is the custom elements support. I think @hyperapp/html is tree-shakeable so code size for production shouldn't be a big difference in most cases. Though, maintaining a big clunky list of exported functions is annoying but I think that's a small price to pay to not have to worry about browser support.

I actually would like it if Proxy was used but like you said, it only works where it works.

zaceno commented 2 years ago

Unless you need IE11 support (which I don't think hyperapp supports anyway) Proxy is fine! I've used the approach suggested here in the past as well and really it's fine :)

The very small issues I've had with it are:

import html from '@hyperapp/html'
const {div, p, text} = html

Those aren't huge issues though and I could go either way. 🤷 😄

jorgebucaran commented 2 years ago

Custom elements is a nice plus. Now, the purpose here would be shipping less code with the package or would there be anything else?

I prefer the one-liner: import {div, p, text} from '@hyperapp/html' rather than the two-liner.

In the land of the multi-line, the one-liner is king.

mindplay-dk commented 2 years ago

One-liner is probably doable with export = ?

sergey-shpak commented 2 years ago

pf, using Proxy since early beginning (looks like already more than 2 years): https://gist.github.com/sergey-shpak/9266316d9abd550ddbeac2c88e5a0d22

mindplay-dk commented 2 years ago

@sergey-shpak great minds... heh. I too tried to get around the noisy empty attributes object you have to pass with every call - although, for some reason, my attempts seemed to break the official examples... children aren't always an array, I think?

Such a small thing, but it would make the examples look much better. I guess it's a breaking change though...

sergey-shpak commented 2 years ago

@mindplay-dk the gist is pretty old I haven't updated it for a while, my current implementation is following:

import {h, text} from 'hyperapp'

/* Html factory */
export const html = new Proxy({}, {
  get: (target, name) => name !== 'text'
  ? (attrs = {}, child) => 
    !Array.isArray(attrs) && !attrs.tag
    ? h(name, attrs, child)
    : h(name, {}, attrs)
  : text
})

and the usage

const {div, span, text} = html 

// no useless array for single element
// and no empty props object
div(text('Some text'))

// or something like
div([
  span(text('some text')),
  span(text('other text'))
])

// and common usage
div({class: 'active'}, text('no array for single child'))

*added later: works with hyperapp@2.0.19, I haven't tested with other versions but should be ok