localvoid / ivi

Lighweight Embeddable Web UI Library
MIT License
724 stars 22 forks source link

hoist inline fns? #60

Open leeoniya opened 1 year ago

leeoniya commented 1 year ago

on the topic of hoisting...

this example:

const Example = component((c) => {
  const [counter, setCounter] = useState(c, 0);

  const increment = () => {
    setCounter(counter() + 1);
  };

  return () => (
    htm`
    <div>
      <p>Count: ${counter()}</p>
      <button @click=${increment}>Increment</button>
    </div>`
  );
});

can be this if the click handler is auto-hoisted? this can prevent garbage listeners from accumulating without having to manually hoist.

const Example = component((c) => {
  const [counter, setCounter] = useState(c, 0);

  return () => (
    htm`
    <div>
      <p>Count: ${counter()}</p>
      <button @click=${() => setCounter(counter() + 1)}>Increment</button>
    </div>`
  );
});
localvoid commented 1 year ago

The easiest general-purpose solution can be something like this https://github.com/localvoid/hoistr . This typescript transformer won't work in the example above because it hoists to the module scope, but the idea is to wrap any function into hoist() function and babel plugin/ts transformer/etc should hoist it to the outermost valid scope.

For use-cases like in the example above it should be auto-hoisted. But I am not sure if auto-hoisting should be applied to all template expressions or just event handlers. If it can be applied to all template expressions, it should be quite straightforward to add such optimization by transforming expressions in the babel plugin https://github.com/localvoid/ivi/blob/43cdf6f747dc782883ca73bdb5b4e21fa9c27655/packages/%40ivi/babel-plugin/client.js#L228-L229

leeoniya commented 1 year ago

But I am not sure if auto-hoisting should be applied to all template expressions or just event handlers.

i think it can be auto-applied to any expression that does not use anything from the deeper scopes. you can hoist it as high as the nearest used symbol's scope?

EDIT: maybe it can only be done safely for inline anon fns, not all expressions. e.g. you cannot hoist attr=${counter() + 1} since this value can change between render invocations.