Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ideas for Lit 3.0 #3600

Closed
justinfagnani opened this issue Jan 23, 2023 · 7 comments
Closed

Ideas for Lit 3.0 #3600

justinfagnani opened this issue Jan 23, 2023 · 7 comments

Comments

@justinfagnani
Copy link
Collaborator

As we did well before planning lit-html 2.0 and lit-element 3.0, I'd like to start collecting ideas for what could be changed in a possible Lit 3.0.

To be really clear, there is no current plan for breaking changes, nor Lit 3.0. I just want to gather and document ideas that might require a new major version. There have been a lot of changes in the web platform in the last two years that we could possibly use that we should look at.

Also, it's important to remember that because of web components and Lit's very low-coupling between components, different versions of Lit are compatible with each other. Of course, this is not a panacea as some customers have single-version policies, and any upgrade pain is a cost sink against other development work.

This is not an exhaustive list, and some ideas in here may be bad! There's also a mix of large and small ideas, but even a small breaking change is a breaking change and should wait till the next major version.

Goals

As always our goals are to be as backwards compatible as possible, but also be smaller, faster, more standard, and more interoperable.

Platform features we could use

  • Decorators
  • Public class fields
  • Private class fields
  • WeakRefs
  • Class static initialization blocks
  • Native logical assignment and other misc ES2021+ features
  • Constructible stylesheets
  • Object.hasOwn()
  • Scoped custom element registries (maybe)

Possible changes

All packages

Drop IE 11 support

IE11 support is opt-in right now via the polyfill-support modules, but it costs us real time and effort in managing the CI runners for it. It would be very nice to officially drop support and not test on it aymore.

Publish ES2022 or later

This would reduce code size by letting us stop downleveling optional chaining, nullish coalescing, and logical assignment. Those expressions would get very slightly faster too.

Use native private fields

Native private fields are interesting not necessarily because of the runtime enforced privacy, but because all JS tooling knows that they're indeed private and what the visibility scope of the privacy is (the class body). Because there is no way for an expression outside of the class body to access a private field, they can be very safely renamed. This could drastically simply our build setup since we won't have to configure Terser to understand our naming conventions around what is renameable. It could even allow us to publish unminified sources again, since any modern Terser config would be able to apply good minification to our sources.

Use Object.hasOwn()

It's a few less characters, and can be torn off to be more minimizable:

const {hasOwn} = Object;

ReactiveElement and LitElement

Use native decorators

Native decorators (aka Stage 3 decorators) are well on their way to being implemented. They're in Babel and recently mostly landed in TypeScript.

Native decorators would allow us to deprecate our non-standard decorators, and eliminate the syntax differences between JavaScript and TypeScript usage.

We can add native decorators without breaking changes, but we could also simplify things by removing other ways of declaring reactive properties:

  • Remove support for the static properties block
  • Remove the createProperty() API
  • Remove addInitializer()
  • Remove the existing decorators

These could be disruptive changes, so would need a lot of deliberation.

Reorganize decorators

Right now @property() has a lot of options. Many are related to a property being an attribute. We might be able to get some simplification and clearer guidance if we split those up:

  • @property() creates a reactive property, and it's only a JS property that triggers an update when it changes.
  • @attribute() creates an observed attribute, and optionally reflects

@state() could go away, as it's just @property.

We could also try to guide usage a bit more around input and output properties. Generally a property should be used as input or output but not both. We could replace @property() with @input() and @output() decorators. Linters could warn if an element is writing to it's own @input(), and we could make an internal-only setter API for @output()s.

Stop reflecting initial values

Native elements do not spontaneously sprout attributes. Lit elements currently do for reflecting properties because we can't tell what is an initial value vs what's set externally. Native decorators may let us do that so we can reflect only on external values.

Assume native Constructible Stylesheets

This would let use simplify the CSS tag management code a little. We would need a great polyfill, however. It's also not a lot of code.

Make lifecycle more SSR friendly

We could make some lifecycle methods client-only and recommend moving client initialization into client-safe methods, etc.

lit-html

Land the template compiler

The template compiler pre-processes templates so that prep work doesn't have to be done at first-render time. We think it can improve first-render performance by 15-20%

What it doesn't do now is reduce code size. About 1k of lit-html is used to process templates, and since render() can take regular TemplateResults as well as CompiledTemplateResults, a minifier or bundler has to keep that code around. We could vend a function like renderCompiled() that doesn't call into the main code path and allows render() to be tree-shaken away.

Add spread operator and "attribute templates"

Spread ended up being difficult to add to lit-html because it introduces the possibility of collisions on binding names, and takes some effort to not introduce a dependency on the ordering of property changes on rendering. We may be able to revisit spread and make subtle breaking changes if necessary.

If we can do spread, then we can also do attr templates - which contain just attribute, property, and event bindings, but no host tag or child content.

Use WeakRefs for async directive detachment

Depending on what async directives do on disconnection, they make be able to use finalizer-timing to clean up other resources and so we could remove the complexity around notifying directives of disconnection from the DOM.

@superflows-dev
Copy link

superflows-dev commented Jan 24, 2023

Constructible stylesheets is must in my opinion.

Some more suggestions from my side:

  • I have encountered use-cases, where I have had to render the markup received from one slot in multiple locations. For instance, accepting menu ul via a slot and rendering it in two places - one for bigger screens and one for smaller mobile screens, say in a navbar

  • Currently nested slotted elements cannot be styled using the ::slotted selector. It would be great if that is made possible

  • Unless the component is loaded properly, slotted content doesn't appear nicely formatted. So currently the only option is using the following css from outside the component.

     <my-element>:not(:defined) {
        display: none;
      }

This is kind of tedious if multiple lit-element components are used in an application. Built-in support to manage this from inside the lit-element would be great!

@justinfagnani
Copy link
Collaborator Author

@superflows-dev Lit doesn't do anything with slots, and it's very likely to stay that way. The behaviors you're describing are standard shadow DOM behavior and custom elements behavior and any changes there should be taken up with WICG/webcomponents most likely.

@jpzwarte
Copy link
Contributor

jpzwarte commented Jan 24, 2023

I would love to see more built-in UI utilities (like Angular CDK):

  • an EventsController or @listen decorator that handles adding & removing the event handler automatically
  • an @event decorator for this.dispatchEvent(....)? @event() foo!: EventEmitter<boolean>; this.foo.emit(false);
  • a SelectionController for managing selection state; similar to SelectionModel from Angular CDK; also see https://github.com/sanomalearning/design-system/blob/main/packages/core/src/utils/controllers/selection.ts
  • a RovingTabindexController for managing keyboard interactivity; I think most design systems have these (including Spectrum, Shoelace and my own)
  • Perhaps more <slot> abstractions? I believe shoelace has a HasSlotController for detecting when a slot has assigned nodes/elements
  • Perhaps more basic Form Associated Custom Elements controllers/helpers/decorators/mixins etc. now that ElementInternals are on track to be supported in all browsers?

@Westbrook
Copy link
Contributor

There seems to be a handful of opt-in/pre-compile paths that could be perused, possibly without the need for breaking changes. Things that could be interesting:

Template pre-compiling
This has been discussed in the Lit team a number of times and it would be cool to follow through on delivering the initial runtime benefits that could offer to library consumers.

Support for static styles without Shadow DOM
That fact that turning Shadow DOM off means you get no control AT ALL over styles seems like a big library foot gun as there are a lot of people who don't understand Shadow DOM and want a path into custom elements with Lit, but aren't ready to turn that on. There are lots of caveats, which makes it completely understandable why you don't do it now, but taking the styles as provided and applying them to this.getRootNode().adoptedStyleSheets seems like something that would at least allow the styles to exist. Applying styles in this way could be an opt-in feature that allowed a whole new set of consumers to be empowered by Lit and custom elements.

Working in this area, it would seem that the apply path could be customizable, so that if there was a smarter place to put the styles that this.getRootNode().adoptedStyleSheets the developer could make that decision in a sub-class or similar. In the realm of customization, I'd expect that people would be interested in adding some form of synthetic scoping to the styles they were applying, so a pairing the apply with some sort of parse/scope action could be useful to them. A parse/scope method would allow the consuming developer to choose to do things like scope by custom element name (lowest level and only effects the CSS), scoped by paired class name application (.selector becomes .xyz .selector.xyz, IIRC, similar to things that shadyDOM did back in the day), or some other custom mechanism. parse/scope is really where this feature starts to benefit from pre-compilation of the code, even parsing a constructible style sheet takes times, especially if there are a lot of them, but if the pre-compiler processed the styles as expected at build time, that would pair well with template pre-compilation benefits at runtime that I've seen shared around in the past.

Support for some sort of :not(:defined) styles both in ElementRenderer but also at CSR time
At SSR time, not only will some custom elements benefit from defer-hydration style instructions, but some custom elements just shouldn't be SSR'd (say they've got copious DOM nodes or irresponsibly long data requests or whatever). It would be great if there was a way to outline styles that should apply in this case on the custom element itself that parents (whether at CSR or SSR time) could extract from the element and apply to themselves to allow the element to be "styled" until the appropriate time to upgrade and/or hydrate that element. This could be hung off of the element class in a form of static notDefinedStyles, which seems to better support CSR usage, or there could be some convention established for resolving these styles at SSR time.

static styles extractor
It's great that we're so close to using import styles from './styles.css' asset { type: 'css' };, but it's likely to take a while for tooling to handle that. What's more, there's likely to persist a healthy user base that enjoys that single file component structure of writing static styles = css... directly in their CSS. A Lit compiler could handle centralize the tooling of extracting static styles into an actual CSS file, and possible the bundling via something like w3c/csswg-drafts#5629 (comment), rather than leaving it up to various bundlers to build in support for this and ship plugins specifically supporting Lit.

Expanded built-in controller support
@jpzwarte points to a lot of valuable controller coverage that it would be good to have available in the developer community. Whether that's meant to be "built into Lit" or shipped by the community is always a hard line to find, but it would be great to see something that could benefit centralizing those tools, whether that meant moving them under the Lit umbrella or made them more findable/sharable for the next devs, so that we could work to making one or a few versions of the same better/faster/stronger rather than having each project reinvent the wheel. Could also start to structure conversations around Community Protocols for some or new browser features for others.

Getting started mode
Would be interesting to see how far Dev Mode warnings could be pushed in the form of something like a "getting started" mode that put on the rails development directly into the browsers Dev Tools.

@korgan00
Copy link

I miss from Vue the Scoped Slots.

It lets you delegate the rendering of some parts of the component using the variables given by the component.

One interesting use case is Lists. The component creates the list layout and can even hide part of the list (like infinite scrolling) and delegates into the parent the display of each element.

Another use case highlighted in the Vue guide is creating renderless components. This is just a bit of sugar in the code, but it is a good side effect.

@sorvell
Copy link
Member

sorvell commented Jan 25, 2023

With respect to slots, there's a nascent proposal @kevinpschaaf is working on that should address some of these use cases, see #3591.

@sorvell
Copy link
Member

sorvell commented Jan 25, 2023

Lit's update model is currently asynchronous. This provides a simple, interoperable way to efficiently render changes when properties are set (e.g el.foo = 5; el.bar = 6; renders once asynchronously, be default, at microtask timing).

The main drawback is that it means Lit element rendering doesn't exactly match how native elements update and render (e.g. input.value = 'hi' synchronously renders this value).

There are a couple of ways this could be improved:

  • add a flushing mechanism that could synchronously force rendering.
  • add a mechanism to automatically flush based on reading properties (similar to the way the platform will apply layout and styling changes sychronously when layout related properties are accessed).

@lit lit locked and limited conversation to collaborators Jan 26, 2023
@justinfagnani justinfagnani converted this issue into discussion #3604 Jan 26, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

6 participants