Skip to content

[lit-html] Add spread directive and attr templates#1960

Draft
justinfagnani wants to merge 4 commits into
mainfrom
spread
Draft

[lit-html] Add spread directive and attr templates#1960
justinfagnani wants to merge 4 commits into
mainfrom
spread

Conversation

@justinfagnani

@justinfagnani justinfagnani commented Jun 15, 2021

Copy link
Copy Markdown
Collaborator

This needs more tests and I'm not entirely happy with using the private object this way, but the general approach works and is small.

Tests needed:

  • - AsyncDirective lifecycle in all binding types
  • - Multiple spread directives on an element
  • - Removing the spread directive
  • - Collisions between spread and normal bindings

@github-actions

github-actions Bot commented Jun 15, 2021

Copy link
Copy Markdown
Contributor

📊 Tachometer Benchmark Results

Summary

  • lit-element-list
  • lit-html-kitchen-sink
  • lit-html-repeat
  • lit-html-template-heavy
  • reactive-element-list

Results

tachometer-reporter-action v2 for Benchmarks

readonly [name: string]: unknown;
}

class SpreadDirective extends Directive {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider whether this should be an AsyncDirective so that its disconnected callback can undo all the parts if you render a different value to the expression (or whether spread must be static), e.g....

<div ${bool ? spread({foo, bar}) : nothing}></div>

To be fair, we don't do a good thing for e.g. classMap in this situation either (it would also need to be disconnectable), which seemed a bit overkill given the performance hit:

<div class=${bool ? classMap({foo, bar}) : nothing}></div>

@justinfagnani justinfagnani Jun 16, 2021

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this is the most complete implementation, but given that it's more costly it might be better to keep things simple. I think this pattern should suffice:

html`<div ${spread(bool ? {foo, bar} : undefined)}></div>`

}
}

render(_values: SpreadValues) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can imagine use cases where it'd be nice to let values be nullable, to avoid users from needing to always make a throwaway object in the alternate case:

<div ${spread(maybeGotStuff ?? {})}></div>

or

<div ${spread(shouldRenderStuff ? stuff : {})}></div>

Again, we don't do this in classMap/styleMap, but I think it's more likely to have patterns where entire bags are forwarded in (e.g. "formControlAttributes") vs. being generated by the user (otherwise the user could have just written the attribute bindings themselves).

But it does sort of beg the question what we expect spread to actually be used for in the OSS world.

@justinfagnani justinfagnani Jun 16, 2021

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, as per the other thread.

I imagine this form of spread to be a lower level thing that's often wrapped for better DX. An element might receive a bag of property or event bindings and need to add the correct prefix. attr will wrap this. Custom attribute-merging strategies will... do something. We need to make sure that's possible.

}

render(_values: SpreadValues) {
// TODO for SSR

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any nascent ideas here? Seems like we could go two ways here:

  • Return an array of descriptors of the part types + values, and figure out how to reuse the attribute-part opcode handling to handle the per-part-specific stuff:
    case 'attribute-part': {
  • Return the array of acutal part instances (iow, use the update code) that the attribute-part opcode handling can directly use, and figure out how to deal with the fact that you don't have an actual part to get the element to pass in render (which isn't technically a problem in SSR because it doesn't need the element).

We do need to reuse code in the attribute-part opcode handling to ensure directives passed to spread work correctly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure yet. I think something like just forwarding the object in an AttributeResult type might work, even if it's a bit weird to explain how that works or doesn't client-side.

I think figuring out hydration is the hard part here...

@LarsDenBakker

Copy link
Copy Markdown
Contributor

How do you feel about having a directive which spreads only properties, and one that spread binding like the current implementation? A very common use case is spreading an object of properties, often coming from some other source. Right now users would need to always transform the object to have all keys start with a .. Or they won't notice and always set attributes, which isn't great.

@justinfagnani

justinfagnani commented Jun 16, 2021

Copy link
Copy Markdown
Collaborator Author

@LarsDenBakker

I think that's a good idea, but hopefully can be well expressed as a wrapper on this:

spreadProps = (props) => spread(Object.fromEntries(Object.entries().map(([k, v]) => ['.' + k, v])));

If the Object.fromEntries(Object.entries()..) bit is too much work we can look at customizing the name->part mapping with a callback.

@LarsDenBakker

Copy link
Copy Markdown
Contributor

@justinfagnani that wrapper would do quite a lot of work each render though, so would be nice to make that more convenient.

@abdonrd

abdonrd commented Aug 20, 2021

Copy link
Copy Markdown
Contributor

Is there news on this? Thanks in advance!

The Carbon team is interested in use it:
carbon-design-system/carbon-web-components#657 (comment)

It's one of the main things they are waiting to update to Lit v2.

cc @jeffchew

@justinfagnani justinfagnani modified the milestones: Lit RC.next, Lit 1.1 Aug 20, 2021
@changeset-bot

changeset-bot Bot commented Nov 3, 2021

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 501fca4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@justinfagnani justinfagnani changed the title [lit-html] Add spread directive [lit-html] Add spread directive and attr templates Nov 3, 2021
@justinfagnani
justinfagnani marked this pull request as draft November 4, 2021 00:21
@daKmoR

daKmoR commented Jan 10, 2022

Copy link
Copy Markdown
Contributor

any chance this is going to happen within January 2022?

we are thinking about waiting or hacking our own version for now 😅

@samanime

samanime commented Jan 12, 2022

Copy link
Copy Markdown

It'd be great to have this feature soon. I had upgraded without realizing it was missing and now I'll need to downgrade back.

If figuring out how to be deterministic when there are multiple objects with duplicate keys, why not simply limit it to a maximum of one spread operation per element and let them merge objects before. Then you don't have to try and come up with a deterministic approach for everyone, as they can solve it beforehand themselves.

This PR was started back 7 months ago and we still don't have a solution. =S

@songkeys

Copy link
Copy Markdown

It's been a whole year. What is the current status of this feature?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants