syntax-tree/unist-builder

Allow "type" to be a function (as a "component")

Closed this issue · 7 comments

Initial checklist

Problem

Related to #10. If we think it's cool to write unists as JSX, a natural conclusion is we must support components as well.

Solution

I propose to support this pattern:

const root = u(Comp, { id: 1 }, u("leaf", "leaf 1"), u("leaf", "leaf 2"));

Which will be equivalent to

const root = Comp({ id: 1, children: [u("leaf", "leaf 1"), u("leaf", "leaf 2")] });

We don't necessarily need to support "class components", just the simple idea that you can provide a function which will be called with props and children.

Alternatives

If we are to support JSX—there doesn't seem to be another way.

Hmm, not sure, I’d want a really good use case for functions-that-get-called-with-props.
This package is pretty tiny and used in a lot of places, I can see component support getting bigger and bigger and I’d rather not have that

I’d want a really good use case for functions-that-get-called-with-props.

True, I don't really have a compelling use-case that can't be implemented by calling the function myself. I'm just exploring the possibility if we allow writing ASTs as JSX. For example, we can have a component that selectively renders to either a blockquote, or a paragraph, and doing so declaratively by keeping it JSX.

I can see component support getting bigger and bigger

The idea of "component" here will be really simple. It would simply be

if (typeof type === "function") {
  return type({ ...props, children: value });
}

I wonder whether a good higher-level mdastscript might be better at all this then?

unist accepts a string value as well. Or nothing. And its impossible to discern props from a node: hast has problems with this too, because button elements can have a type property for example: https://github.com/syntax-tree/hastscript/blob/2f8c133920fa57405815de3f3cdc21d1bc892444/lib/core.js#L78

Sigh yeah ambiguity is a problem. We probably can't do all this without deprecating a few APIs. This particular proposal doesn't really introduce ambiguity, though, since I'm proposing to allow Function in addition to string as type, which always exists. #10 is more likely to be ambiguous, but we can still work around it by only collecting rest parameters when there are more than two parameters.

When you say mdastscript, do you mean a hypothetical new package, or the deprecated existing one, or the newer mdast-builder? The API design of mdast-builder looks, frankly, even less appealing to me since it's not compatible with JSX at all.

I’m somewhat open to this, but not super excited about it either.
I’m not really sure what it’ll look like either, and how it’ll be used.

Yep, mdastscript/mdast-builder/etc are a hypothetical package that I myself don’t really need (I like how explicit and clear plain objects are, I don’t need to translate function calls to them in my head), but other folks have been asking about over the years.

Is this something you’d want to work on? Or can experiment with in some other place?

  • This would make it easy to create broken nodes: those without a type field. Especially if we’d want users to write a type in props, that would then make something like #10 (comment) even harder, because props is than an actual node
  • Another big question here is what children is, it could be a string (in which case you’d get value: string in props), it could be missing (no extra fields in props), it could be an array (children: Array<Node> in props).

Ok closing, I’m not really sure about the trade offs!