ryansolid / babel-plugin-jsx-dom-expressions

A JSX to DOM plugin that wraps expressions for fine grained change detection
MIT License
60 stars 10 forks source link

Idea: extract component generator to separated package #29

Closed mduclehcm closed 4 years ago

mduclehcm commented 4 years ago

I like your idea about dom-express so much. This is an excellent idea!!!

But i like the way svelte detect dirty more than dom-expression dependencies tracking. If you don't mind, can i refactor your source code to extract component generate logic to separated package (or helpers inside this repo). `babel-plugin-jsx-dom-expressions' can accept component generator as options. So I can create other component generator for my purpose.

Thanks for reading.

ryansolid commented 4 years ago

I'm gathering you are talking about the compilation rather than a way of providing a different runtime function. A runtime method would be easy. The challenge, of course, is dealing with a divergence of features supported, as admittedly I've been only accounting for dependency tracking reactive approaches like S, Knockout, MobX, Solid etc... I've been improving my understanding of generalizing even those library approaches. But the resulting dom-expressions wrapper isn't exactly vanilla MobX etc... It's shaped to fit details like hierarchal disposal patterns and context. My next set of large changes I'm planning for this repo is revamping how hydration works (and possibly modifying SSR). Which I can see adding other bits of these custom layers.

In theory, I'm not opposed. I'd be interested in how you would picture this working. I only understand Svelte's approach high-level but if only the component code that needed adjustment I'd be surprised. I guess the wrap function is generalizable. But my understanding is that Svelte takes a more Component-based holistic way of laying out the update function that extends beyond just the view code this library handles including the $ expressions as well. Anyway if you can share a few more details I think that'd help.

mduclehcm commented 4 years ago

Sorry for my bad english. What i wan't to do is compile to something like this

// code.js
export function Counter() {
  let count = 1;

  function inc() {
    count += 1;
  }

  return (
    <div>
      <p>Counter: {count}</p>
      <button onClick={inc}>inc</button>
    </div>
  );
}
// output.js
import { createComponent, ComponentOptions } from './internal/createComponent';
import { createTemplate } from './internal/createTemplate';

function createOptions(): ComponentOptions {
  const createElement = createTemplate(
    '<div><p>Counter: </p><button>inc</button></div>',
  );

  let el, /* text_1 */el_1, /* button_1 */ el_2;

  function create() {
    el = createElement();
    el_1 = document.createTextNode('');
    el.firstChild.appendChild(el_1);
    el_2 = el.firstChild.nextSibling;
    return el;
  }

  function mount(ctx) {
    el_2.addEventListener('click', /* el_2: onClick */ ctx[2]);
  }

  function render(dirty, ctx) {
    if (dirty & /* count */ 1) {
      el_1.data = ctx[1];
    }
  }

  function instance(setState) {
    let count = 1;

    function handleClick() {
      setState(/* count */ 1, (count += 1));
    }

    return [count, handleClick];
  }

  return {
    create,
    mount,
    render,
    instance,
  };
}

export const Counter = createComponent(createOptions);
ryansolid commented 4 years ago

Hmm.. yeah that looks like Svelte. It's also considerably different from what I'm doing here. Like it has to detect things are components and handle them appropriately. And involves manipulating more than just JSX. Basically you are looking for a single file component structure that uses JSX instead of a string, faux HTML DSL.

Whereas this library isn't nearly as structurally strict. However, it is specific on how it handles bindings matching them to the dom-expressions runtime. There are no Components it just converts JSX. You can have 8 Components in a file, or 0. I just look for JSX entry and spit out real DOM nodes with wrapped binding expressions. Then creating a Component is just call a function. createComponent is a create instance wrapping just isolates reactive scope and wraps props.

This would have to probably look for the default export function decide if that's a Component parse the JS body to pull into the instance. Parse through the bindings to assign them to generated lifecycle functions. Look for variables that are listened to by those bindings declared in local scope and augment all instances of mutation with an invalidator function.

Let's compare the current output:

import { template as _$template, insert as _$insert, delegateEvents as _$delegateEvents } from "runtime"

const _tmpl$ = _$template(`<div><p>Counter: </p><button>inc</button></div>`);

export function Counter() {
  let count = 1;

  function inc() {
    count += 1;
  }

  return function () {
    const _el$ = _tmpl$.cloneNode(true),
          _el$2 = _el$.firstChild,
          _el$3 = _el$2.firstChild,
          _el$4 = _el$2.nextSibling;

    _$insert(_el$2, count, null);

    _el$4.__click = inc;
    return _el$;
  }();
}

_$delegateEvents(["click"]);

I'm thinking these are too different, The overlap is actually pretty small. If I'm wrong that'd be great. But I think as you play with it you might come to the same conclusion.

mduclehcm commented 4 years ago

@ryansolid Do we have group chat for this library? Can I join with you to work on ssr and hydrate parts?

ryansolid commented 4 years ago

I can get one up. So far most people have talked through the Solid community since it is the project that is really driving this development. But it also has a lot of specific talk about Solid APIs and questions. The one thing that I've been working through is how much of the approach should be runtime or compiler. The thing that I've realized over time is both parts are important. There are some big limits to compiler only approach, but you can almost always use compilation to make the code more efficient at runtime.

I've set up this gitter for dom-expressions which I think is probably the right genericity for this right now as both parts are intertwined. There are people looking and testing this from the Solid side but I imagine they are more going to drive the requirements(expectations) and the finesse of the final API. But the generalization and actual core implementation probably needs its own focus. There are just a decent number of touch points. Since for example on the Solid side, Suspense is a big part of the picture but this library at its core doesn't have the concept. I'm currently getting the feeling runtime is a big part of this.

In any case I'd love help even if just to work through some ideas with. This area is definitely a challenge for this approach as there aren't "components" to act as boundaries.

Anyway I just made this: https://gitter.im/dom-expressions/community And you might want to see this thread: https://github.com/ryansolid/solid/issues/109