Lit 2.0 is designed to work with most code written for LitElement 2.x and lit-html 1.x. There are a small number of changes required to migrate your code to Lit 2.0. The high-level changes required include:
- Updating npm packages and import paths.
- Loading
polyfill-supportscript when loading the webcomponents polyfills. - Updating any custom directive implementations to use new class-based API and associated helpers
- Updating code to renamed APIs.
- Adapting to minor breaking changes, mostly in uncommon cases.
The following sections will go through each of these changes in detail.
Update packages and import paths
Use the lit package
Lit 2.0 ships with a one-stop-shop lit package, which consolidates lit-html and lit-element into an easy-to-use package. Use the following commands to upgrade:
npm uninstall lit-element lit-html
npm install lit
And re-write your module imports appropriately:
From:
import {LitElement, html} from `lit-element`;
To:
import {LitElement, html} from `lit`;
Although the lit-element@^3 and lit-html@^2 packages should be largely backward-compatible, we recommend updating to the lit package as the other packages are moving towards eventual deprecation.
Update decorator imports
The previous version of lit-element exported all TypeScript decorators from the main module. In Lit 2.0, these have been moved to a separate module, to enable smaller bundle sizes when the decorators are unused.
From:
import {property, customElement} from `lit-element`;
To:
import {property, customElement} from `lit/decorators.js`;
Update directive imports
Built-in lit-html directives are also now exported from the lit package.
From:
import {repeat} from `lit-html/directives/repeat.js`;
To:
import {repeat} from `lit/directives/repeat.js`;
Load polyfill-support when using webcomponents polyfills
Lit 2.0 still supports the same browsers down to IE11. However, given the broad adoption of Web Components APIs in modern browsers, we have taken the opportunity to move all of the code required for interfacing with the webcomponents polyfills out of the core libraries and into an opt-in support file, so that the tax for supporting older browsers is only paid when required.
In general, any time you use the webcomponents polyfills, you should also load the lit/polyfill-support.js support file once on the page, similar to a polyfill. For example:
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
<script src="node_modules/lit/platform-support.js"></script>
If using @web/test-runner or @web/dev-server with the legacyPlugin for development, adding the following configuration to your web-test-runner.config.js or web-dev-server.config.js file will configure it to automatically inject the support file when needed:
export default {
...
plugins: [
legacyPlugin({
polyfills: {
webcomponents: true,
custom: [
{
name: 'lit-polyfill-support',
path: 'node_modules/lit/polyfill-support.js',
test: "!('attachShadow' in Element.prototype)",
module: false,
},
],
},
}),
],
};
Update to renamed APIs
The following advanced API's have been renamed in Lit 2.0. It should be safe to simply rename these across your codebase if used:
| Previous name | New name | Notes |
|---|---|---|
UpdatingElement |
ReactiveElement |
The base class underpinning LitElement. Naming now aligns with terminology we use to describe its reactive lifecycle. |
@internalProperty |
@state |
Decorator for LitElement / ReactiveElement used to denote private state that trigger updates, as opposed to public properties on the element settable by the user which use the @property decorator. |
static getStyles() |
static finalizeStyles(styles) |
Method on LitElement and ReactiveElement class used for overriding style processing. Note it now also takes an argument reflecting the static styles for the class. |
_getUpdateComplete() |
getUpdateComplete() |
Method on LitElement and ReactiveElement class used for overriding the updateComplete promise |
NodePart |
ChildPart |
Typically only used in directive code; see below. |
Update custom directive implementations
While the API for using directives should be 100% backward-compatible with
1.x, there is a breaking change to how custom directives are authored. The API
change improves ergonomics around making stateful directives while providing a
clear pattern for SSR-compatible directives: only render will be called on the
server, while update will not be.
Overview of directive API changes
| Concept | Previous API | New API |
|---|---|---|
| Code idiom | Function that takes directive arguments, and returns function that takes part and returns value |
Class that extends Directive with update & render methods which accept directive arguments |
| Declarative rendering | Pass value to part.setValue() |
Return value from render() method |
| DOM manipulation | Implement in directive function | Implement in update() method |
| State | Stored in WeakMap keyed on part |
Stored in class instance fields |
| Part validation | instanceof check on part in every render |
part.type check in constructor |
| Async updates | part.setValue(v);part.commit(); |
Extend AsyncDirective instead of Directive and call this.setValue(v) |
Example directive migration
Below is an example of a lit-html 1.x directive, and how to migrate it to the new API:
1.x Directive API:
import {html, directive, Part, NodePart} from 'lit-html';
// State stored in WeakMap
const previousState: WeakMap<Part, number> = new WeakMap();
// Functional-based directive API
export const renderCounter = directive((initialValue: number) => (part: Part) => {
// When necessary, validate part type each render using `instanceof`
if (!(part instanceof NodePart)) {
throw new Error('renderCounter only supports NodePart');
}
// Retrieve value from previous state
let value = previousState.get(part);
// Update state
if (value === undefined) {
value = initialValue;
} else {
value++;
}
// Store state
previousState.set(part, value);
// Update part with new rendering
part.setValue(html`<p>${value}</p>`);
});
2.0 Directive API:
import {html} from 'lit';
import {directive, Directive, Part, PartInfo, PartType} from 'lit/directive.js';
// Class-based directive API
export class RenderCounter extends Directive {
// State stored in class field
value: number | undefined;
constructor(partInfo: PartInfo) {
super(partInfo);
// When necessary, validate part in constructor using `part.type`
if (partInfo.type !== PartType.CHILD) {
throw new Error('renderCounter only supports child expressions');
}
}
// Optional: override update to perform any direct DOM manipulation
update(part: Part, [initialValue]: DirectiveParameters<this>) {
/* Any imperative updates to DOM/parts would go here */
return this.render(initialValue);
}
// Do SSR-compatible rendering (arguments are passed from call site)
render(initialValue: number) {
// Previous state available on class field
if (this.value === undefined) {
this.value = initialValue;
} else {
this.value++;
}
return html`<p>${this.value}</p>`;
}
}
export const renderCounter = directive(RenderCounter);
Adapt to minor breaking changes
For completeness, the following is a list of minor but notable breaking changes that you may need to adapt your code to. We expect these changes to affect relatively few users.
LitElement
- The
updateandrendercallbacks will only be called when the element is connected to the document. If an element is disconnected while an update is pending, or if an update is requested while the element is disconnected, update callbacks will be called if/when the element is re-connected. - For simplicity,
requestUpdateno longer returns a Promise. Instead await theupdateCompletePromise. - Errors that occur during the update cycle were previously squelched to allow subsequent updates to proceed normally. Now errors are re-fired asynchronously so they can be detected. Errors can be observed via an
unhandledrejectionevent handler on window. - Creation of
shadowRootviacreateRenderRootand support for applyingstatic stylesto theshadowRoothas moved fromLitElementtoReactiveElement. - The
createRenderRootmethod is now called just before the first update rather than in the constructor. Element code can not assume therenderRootexists before the elementhasUpdated. This change was made for compatibility with server-side rendering. ReactiveElement'sinitializemethod has been removed. This work is now done in the element constructor.- The static
rendermethod on theLitElementbase class has been removed. This was primarily used for implementing ShadyDOM integration, and was not intended as a user-overridable method. ShadyDOM integration is now achieved via thepolyfill-supportmodule. - When a property declaration is
reflect: trueand itstoAttributefunction returnsundefinedthe attribute is now removed where previously it was left unchanged (#872). - The dirty check in
attributeChangedCallbackhas been removed. While technically breaking, in practice it should very rarely be (#699). - LitElement's
adoptStylesmethod has been removed. Styling is now adopted increateRenderRoot. This method may be overridden to customize this behavior. - Removed
requestUpdateInternal. TherequestUpdatemethod is now identical to this method and should be used instead. - The type of the
cssfunction has been changed toCSSResultGroupand is now the same asLitElement.styles. This avoids the need to cast thestylesproperty toanywhen a subclass setsstylesto an Array and its super class set a single value (or visa versa).
lit-html
render()no longer clears the container it's rendered to on first render. It now appends to the container by default.- Expressions in comments are not rendered or updated.
- Template caching happens per callsite, not per template-tag/callsize pair. This means some rare forms of highly dynamic template tags are no longer supported.
- Arrays and other iterables passed to attribute bindings are not specially handled. Arrays will be rendered with their default toString representation. This means that
html`<div class=${['a', 'b']}>will render<div class="a,b">instead of<div class="a b">. To get the old behavior, usearray.join(' '). - The
templateFactoryoption ofRenderOptionshas been removed. TemplateProcessorhas been removed.- Symbols are not converted to a string before mutating DOM, so passing a Symbol to an attribute or text binding will result in an exception.
Forward-compatible migration
lit-html 1.4.0 adds a number of forward-compatible modules to assist migration from 1.x to 2.x, so that many of the changes to prepare for Lit 2.0 can be done incrementally in the 1.x codebase before upgrading the entire app.
lit-html/directive.js- Class-based directive APIlit-html/async-directive.js- Class based async directive API (notedisconnectedandreconnectedcallbacks will not be called in 1.x)lit-html/directive-helpers.js- Provides forward-compatible implementation ofisTemplateResult
In addition, imports from lit-html 1.x and lit-element 2.x will continue to work after upgrading to the Lit 2.0 versions (lit-html 2.x and lit-element 3.x). However, once all components are upgraded in an app, we recommend switching imports to the lit package as described above, as the separate packages may be deprecated in future major releases.