Lit Launch Day: Lit 3.0, Labs graduations, a compiler and more!

We're launching the next major version of Lit

Photo of Lit Team
Lit Team

Lit Launch Day: Lit 3.0, Labs graduations, a compiler and more!

It's launch day for the Lit project, and we have a bunch of exciting releases to share with the Lit and web components communities!

After several months of development, the Lit team is happy to announce the final release of Lit 3.0 – our first major version since Lit 2.0 in early 2021, the first graduating class of Lit Labs packages @lit/react, @lit/task, and @lit/context, and two bonus releases @lit-labs/compiler and @lit-labs/preact-signals.

This is a big release, so here are links to the individual announcements:

Lit 3.0: Bye, Bye IE, Hello TC39 Decorators!

We value stability for our community. Breaking changes are a cost that every user and the entire ecosystem has to bear: projects have to upgrade, multiple versions can be included in an app, and documents, samples, tutorials, starter kits, etc. have to be updated for every breaking change.

So we want to only make breaking changes when we must, or when the benefits to the community clearly outweigh the costs. For benefits we tend look for decreased code size, increased performance, reduction in maintenance burden, and better alignment with standards.

We also follow semantic versioning, so we increase the major version only when there are breaking changes. New features typically arrive in new minor versions, which are backwards compatible. So Lit 3.0 is intended to only be a breaking change, with no new features. There is one exception though – standard decorators!

Breaking changes

For Lit 3.0, the biggest breaking change is that we've dropped IE11 support. After surveying our developer community, we feel like now is the right time to say goodbye to IE, and very few customers will be affected.

This release is also an opportunity to make a few additional breaking changes that trim out some technical debt to unlock new features we have scheduled for our 3.x release series and beyond.

These changes are hopefully minor for most customers. If you run Lit 2.x with no deprecation warnings, and use toolchain that supports modern JS, this should be a seamless upgrade!

Here are the biggest things Lit 3.0 changes:

  • IE11 is no longer supported.
  • Lit's npm modules are now published as ES2021.
  • APIs deprecated with the Lit 2.0 release have been removed.
  • SSR hydration support modules were moved to the @lit-labs/ssr-client package.
  • Decorator behavior has been unified between TypeScript experimental decorators and standard decorators.
  • Support was removed for Babel decorators version "2018-09"

Full details in the the upgrade guide.

New: standard decorators support

The one new feature that we did add to Lit 3.0 is support for the TC39 standard decorators specification to our existing decorators.

The new decorator spec has reached Stage 3 in TC39, meaning that browsers and compilers are now implementing them. This is a huge step for Lit! The arrival of standard decorators allows us to begin the process of moving to a decorator implementation that won't require a compiler to use.

It's very important to us to make the upgrade path from experimental decorators as smooth as possible. To accomplish this we made our existing decorators support the standard spec and made them work with the new accessor keyword in experimental decorator mode.

This way you can use Lit decorators with auto-accessors (using the accessor keyword) so that the same call site works with both settings:

Once all decorator call sites in your project use the accessor keyword, you can build with experimentalDecorators set to true or false without a change in behavior.

But in order to make these hybrid decorators have consistent behavior in both modes, we had to make a few minor breaking changes to our experimental decorators:

  • We now call requestUpdate() automatically for @property and @state decorated accessors where previously that was the setter's responsibility.
  • The value of an accessor is read on first render and used as the initial value for changedProperties and attribute reflection.
  • @property and @state must be used on the setter for hand-written accessors.

Standard decorator mode requires TypeScript 5.2 or Babel 7.23 with the @babel/plugin-proposal-decorators plugin using decorator version "2023-05".

Upgrading

The upgrade from Lit 2.0 should be seamless for the vast majority of users. You can usually upgrade your npm dependency version with:

You can find more details and how to handle the changes in the Lit 3 upgrade guide.

Detailed change logs can be found on GitHub.

Even Faster Rendering with the new Lit Template Compiler

The new @lit-labs/compiler Labs package provides a TypeScript Transformer that can be run over your JavaScript or TypeScript files to do build-time preparation of Lit templates that Lit would normally do at runtime.

Lit Compiler benchmarks

While not all templates will see rendering performance improvements from compilation, on our "template heavy" benchmark we measured a 46% faster first render, and a 21% faster update!

To try out @lit-labs/compiler today, you’ll need a build step that accepts a TypeScript transformer. For Rollup.js users, this could be @rollup/plugin-typescript. An example rollup.config.js file might look like:

What does the transform do?

Given some source code containing an html tag function to declare templates:

The Lit template transform will remove the html tag function and replace it with something similar to the following:

We call this a compiled template, and it behaves the same as your authored template, except that when Lit renders the compiled template, Lit can skip an internal render phase called the Prepare phase, meaning you get a quicker initial render.

As you can see in the above example, there is some additional code generated as part of the transform. We’ve measured that minified and compressed, you may get a 5% increase in file size for the compiled file. This is something we have plans to address.

Looking forward

We’d love to hear from you and get feedback on your experience using the transform, as well as hear what you’d like to see optimized! Leave that feedback in this Labs Feedback discussion. We’d also like to learn more about what build systems Lit is used in, and welcome contributions!

This is just the beginning. With this new package we have a foundation for layering on additional build-time optimizations. Some optimizations we’ve thought about:

  • For a Lit app which can be completely compiled, we could vend an import of lit-html that is smaller.
  • Add an option to the compiler transform to also minify the HTML in the templates.
  • Do build-time evaluation of other parts of the Lit API, such as compiling away the built-in Lit decorators.
  • Compress the emitted output file by applying domain-specific file compression.

Preact Signals integration

Signals: they're so hot right now!

Many frameworks are adopting signals – reactive holders of state and computation – for performance and DX improvements. Lit already has a very efficient rendering system, and our preliminary benchmarks don't show a clear performance wins from signals.

For Lit developers we think signals promise to offer a convenient and relatively simple option for shared observable state, a recurring need in our community. So we are starting to explore what it would look like to integrate signals with Lit with a new @lit-labs/preact-signals package.

This package provides three ways to use Preact Signals:

  1. A SignalWatcher mixin, which makes a component automatically watch all signals used during updates.
  2. A watch() directive, which watches one signal and updates a binding with it.
  3. A special html template tag that auto-applies the watch() directive to all bindings that use signals.

Here's an example of using the SignalWatcher mixin:

Why Preact Signals?

One issue that signals present for web components is that there are many different signals implementations and no interoperability between them. This goes against the interoperability goals of web components, so for now, rather than build our own signals package or pick just one that we endorse, we plan on offering integration packages to be able to use various signal libraries with Lit.

These integrations will be relatively simple because we have two straight-forward ways of using signals:

  1. Treat a component's update lifecycle as an effect, so that it's run when any signals it accesses (like in templates) changes. This integrates signals with Lit's batching async lifecycle for good performance when many signals change.
  2. Use a directive to wire a signal directly to a location in DOM.

We chose Preact's signals library for our first integration because it's a relatively small, fast, and easy-to-understand implementation published to npm as standard JS modules.

Lit Labs Graduation Day

We are also graduating our first set of Lit Labs packages: Context, Task, and React.

These packages have a new home in the @lit npm scope, but are otherwise exactly the same as the current labs versions. The labs packages have been updated to depend on and re-export the non-labs versions so that they share a single implementation.

Find them in their new npm homes: @lit/react, @lit/task, and @lit/context

These packages can now be considered stable, and have documentation and examples on lit.dev!

Current users of these packages under the @lit-labs scope can migrate by first updating to the latest version of those packages to test for any breakages, then update the import to use the @lit scoped package. The latest @lit-labs scoped packages will receive the same updates until the next major version of the @lit scoped package.

Thank you so much to everyone in the community who has tested out a Labs package, filed issues, discussed features, and led these packages to graduation! 🎓

React

While custom elements can be used in React projects as is, there are some rough edges around their usage, namely:

  • Setting properties on elements (rather than attributes)
  • Adding handlers for custom events
  • Type checking custom elements and their props

Some of these are being addressed by React in a future version, but they are currently only present in experimental builds not recommended for production.

@lit/react allows creation of React components wrapping the custom element. The created components can be idiomatic to React such that users of the component do not have to worry about the inner web component implementation.

It is useful for both web component authors who wish to vend React versions of their components for users to reach a wider audience, as well as React developers who wish to use a neat web component that they found in their project more ergonomically.

@lit-labs/react has been our most popular labs project by far with over 500k weekly npm downloads and is already being used by many web component libraries to provide React versions of their components to users.

Read more about it at our React framework integration documentation.

Task

Working with asynchronous data in a Lit component tends to involve boiler plate code, and there are a few subtle edge cases to handle. Task is a simple ReactiveController that automatically handles these edge cases and makes it easy to do asynchronous work correctly.

Some considerations include:

  • Conditionally rendering different content while the data is pending, retrieved, or the request failed
  • Handling race conditions, ensuring the data for the latest request is kept and previous requests are cancelled or ignored if completed later

We've found @lit-labs/task indispensable when working with asynchronous values. It includes a number of refinements since its initial release, like how every task receives an AbortSignal that the library ensures aborts if the task run becomes obsolete.

We are happy for it to officially graduate and encourage everyone to use it! For more info see the new Async Tasks docs page.

Context

@lit/context is an interoperable system for an element to request data from any of its ancestors. It's useful for shared elements to receive configuration from their environment, for elements in a composable plugin system to exhange data, and as an interoperable DOM-based dependency injection system.

Start by creating a context using the createContext() function. A context acts as a key, for the consumer and provider of the data to identify each other.

Then, on the providing element, declare a property with the @provide decorator. As with the main lit decorators, these work as standard decorators, and with TypeScript's experimentalDecorators option enabled. The property's value will be made available to any descendents of the element that consume the context.

Finally, on the consuming element, declare a property with the @consume decorator. When the element attaches to the DOM it will receive the current value of the first ancestor that provides the context. If called with subscribe: true then it will also receive updates as the value changes.

For more info, see the Context documentation.

We're excited to see what you build with Lit!

We're constantly amazed by the things the Lit and web components communities are building, and can't wait to see what's going to be made with Lit 3.0 and our growing collection of helper libraries.

Please drop by our Discord server, GitHub discussions or find us on the site formerly known as Twitter to join our community and show us what you're up to!

Thanks!,

-The Lit Team

Read more posts