hyperapp icon indicating copy to clipboard operation
hyperapp copied to clipboard

@hyperapp/html: use a Proxy?

Open mindplay-dk opened this issue 3 years ago • 9 comments

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? :-)

mindplay-dk avatar Feb 23 '22 12:02 mindplay-dk

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.

icylace avatar Feb 25 '22 06:02 icylace

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.

mindplay-dk avatar Feb 25 '22 07:02 mindplay-dk

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.

icylace avatar Feb 25 '22 07:02 icylace

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:

  • Tree shaking (but now that I'm striving to be bundler-free, proxy is technically less to import)
  • I didn't manage to declare types for the proxy. But that doesn't mean there isn't a way to do it ;)
  • I prefer the one-liner: `import {div, p, text} from '@hyperapp/html' rather than the two-liner:
import html from '@hyperapp/html'
const {div, p, text} = html

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

zaceno avatar Mar 04 '22 14:03 zaceno

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.

jorgebucaran avatar Mar 04 '22 19:03 jorgebucaran

One-liner is probably doable with export = ?

mindplay-dk avatar Mar 05 '22 09:03 mindplay-dk

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

sergey-shpak avatar Mar 19 '22 14:03 sergey-shpak

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

mindplay-dk avatar Mar 22 '22 11:03 mindplay-dk

@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 [email protected], I haven't tested with other versions but should be ok

sergey-shpak avatar Mar 22 '22 15:03 sergey-shpak