Replace the GWT client engine with a TypeScript implementation - #25210
Closed
totally-not-ai[bot] wants to merge 83 commits into
Closed
Replace the GWT client engine with a TypeScript implementation#25210totally-not-ai[bot] wants to merge 83 commits into
totally-not-ai[bot] wants to merge 83 commits into
Conversation
First step of the GWT->TypeScript client migration (see MIGRATION_PR_PLAN.md). Ports the dependency-free leaf utilities and their unit tests: assert, Console, WidgetUtil, BrowserInfo, JsArray, StorageUtil, SharedUtil, DomApi, Profiler. Also raises the typescript-eslint default-project file cap (default 8) in eslint.config.js: the src/test/frontend test files run under the default project, and this PR pushes the count past 8, so the cap must grow with the suite. Additive only -- nothing is wired into the running client (GWT remains live); the modules are exercised by mocha (45 tests).
Ports the reactive dependency-tracking core and the node-feature layer: reactive, NodeFeature, NodeFeatures, NodeMap, NodeList, MapProperty. Depends only on the leaf utilities (PR1); the nodefeature layer is decoupled from the state tree via the MapPropertyOwner interface. Additive only -- not wired into the running client. Exercised by mocha (38 tests).
Ports the state-tree layer (StateNode, StateTree, TreeChangeProcessor, ConstantPool, ExistingElementMap) and the JSON codec primitives it decodes values through (ClientJsonCodec, JsonConstants). Depends on PR1 leaves + PR2 reactive/nodefeature. Additive only -- not wired into the running client. Exercised by mocha (42 tests).
…tore unregisterNode assert (review)
Ports the element-binding layer that syncs StateNode data to the DOM: SimpleElementBindingStrategy (+ the Polymer model-property binding split-out), Binder, BindingStrategy, TextBindingStrategy, ServerEventHandlerBinder, ServerEventObject, Debouncer, ElementUtil, LitUtils, ReactUtils, PolymerUtils, PolymerModelTree, and UpdatableModelProperties. Depends on PR1-3 (leaves, reactive/nodefeature, state tree); event dispatch to the server is via injected callbacks, so no communication-layer dependency yet. Additive only -- not wired into the running client. Exercised by mocha (28 test files).
…ouncer flushAll deviation (review)
…cle to TypeScript Ports the support-services layer: resource/dependency loading (ResourceRegistry, ResourceLoader, DependencyLoader, EagerDependencyTracker), server-driven JS execution (ExecuteJavaScriptProcessor, ExecuteJavaScriptElementUtils), the application configuration + lifecycle (ApplicationConfiguration, JsoConfiguration, UILifecycle, InitialPropertiesHandler, LocationParser), the deferred-send scheduler (TrackingScheduler), the Registry base, and SystemErrorHandler. Depends on PR1-4; closes over already-ported code (no network-layer dependency). Additive only -- not wired into the running client. Exercised by mocha (86 tests).
Ports the communication + push + connection-state cluster (mutually dependent, so landed together): message handling and RPC (MessageHandler, MessageSender + sendBeacon, ServerConnector, ServerRpcQueue, MessageOrdering, RequestResponseTracker, XhrConnection, ResynchronizationState, URIResolver), push (PushConnection, AtmospherePushConnection, PushConfiguration), and connection state (ConnectionStateHandler, DefaultConnectionStateHandler, ReconnectStateMachine, ReconnectConfiguration, ConnectionMessageType, ConnectionIndicator, LoadingIndicator configurator/handler, Heartbeat, Poller, PollConfigurator). Depends on PR1-5; services are injected via Registry rather than imported, so it closes over ported code. Additive only -- not wired into the running client. Exercised by mocha (102 tests).
Ports the top layer that wires the client together: DefaultRegistry (the DI hub that instantiates every service), ApplicationConnection (the engine entry via ApplicationConnection.create), Bootstrapper, and the published client API (publishClient, clientApi). Completes the TypeScript port of the client engine -- all 80 internal modules are now present and unit-tested. Still additive: nothing is wired into the running client (GWT remains live); the cutover is a separate step. Depends on PR1-6. Exercised by mocha (23 tests).
With the TypeScript engine now the live client (previous commit), the GWT client is dead. Remove it: the com.vaadin.client.* sources and their JUnit + GWTTestCase tests, the ClientEngine*.gwt.xml modules, ClientEngineLinker and its single-script template, and the GWT dependencies / gwt-maven-plugin / GWT test wiring from pom.xml and package.json. flow-client is now a TypeScript + resources module with no Java sources. Nothing outside flow-client referenced com.vaadin.client.
…tion + document config coercion (review)
…cript FlowClient.init() -- imported and called by Flow.ts -- is generated by scripts/client.js. Change the generator to emit the TypeScript bootstrap (onModuleLoad from Bootstrapper) instead of wrapping the compiled GWT engine blob, making the TypeScript engine the live client. The GWT compile output is no longer read. The full mocha suite, including FlowTests (which exercises the bootstrap), passes against the TypeScript client. The GWT sources and build wiring are removed in the follow-up PR.
BootstrapHandler emitted a client.nocache.js <script> and computed the GWT client engine URL, but the live client loads via Flow.ts -> FlowClient, so this path is dead once the GWT engine is gone. Remove getClientEngineUrl/getClientEngine, the CLIENT_ENGINE_NOCACHE_FILE constant and its emission in setupFrameworkLibraries, and the matching compile.properties mock in MockServletServiceSessionSetup. Nothing referenced the removed API.
…t exclusions (review)
…mock stub (review)
Mirrors the GWT client fix #25147 in the TypeScript port: when the server clears and refills a container in one change set, the old children are now kept until the replacements are inserted, so the container is never momentarily empty and the surrounding scroll position is preserved. Uses native DOM (DomApi is removed from this port).
Console.java suppressed all browser logging in production mode unless the localStorage flag vaadin.browserLog was set to "true", and ApplicationConfiguration.setProductionMode fed the flag in via Console.setProductionMode. The TypeScript port only carried over isLocalStorageFlagEnabled, dropped the setProductionMode call and had every ported module call console.debug/log/warn/error directly, so all 64 log statements across 21 engine modules logged unconditionally -- including a console.log of the raw session-resynchronization response body. Port the rest of Console: setProductionMode plus gated debug/log/warn/error, route the engine's logging through it, and reinstate the setProductionMode call in ApplicationConfiguration. Two sites keep logging ungated because their Java counterparts were JSNI calling console.error directly (ResourceLoader's dynamic dependency promise) or reported the failure outside the gate via Console.reportStacktrace (ExecuteJavaScriptProcessor.invoke); both are marked. An eslint no-console rule over src/main/frontend/internal keeps the gating from regressing.
9b627a7d10. The Java diff is 105 deleted files plus 2 modified files whose changes are entirely private (BootstrapHandler's CLIENT_ENGINE_NOCACHE_FILE constant and three private methods; ClassesSerializableTest's exclusion list inside a method body) — no API impact from those two.
|
Contributor
doStartApplication loaded ApplicationConnection through a dynamic import, which made the application start asynchronously where the Java bootstrap was synchronous. The stated reason -- keeping the engine out of the bundle the HtmlUnit-based GwtTests load -- no longer applies now that the GWT client and its tests are gone. The dynamic import also split the engine into its own bundler chunk (esbuild with code splitting: 4.8kB entry + 105kB engine + 1.2kB shared, versus a single 111kB chunk when imported statically). That chunk is always fetched, since Flow.ts imports FlowClient only when it is about to start a UI, so the split only added a round-trip to the startup critical path. Import the engine statically. No engine module has top-level side effects and the module graph is free of cycles, so evaluating it when FlowClient is imported is safe. Covered by a test asserting the client API is published by the time doStartApplication returns, which fails with the dynamic import.
9b627a7d10. The Java diff is 105 deleted files plus 2 modified files whose changes are entirely private (BootstrapHandler's CLIENT_ENGINE_NOCACHE_FILE constant and three private methods; ClassesSerializableTest's exclusion list inside a method body) — no API impact from those two.The engine assembly port declared its own JAVA_CLASS constant with the value "class", but NodeProperties.JAVA_CLASS is "jc". getJavaClass therefore read a key the server never writes and always returned null, so the javaClass field of the dev-mode client.getNodeInfo(nodeId) API -- used by the dev tools to report the component type behind an element -- was always empty. Drop the local constant and add JAVA_CLASS to the TS NodeProperties table, so the value sits with the other wire names mirrored from com.vaadin.flow.internal.nodefeature.NodeProperties and cannot drift on its own again. The ApplicationConnection test fixture hardcoded the same wrong key, which is why this passed unnoticed; it now uses the wire names the server actually writes and fails if the constant is wrong.
Artur-
pushed a commit
that referenced
this pull request
Aug 14, 2026
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.
Member
|
Commits distributed into the PRs that introduced the code they fix. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
Removes the GWT-based Flow client engine and replaces it with a TypeScript port that ships as part of the frontend bundle.
flow-client/src/main/frontend/internal/, a module-for-module port of the Java client: bootstrap (Bootstrapper,ApplicationConfiguration,ApplicationConnection), the reactive core and node features (StateNode,StateTree,NodeMap,NodeList,MapProperty,TreeChangeProcessor), the binding layer (Binder,SimpleElementBindingStrategy,TextBindingStrategy,Debouncer,ServerEventObject), networking (MessageSender,MessageHandler,XhrConnection,AtmospherePushConnection,Heartbeat,Poller, reconnect/loading-indicator state handling), and the support services (DependencyLoader,ResourceLoader,Profiler,PolymerUtils,LitUtils,ReactUtils,WidgetUtil,URIResolver, …).com.vaadin.client.**, the GWT linkercom.vaadin.flow.linker.ClientEngineLinker, the*.gwt.xmlmodules and the single-script template, thesrc/test-gwtHtmlUnit suite, and the corresponding Java unit tests.flow-client/pom.xmldrops thegwt-maven-plugin, thegwt-user/gwt-dev/gwt-elementaldependencies, thesdmprofile, and the GWT-specific surefire/spotless/build-helper configuration.BootstrapHandlerno longer resolves and injects aclient.nocache.js/ compiled client-engine<script>tag.scripts/client.jsno longer inlines the compiled GWT bundle intoFlowClient.js; it emits a small entry point that callsonModuleLoad()from the TypeScript bootstrap.src/test/frontend/covering the ported modules, replacing the deleted Java/GWT tests.Why
The GWT toolchain was the last piece of the client that could not be built, linted, debugged, or tested with the same tooling as the rest of the frontend. Porting it removes a separate compiler and dependency stack, drops the extra client-engine script request from bootstrap, and makes the client engine ordinary TypeScript that ships through the existing bundler.
Behavior fixes made during the port
DomApiabstraction and the Polymer/HTML-import DOM path are gone; the binding layer,ApplicationConnection, and dependency loading use native DOM directly, and the HTML-imports-ready bootstrap gate is removed (chore: remove Polymer 1 DOM API and HTML import loading #25145, refactor: use the elemental DOM API directly in the client engine #25146).client.getNodeInfo(nodeId).javaClassworks again — the engine read node property"class"instead of the"jc"the server writes.JAVA_CLASSnow lives in theNodePropertiestable with the other wire names mirrored fromcom.vaadin.flow.internal.nodefeature.NodeProperties; the test fixture that hardcoded the wrong key now uses real wire names.Console.setProductionMode, so all 64 log statements across 21 engine modules logged unconditionally (including the raw session-resynchronization response body). Logging is routed through the portedConsoleagain, and an eslintno-consolerule oversrc/main/frontend/internalkeeps it that way; two deliberately ungated sites are marked.import(), matching the old Java bootstrap and removing an always-fetched 105 kB chunk from the startup critical path.API Changes
API Changes: flow-server-remove-client-engine vs origin/main
105 public classes/interfaces removed (all of
com.vaadin.client.**pluscom.vaadin.flow.linker.ClientEngineLinker); no additions and no signature changes. All public/protected members of these types are removed with them. The only other Java edits (BootstrapHandler,ClassesSerializableTest) touch private members and test configuration only.com.vaadin.client
com.vaadin.client.bootstrap
com.vaadin.client.communication
com.vaadin.client.flow
com.vaadin.client.flow.binding
com.vaadin.client.flow.collection
com.vaadin.client.flow.dom
com.vaadin.client.flow.model
com.vaadin.client.flow.nodefeature
com.vaadin.client.flow.reactive
com.vaadin.client.flow.util
com.vaadin.client.gwt
com.vaadin.flow.linker.ClientEngineLinker