An automatic tool that facilitates migration from Polymer to Lit by automatically converting basic Polymer constructions into their Lit equivalents in Java and JavaScript source files.
Polymer support has been deprecated since Vaadin 18 (released in November 2020), in favor of faster and simpler Lit templates. In Vaadin 24, the built-in support for Polymer templates is removed. Polymer support is still available in Vaadin 24 for Prime and Enterprise customers.
The converter only supports a limited set of transformations intended to reduce the effort for basic cases while everything else should be still converted by hand.
Here are a few examples of what the converter cannot handle:
- A Java Model implementation can be only generated for internal models i.e that are declared within the View class.
- A Java Model implementation can be only generated for String and Boolean fields.
- TypeScript source files are not supported because proper type conversion almost always requires knowing project specifics.
- Complex Polymer observers are not supported.
static get properties() { return { userList: { type: String, observer: 'userListChanged(users.*, filter)" }, } }
Run the converter in your project's root folder as follows:
mvn vaadin:convert-polymer
./gradlew vaadinConvertPolymer
To convert a project that is based on Vaadin < 24, use the full Maven goal:
mvn com.vaadin:vaadin-maven-plugin:24.2-SNAPSHOT:convert-polymer
Or, in the case of using Gradle, add the following to build.gradle
:
buildscript {
repositories {
classpath 'com.vaadin:flow-gradle-plugin:24.2-SNAPSHOT'
}
}
The converter accepts the following properties:
By default, the converter scans all the files that match **/*.js
and **/*.java
and tries to convert them to Lit.
To limit conversion to a specific file or directory, you can use the vaadin.path
property:
mvn vaadin:convert-polymer -Dvaadin.path=path/to/your/file
./gradlew vaadinConvertPolymer -Dvaadin.path=path/to/your/file
The path is always relative to your project's root folder.
By default, the converter transforms Polymer imports into their Lit 2 equivalents.
If your project is using Lit 1 (Vaadin < 21), you can use the vaadin.useLit1
flag to enforce Lit 1 compatible imports:
mvn vaadin:convert-polymer -Dvaadin.useLit1
./gradlew vaadinConvertPolymer -Dvaadin.useLit1
By default, the converter transforms [[prop.sub.something]]
expressions into ${this.prop?.sub?.something}
.
If your project is using the Vaadin Webpack config, which doesn't support the JavaScript optional chaining operator (?.), you can use the vaadin.disableOptionalChaining
flag:
mvn vaadin:convert-polymer -Dvaadin.disableOptionalChaining
./gradlew vaadinConvertPolymer -Dvaadin.disableOptionalChaining
This is an overview of the transformations that can be performed automatically by the converter:
Imports
-import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
+import { html, LitElement, css } from 'lit';
Lifecycle callbacks
-ready() { ... }
+firstUpdated() { ... }
Templates and bindings
-static get template() {
- return html`
- <div>[[person.name]]</div>
- `
-}
+render() {
+ return html`
+ <div>${this.person?.name}</div>
+ `
+}
Nested properties are accessed through the optional chaining operator to keep that in line with how Polymer interprets expressions.
More complex expressions, such as containing method invocations, are also supported.
Default property values
Initializing default property values is moved to the constructor.
static get properties() {
return {
fruits: {
type: String,
- value: () => ['apple', 'banana']
}
}
}
+constructor() {
+ super();
+ this.value = ['apple', 'banana'];
+}
Computed properties
Computed properties are replaced with getters.
static get properties() {
return {
firstName: String,
lastName: String,
- fullName: {
- type: String,
- computed: 'computeFullName(firstName, lastName)',
- },
};
}
+get fullName() {
+ return this.computeFullName(this.firstName, this.lastName);
+}
computeFullName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
Event handlers
-<div on-click="onClick"></div>
+<div @click="${this.onClick}></div>
Two-way binding
Two-way binding is replaced with a pair of one-way binding and event handler.
-<input value="{{value}}" />
+<input
+ .value="${this.value}"
+ @value-changed="${(e) => (this.value = e.detail.value)}" />
Observers
Observers are replaced with a pair of getters and setters.
static get properties() {
return {
firstName: {
type: String,
- observer: '_firstNameChanged'
}
};
}
+set firstName() { ... }
+get firstName() { ... }
_firstNameChanged(newValue, oldValue) { ... }
<dom-if>
-<dom-if if="{{condition}}">...</dom-if>
+${condition && html`...`}
<dom-repeat>
-<template is="dom-repeat" items="{{items}}">
- <div>[[item]] [[index]]</div>
-</template>
+${items.map((item, index) =>
+ html`<div>${item} ${index}</div>`
+)}
<style>
-<style>
- :host {
- color: black;
- }
-</style>
+static get styles() {
+ return css`
+ :host {
+ color: black;
+ }
+ `
+}
Static node map
-this.$.container.textContent = 'Content';
+this.shadowRoot.querySelector('#container').textContent = 'Content';
Imports
-import com.vaadin.flow.component.polymertemplate.Id;
+import com.vaadin.flow.component.template.Id;
-import com.vaadin.flow.component.polymertemplate.PolymerTemplate;
+import com.vaadin.flow.component.littemplate.LitTemplate;
-import com.vaadin.flow.templatemodel.TemplateModel;
Views extending PolymerTemplate
PolymerTemplate extend is replaced with LitTemplate extend.
-public class UserEditView extends PolymerTemplate {
+public class UserEditView extends LitTemplate {
Models extending TemplateModel
TemplateModel extend is removed.
public class UserEditView extends LitTemplate {
- public interface Model extend TemplateModel { ... }
+ public interface Model { ... }
}
Model implementation
As Model
no longer extends TemplateModel
, the getModel()
method is added with a basic implementation of setters and getters.
The converter is only able to generate an implementation for String and Boolean fields. For others, it generates empty methods.
public class UserEditView extends LitTemplate {
public interface Model {
String getFirstName();
void setFirstName(String value);
}
+ private Model getModel() {
+ return new Model() {
+ @Override
+ public void setFirstName(String firstName) {
+ getElement().setProperty("firstName", firstName);
+ }
+
+ @Override
+ public String getFirstName() {
+ return getElement().getProperty("firstName", "");
+ }
+ }
+ }
}