Logo

JavaVaadinUpgrade

Vaadin 24 und die fehlenden Komponenten

Stefan Müller

·

12.11.2024, 16:00

Im Zuge der Migration eines unserer Projekte von Vaadin 23 auf Vaadin 24 kam es zu einigen unerwarteten Problemen, die nur im Produktiv-Umfeld sichtbar wurden. Die Umstellung war ohnehin schon ungewöhnlich aufwändig, da beim Upgrade auch die Spring Boot Version von 2.8 auf 3.2 aktualisiert werden musste, welche wiederum eine Umstellung auf die neuen jakarta. Packages erforderte.

Nach der Anpassung großer Teile des Codes an die neuen Gegebenheiten war es dann endlich soweit und wir konnten die Änderungen in unserem Staging-System testen. Bei den folgenden manuellen Tests stellten wir jedoch fest, dass einige der Komponenten von Vaadin fehlten oder sich merkwürdig verhielten. Es gab auch Komponenten, die zwar als fehlerhaft markiert und eingefärbt waren, bei denen aber die Fehlermeldungen fehlten oder in einem anderen Layout angezeigt wurden als in unseren lokalen Entwicklungsumgebungen.

Zunächst dachten wir, das Problem müsse in unserer CI-Pipeline liegen, weil es eindeutig nicht bei der Entwicklung aufgefallen war. Wir löschten alle Caches, prüften die Pipeline-Logs und installierten eine identische Java-Version, wie sie in der Pipeline verwendet wurde, auf den Entwicklungssystemen. Bei unseren lokalen Testbuilds konnten wir den Fehler trotzdem nicht reproduzieren.

Nachdem die Überprüfung der Pipeline nicht zur Lösung des Problems beigetragen hatte, versuchten wir die Schritte aus der Pipeline lokal nachzubilden. Dabei fiel uns auf, dass auf einem der Entwicklungssysteme ein Produktiv-Build entstand, der alle Komponenten korrekt anzeigte, während ein anderes Entwicklungssystem einen Produktiv-Build erstellte, in dem die Komponenten genauso fehlten wie in unserem Staging-System. Mit gradle kontrollierten wir zunächst, ob wir dieselben Versionen aller Abhängigkeiten auf beiden Systemen nutzten. Wir konnten jedoch keine Unterschiede feststellen. Wir konnten jedoch feststellen, dass ein Aufruf von gradlew vaadinClean dafür sorgte, dass auf beiden Entwicklungssystemen der selbe unvollständige Build entstand.

Beim Vergleich der Builds und der generierten Dateien unter frontend/generated ist dann aufgefallen, dass die Builds von den beiden Entwicklungssystemen sehr unterschiedlich aufgebaut waren. Auf beiden Systemen gab es die JavaScript-Komponenten im Bundle, aber die Komponenten waren sehr unterschiedlich auf Chunks (Einzelteile des Codes, die vom Browser bei Bedarf geladen werden) verteilt. In der Browser-Console konnte nachgestellt werden, dass die fehlenden Komponenten nicht in der customElement Registry des Browsers registriert waren.

Der folgende Code lieferte die Implementierung vom VerticalLayout von Vaadin:

customElements.get('vaadin-vertical-layout')

Der folgende Code lieferte leider kein Ergebnis zurück, obwohl wir die Komponente in unserem Code benutzten:

customElements.get('vcf-pdf-viewer')

Wir schlossen daraus, dass Vaadin 24 anders entscheidet, welche Komponenten mit in den JavaScript Build aufgenommen werden, als Vaadin 23, bei dem wir das Problem überhaupt nicht nachstellen konnten.

Eingrenzen der Ursache in unserem Code

Auffällig war, dass die Komponenten nur in einem unserer Views nicht funktionierten. Dieses View wird zum Ausfüllen von Formularen benutzt. Der Inhalt der Formulare wird dynamisch geladen, je nachdem, um was für eine Art von Formular es sich handelt. Das heißt anders als bei Routen kann das Vaadin Framework nicht herausfinden, welche Komponenten hier geladen und angezeigt werden.

var formInstance = formClass.getDeclaredConstructor().newInstance();
/* ... initialization code ... */
formContainer.add(formInstance);

@Uses Annotation

Wir benötigten also eine Möglichkeit, dem Vaadin Framework eine Liste mit den verwendeten Komponenten zu übergeben. Nach einiger Suche im Internet haben wir dann eine Möglichkeit gefunden, die Annotation @Uses.

Laut der Dokumentation sollte sogar eine Fehlermeldung im Log anzeigen, wenn Komponenten gebraucht werden, die aufgrund der neuen Optimierung bei der Aufführung fehlen. In unseren Logs erschien diese aber nie.

@Route(value = "case/form/:caseFormId", layout = MainLayout.class)
@Uses(PdfForm.class)
@Uses(CustomField.class)
@Uses(CurrencyField.class)
public class CaseFormView extends FlexLayout {
    /* ... */
}

Siehe auch:

Wir haben diese Lösung für den PDF Viewer ausprobiert und konnten das Problem so recht einfach beheben.

@LoadDependenciesOnStartup Annotation

Ein weiteres Problem an diesen dynamisch erstellten Komponenten ist erst deutlich später aufgefallen. Es wurden weitere Komponenten nicht korrekt dargestellt, wenn sie in den Routen zuvor nicht bereits benutzt wurden. Wir hatten natürlich nicht für jede Standard-Komponente von Vaadin eine @Uses Annotation hinzugefügt, da wir davon ausgingen, dass diese bereits auf anderen Routen referenziert wurden. Jedoch wurde in Vaadin 24 die Art und Weise geändert, wie Komponenten geladen werden, damit sie erst geladen werden, wenn sie für Routen benutzt werden. Dieses "Lazy Loading (On Demand)" sorgte dann dafür, dass administrative Benutzer, die frühzeitig bereits Views mit einem NumberField und Comboboxen benutzen, diese auch in den Formularen gesehen haben, die normalen Benutzer jedoch nicht. Für dieses Problem fanden wir mit der @LoadDependenciesOnStartup Annotation aber schnell eine dokumentierte Möglichkeit, mit der die im Production-Build enthaltenen Komponenten sofort beim Start geladen werden.

@LoadDependenciesOnStartup
public class AppShellConfig implements AppShellConfigurator {
    /* ... */
}

Siehe auch:

Fazit

Nachdem wir das Problem eingrenzen konnten, haben wir tatsächlich auch viele Ressourcen zu dem Thema gefunden. Im Blogeintrag für Vaadin 24.1 wird diese neue Art des Ladens der Komponenten sogar unter "Component loading optimizations" genannt. Dass diese Optimierungen aber Probleme für jene Komponenten bedeuten, die vom Vaadin Framework als nicht in der Route benutzt angesehen wurden, war uns nicht sofort klar. Erschwerend kam hinzu, dass weder in der Browser-Console noch im Backend-Log eine entsprechende Fehlermeldung aufgetaucht ist, dass die Komponente genutzt, aber nicht geladen ist.

Wir haben für uns beschlossen, ab jetzt das Dev-Bundle genauso optimieren zu lassen, wie das Production-Bundle. Dadurch fallen solche Probleme schon früh in der Entwicklungsphase auf. In Spring Boot Applications kann man das über folgende Einstellung in der application.yaml erreichen:

# * removes not used components from bundle like in production
# * lazy loads components when needed by route like in production
vaadin.devmode.optimizeBundle: true

Außerdem müssen wir daran denken, bei Problemen mindestens einmal gradlew vaadinClean aufzurufen, weil nicht jede Änderung am Code auch dazu führt, dass ein neues Dev-Bundle generiert wird.

Zusätzlich werden wir unsere automatisierten Browser-Tests ab jetzt mit dem Production-Bundle testen lassen, weil es merkbare Unterschiede zwischen dem Dev-Bundle und dem Production-Bundle geben kann.

  • Stylesheets werden im Dev-Bundle Inline in das HTML der Web-Anwendung integriert, im Production-Bundle werden sie als Ressourcen vom Server geladen.
  • Das Dev-Bundle enthält standardmäßig alle Komponenten, die im Classpath gefunden werden. Das Produktions-Bundle enthält nur jene, die von Vaadin als vom Code benutzt erkannt werden.
  • Das Dev-Bundle lädt alle Komponenten beim ersten Laden der Web-Anwendung. Das Production-Bundle nur jene, die für "/", "" oder "/login" als benutzt erkannt werden. Der Rest wird nachgeladen, wenn wir auf andere Routen navigieren.

Wir hoffen, dass dieser Artikel anderen Benutzern helfen kann, die aktuell auf der Suche nach einer Lösung für diese oder ähnliche Probleme sind. Bei uns sind die Best Practices entsprechend aktualisiert und wir haben in Zukunft einen Stolperstein weniger bei der Entwicklung mit dem Vaadin Framework.

Logo

© 2022-2024 Lucent Code GmbH
Made with ❤️ in Berlin, Germany