Skip to content

Port the Flow client engine from GWT/Java to TypeScript and delete the GWT client - #25216

Open
totally-not-ai[bot] wants to merge 1 commit into
flow-client-ts-console-gatingfrom
flow-client-ts-cleanup-stale-refs
Open

Port the Flow client engine from GWT/Java to TypeScript and delete the GWT client#25216
totally-not-ai[bot] wants to merge 1 commit into
flow-client-ts-console-gatingfrom
flow-client-ts-cleanup-stale-refs

Conversation

@totally-not-ai

@totally-not-ai totally-not-ai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The Flow client engine — the code that runs in the browser, applies the server's state-tree changes to the DOM, and carries the client half of the UIDL protocol — was written in Java and compiled to JavaScript with GWT. This change replaces it with a hand-written TypeScript implementation under flow-client/src/main/frontend/internal, bundled with esbuild, and removes the GWT client engine and its build machinery entirely.

What changed

New TypeScript engine (79 modules). The port follows the Java sources one-to-one so the two can be diffed against each other: bootstrap (Bootstrapper, ApplicationConfiguration, ApplicationConnection), the reactive state tree (StateNode, StateTree, TreeChangeProcessor, node features, Reactive), the binding layer (SimpleElementBindingStrategy, TextBindingStrategy, ServerEventObject, Debouncer), networking (MessageSender, MessageHandler, XhrConnection, AtmospherePushConnection, reconnect/heartbeat/poll handling), and the support services (ResourceLoader, DependencyLoader, Profiler, SystemErrorHandler, PolymerUtils, LitUtils, …). The wire-format constants stay mirrored from their server-side counterparts.

Modernization enabled by dropping GWT. The DomApi abstraction and the Polymer/HTML-import DOM path are gone — the port uses native DOM directly in the binding layer and in ApplicationConnection, and the bootstrap no longer waits for an HTML-imports-ready gate (#25145, #25146). The GWT client fix from #25147, keeping replaced children in place until their replacements are inserted so a refilled container is never momentarily empty and the scroll position survives, is carried into the port.

Removal of the GWT client. All 105 Java classes under com.vaadin.client plus the bundled GWT support classes and com.vaadin.flow.linker.ClientEngineLinker are deleted, along with the client-engine <script> tag in BootstrapHandler, ApplicationConstants.CLIENT_ENGINE_PATH (it named a folder that is no longer served), the com/google/gwt/dev/js/globals resource, and the gwt.version property and gwt-elemental dependencyManagement entry in the root pom. No module depends on GWT any more.

Fixes on top of the port

  • getJavaClass read the wrong node property. The port declared a local JAVA_CLASS = "class" constant while NodeProperties.JAVA_CLASS is "jc", so the javaClass field of the dev-mode client.getNodeInfo(nodeId) API — used by dev tools to report the component type behind an element — was always null. The constant now lives in the TS NodeProperties table with the other mirrored wire names, and the ApplicationConnection test fixture, which hardcoded the same wrong key and hid the bug, now uses the keys the server actually writes.
  • The engine is imported statically in the bootstrap. doStartApplication used a dynamic import, which made startup asynchronous where the Java bootstrap was synchronous and split the engine into its own bundler chunk (4.8kB + 105kB + 1.2kB versus a single 111kB chunk). That chunk is always fetched, so the split only added a round-trip to the startup critical path. No engine module has top-level side effects and the module graph is acyclic, so static import is safe; a test asserts the client API is published by the time doStartApplication returns.
  • Production-mode console log gating is restored. Console.java suppressed browser logging in production unless the vaadin.browserLog localStorage flag was set; the port carried over only the flag check and called console.* directly from every module, so all 64 log statements across 21 modules logged unconditionally — including the raw session-resynchronization response body. Console now has setProductionMode and gated debug/log/warn/error, ApplicationConfiguration calls setProductionMode again, and an eslint no-console rule over src/main/frontend/internal keeps it from regressing. Two sites stay ungated (marked) because their Java counterparts were JSNI calling console.error directly.

Also cleaned up: comments describing the port as work in progress ("not ported yet", "contracts satisfied at cutover", $entry wrappers, constraints from the HtmlUnit-based GwtTests) are reworded to describe the code as it is, keeping references to the GWT original where they explain why something looks the way it does; the MIGRATION_STRATEGY.md pointer, which never existed in this repository, is dropped. SessionCloseLogoutIT no longer whitelists console errors from the removed client engine path.

API Changes

165 classes affected: 105 classes removed, 1 constant removed.

com.vaadin.flow.shared.ApplicationConstants

// Removed
public static final String CLIENT_ENGINE_PATH // path to the client engine folder; folder is no longer served

com.vaadin.flow.linker.ClientEngineLinker

// Removed
public class ClientEngineLinker extends AbstractLinker // GWT linker for the compiled client engine

com.vaadin.client.* (GWT client engine — 104 classes removed)

The entire GWT client engine is deleted. All of these types were public; they were only ever consumed by the GWT compiler and by client-side add-ons compiled against the engine, and have no replacement on the Java side — the equivalents now live as TypeScript modules in flow-client/src/main/frontend/internal.

// Removed
public class com.vaadin.client.ApplicationConfiguration
public class com.vaadin.client.ApplicationConnection
public class com.vaadin.client.BrowserInfo
public interface com.vaadin.client.Command
public class com.vaadin.client.ConnectionIndicator
public class com.vaadin.client.Console
public class com.vaadin.client.DefaultRegistry
public class com.vaadin.client.DependencyLoader
public class com.vaadin.client.ElementUtil
public class com.vaadin.client.ExecuteJavaScriptElementUtils
public class com.vaadin.client.ExistingElementMap
public class com.vaadin.client.InitialPropertiesHandler
public class com.vaadin.client.LitUtils
public class com.vaadin.client.PolymerUtils
public class com.vaadin.client.Profiler
public class com.vaadin.client.ReactUtils
public class com.vaadin.client.Registry
public class com.vaadin.client.ResourceLoader
public class com.vaadin.client.StorageUtil
public class com.vaadin.client.SystemErrorHandler
public class com.vaadin.client.TrackingScheduler
public class com.vaadin.client.UILifecycle
public class com.vaadin.client.URIResolver
public final class com.vaadin.client.ValueMap
public class com.vaadin.client.WidgetUtil

public class com.vaadin.client.bootstrap.Bootstrapper
public final class com.vaadin.client.bootstrap.ErrorMessage
public final class com.vaadin.client.bootstrap.JsoConfiguration
public class com.vaadin.client.bootstrap.LocationParser

public class com.vaadin.client.communication.AtmospherePushConnection
public interface com.vaadin.client.communication.ConnectionStateHandler
public class com.vaadin.client.communication.DefaultConnectionStateHandler
public class com.vaadin.client.communication.Heartbeat
public class com.vaadin.client.communication.LoadingIndicatorConfigurator
public class com.vaadin.client.communication.LoadingIndicatorStateHandler
public class com.vaadin.client.communication.MessageHandler
public class com.vaadin.client.communication.MessageSender
public class com.vaadin.client.communication.PollConfigurator
public class com.vaadin.client.communication.Poller
public class com.vaadin.client.communication.PushConfiguration
public interface com.vaadin.client.communication.PushConnection
public interface com.vaadin.client.communication.PushConnectionFactory
public class com.vaadin.client.communication.ReconnectConfiguration
public class com.vaadin.client.communication.ReconnectionAttemptEvent
public class com.vaadin.client.communication.RequestResponseTracker
public class com.vaadin.client.communication.RequestStartingEvent
public class com.vaadin.client.communication.ResponseHandlingEndedEvent
public class com.vaadin.client.communication.ResponseHandlingStartedEvent
public class com.vaadin.client.communication.ServerConnector
public class com.vaadin.client.communication.ServerRpcQueue
public class com.vaadin.client.communication.XhrConnection
public class com.vaadin.client.communication.XhrConnectionError

public class com.vaadin.client.flow.ConstantPool
public class com.vaadin.client.flow.ExecuteJavaScriptProcessor
public class com.vaadin.client.flow.NodeUnregisterEvent
public interface com.vaadin.client.flow.NodeUnregisterListener
public class com.vaadin.client.flow.StateNode
public class com.vaadin.client.flow.StateTree
public class com.vaadin.client.flow.TreeChangeProcessor

public class com.vaadin.client.flow.binding.Binder
public interface com.vaadin.client.flow.binding.BinderContext
public interface com.vaadin.client.flow.binding.BindingStrategy
public class com.vaadin.client.flow.binding.Debouncer
public class com.vaadin.client.flow.binding.ServerEventHandlerBinder
public final class com.vaadin.client.flow.binding.ServerEventObject
public class com.vaadin.client.flow.binding.SimpleElementBindingStrategy
public class com.vaadin.client.flow.binding.TextBindingStrategy

public interface com.vaadin.client.flow.collection.JsArray
public class com.vaadin.client.flow.collection.JsCollections
public interface com.vaadin.client.flow.collection.JsMap
public interface com.vaadin.client.flow.collection.JsSet
public interface com.vaadin.client.flow.collection.JsWeakMap
public class com.vaadin.client.flow.collection.jre.JreJsArray
public class com.vaadin.client.flow.collection.jre.JreJsMap
public class com.vaadin.client.flow.collection.jre.JreJsSet
public class com.vaadin.client.flow.collection.jre.JreJsWeakMap

public class com.vaadin.client.flow.dom.DomApi
public interface com.vaadin.client.flow.dom.DomElement
public interface com.vaadin.client.flow.dom.DomNode

public class com.vaadin.client.flow.model.UpdatableModelProperties

public class com.vaadin.client.flow.nodefeature.ListSpliceEvent
public interface com.vaadin.client.flow.nodefeature.ListSpliceListener
public class com.vaadin.client.flow.nodefeature.MapProperty
public class com.vaadin.client.flow.nodefeature.MapPropertyAddEvent
public interface com.vaadin.client.flow.nodefeature.MapPropertyAddListener
public class com.vaadin.client.flow.nodefeature.MapPropertyChangeEvent
public interface com.vaadin.client.flow.nodefeature.MapPropertyChangeListener
public abstract class com.vaadin.client.flow.nodefeature.NodeFeature
public class com.vaadin.client.flow.nodefeature.NodeList
public class com.vaadin.client.flow.nodefeature.NodeMap

public class com.vaadin.client.flow.reactive.Computation
public interface com.vaadin.client.flow.reactive.FlushListener
public class com.vaadin.client.flow.reactive.InvalidateEvent
public interface com.vaadin.client.flow.reactive.InvalidateListener
public class com.vaadin.client.flow.reactive.Reactive
public class com.vaadin.client.flow.reactive.ReactiveEventRouter
public interface com.vaadin.client.flow.reactive.ReactiveValue
public class com.vaadin.client.flow.reactive.ReactiveValueChangeEvent
public interface com.vaadin.client.flow.reactive.ReactiveValueChangeListener

public class com.vaadin.client.flow.util.ClientJsonCodec
public class com.vaadin.client.flow.util.JsObject
public final class com.vaadin.client.flow.util.NativeFunction

public class com.vaadin.client.gwt.com.google.web.bindery.event.shared.SimpleEventBus
public final class com.vaadin.client.gwt.elemental.js.util.Xhr

Follow-up to #25210. The port comments described work in progress -- "built
alongside the Java version", "not ported yet", "contracts satisfied at cutover",
constraints imposed by the HtmlUnit-based GwtTests, and $entry wrappers on the
Java side -- none of which is true now that the GWT client is gone. publishClient
still claimed it was "not yet wired into the bootstrap", pointing at a
MIGRATION_STRATEGY.md that does not exist in the repository, and
SimpleElementBindingStrategy said its functions would be assembled into a class
that it already defines. Reword them to describe the code as it is, keeping the
references to the GWT original where they explain why something looks the way it
does. The "Slice N:" section headers, which numbered the migration steps, lose
the numbering and keep their titles.

Also remove what the GWT deletion left behind: the com/google/gwt/dev/js/globals
resource, the gwt.version property and the gwt-elemental dependencyManagement
entry in the root pom (no module depends on GWT any more), and
ApplicationConstants.CLIENT_ENGINE_PATH, which named a folder that is no longer
served or referenced. SessionCloseLogoutIT no longer allows console errors from
the removed client engine path.
@totally-not-ai totally-not-ai Bot changed the title flow client ts cleanup stale refs Port the Flow client engine from GWT/Java to TypeScript and delete the GWT client Aug 13, 2026
@Artur-
Artur- changed the base branch from main to flow-client-ts-console-gating August 13, 2026 13:08
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

 1 424 files   -  26   1 509 suites  +58   1h 29m 5s ⏱️ + 1m 37s
10 579 tests +167  10 510 ✅ +165  67 💤 ±0  0 ❌ ±0  2 🔥 +2 
11 015 runs  +167  10 945 ✅ +165  68 💤 ±0  0 ❌ ±0  2 🔥 +2 

For more details on these errors, see this check.

Results for commit a5ba6fc. ± Comparison against base commit dc167bc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant