sourceMaps = new ArrayList<>(List.of(buildFolder
+ .listFiles((dir, name) -> name.endsWith(".js.map"))));
+ Assert.assertFalse(
+ "The dev bundle should have been built with sourcemaps "
+ + "enabled, see vite.config.ts",
+ sourceMaps.isEmpty());
+ return sourceMaps;
+ }
+}
diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/vite.config.ts b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/vite.config.ts
index fa078b4d2e1..9263febf2dc 100644
--- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/vite.config.ts
+++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/vite.config.ts
@@ -23,7 +23,12 @@ function addCssToIndex(): PluginOption {
const customConfig: UserConfigFn = (env) => ({
// Here you can add custom Vite parameters
// https://vitejs.dev/config/
- plugins: [addCssToIndex()]
+ plugins: [addCssToIndex()],
+ build: {
+ // Emit separate .map files for the dev bundle so that
+ // DevBundleSourceMapsIT can verify that the build plugins keep them usable
+ sourcemap: true
+ }
});
export default overrideVaadinConfig(customConfig);
From 06830901d4e05747412515ee3b6287f8ab12afa5 Mon Sep 17 00:00:00 2001
From: "totally-not-ai[bot]"
<290682512+totally-not-ai[bot]@users.noreply.github.com>
Date: Mon, 14 Sep 2026 17:31:37 +0000
Subject: [PATCH 2/2] test: cover the usage statistics rewrite and share the
sourcemap checks
The dev bundle test application did not contain a module the usage
statistics plugin rewrites, so the branch that produces a sourcemap of
its own was never run. It now imports a stand-in for the
@vaadin/vaadin-usage-statistics module, and the test also asserts that
the comment of that module is rewritten in the built chunk.
The sourcemap assertions are shared with the production bundle test
instead of being copied.
---
.../flow/testutil/SourceMapTestUtil.java | 78 ++++++++++++
.../main/frontend/vaadin-usage-statistics.js | 14 +++
.../com/vaadin/flow/frontend/LitView.java | 3 +
.../flow/frontend/DevBundleSourceMapsIT.java | 114 ++++++++++--------
.../java/com/vaadin/viteapp/SourceMapsIT.java | 32 +----
5 files changed, 163 insertions(+), 78 deletions(-)
create mode 100644 flow-test-util/src/main/java/com/vaadin/flow/testutil/SourceMapTestUtil.java
create mode 100644 flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics.js
diff --git a/flow-test-util/src/main/java/com/vaadin/flow/testutil/SourceMapTestUtil.java b/flow-test-util/src/main/java/com/vaadin/flow/testutil/SourceMapTestUtil.java
new file mode 100644
index 00000000000..d1ce191d368
--- /dev/null
+++ b/flow-test-util/src/main/java/com/vaadin/flow/testutil/SourceMapTestUtil.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2026 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.testutil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.json.JsonMapper;
+
+/**
+ * Helpers for the tests that check the sourcemaps a frontend build emits.
+ *
+ * A build plugin that rewrites a module or a chunk has to chain its sourcemap
+ * onto the one the bundler already has, otherwise the emitted map ends up
+ * without the original sources and the browser cannot map the bundle back to
+ * them.
+ */
+public final class SourceMapTestUtil {
+
+ private SourceMapTestUtil() {
+ }
+
+ /**
+ * Asserts that the given sourcemap can be used to map the bundle it belongs
+ * to back to the files it was built from.
+ *
+ * @param name
+ * the name of the sourcemap, used in the assertion messages
+ * @param sourceMap
+ * the contents of the sourcemap file
+ * @return the original files the sourcemap refers to
+ */
+ public static List assertSourceMapUsable(String name,
+ String sourceMap) {
+ JsonNode contents = JsonMapper.shared().readTree(sourceMap);
+ JsonNode sources = contents.get("sources");
+ JsonNode sourcesContent = contents.get("sourcesContent");
+
+ Assert.assertNotEquals(name + " should have a sourcemap with sources",
+ 0, sources.size());
+ Assert.assertNotEquals(name + " should have a sourcemap with mappings",
+ "", contents.get("mappings").asString());
+ Assert.assertEquals(
+ name + " should have the contents of every source in its "
+ + "sourcemap",
+ sources.size(), sourcesContent.size());
+
+ List sourceNames = new ArrayList<>();
+ for (int i = 0; i < sources.size(); i++) {
+ String source = sources.get(i).asString();
+ Assert.assertNotEquals(
+ name + " should have a sourcemap referring to the "
+ + "original files, was " + source,
+ "", source.trim());
+ Assert.assertNotEquals(
+ name + " should have the contents of " + source
+ + " in its sourcemap",
+ "", sourcesContent.get(i).asString().trim());
+ sourceNames.add(source.replace('\\', '/'));
+ }
+ return sourceNames;
+ }
+}
diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics.js b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics.js
new file mode 100644
index 00000000000..12372f856d5
--- /dev/null
+++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics.js
@@ -0,0 +1,14 @@
+// Stands in for the @vaadin/vaadin-usage-statistics package, which a
+// Flow-only application does not pull in. The build plugin that keeps the
+// comment below in the bundle matches on the file name and on the marker, so
+// this file is enough to run the build through that plugin.
+/** vaadin-dev-mode:start
+(function () {
+ window.Vaadin = window.Vaadin || {};
+ window.Vaadin.registrations = window.Vaadin.registrations || [];
+})();
+vaadin-dev-mode:end **/
+
+// Keeps the module, and with it the comment, out of reach of tree shaking
+window.Vaadin = window.Vaadin || {};
+window.Vaadin.usageStatisticsStandIn = true;
diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/LitView.java b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/LitView.java
index e23d7b2ed06..e0f1249b261 100644
--- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/LitView.java
+++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/LitView.java
@@ -23,6 +23,9 @@
@Route("com.vaadin.flow.frontend.LitView")
@Tag("lit-view")
@JsModule("./views/lit-view.ts")
+// Brings the usage statistics stand-in into the bundle, so that the build runs
+// the plugin that keeps its vaadin-dev-mode comment, see DevBundleSourceMapsIT
+@JsModule("./vaadin-usage-statistics.js")
public class LitView extends LitTemplate {
}
diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleSourceMapsIT.java b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleSourceMapsIT.java
index 6332bf0ca52..db41aea17c3 100644
--- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleSourceMapsIT.java
+++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleSourceMapsIT.java
@@ -23,13 +23,13 @@
import org.apache.commons.io.FileUtils;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
-import tools.jackson.databind.JsonNode;
-import tools.jackson.databind.json.JsonMapper;
import com.vaadin.flow.server.Constants;
import com.vaadin.flow.testutil.ChromeBrowserTest;
+import com.vaadin.flow.testutil.SourceMapTestUtil;
/**
* A build plugin that hands back the code of a module without a sourcemap drops
@@ -44,80 +44,94 @@
public class DevBundleSourceMapsIT extends ChromeBrowserTest {
private static final String LIT_VIEW_SOURCE = "src/main/frontend/views/lit-view.ts";
+ private static final String USAGE_STATISTICS_SOURCE = "src/main/frontend/vaadin-usage-statistics.js";
@Override
protected String getTestPath() {
return "/view/com.vaadin.flow.frontend.LitView";
}
- @Test
- public void devBundleSourceMapsPointToOriginalSources() throws IOException {
+ @Before
+ public void init() {
// The dev bundle is built when the application is first opened
open();
waitForElementPresent(By.tagName("lit-view"));
+ }
- boolean litViewFound = false;
+ @Test
+ public void devBundleSourceMaps_pointToOriginalSources()
+ throws IOException {
+ List sources = new ArrayList<>();
List sourceMaps = getDevBundleSourceMaps();
for (File sourceMap : sourceMaps) {
- litViewFound |= assertSourceMapUsable(sourceMap);
+ sources.addAll(SourceMapTestUtil.assertSourceMapUsable(
+ sourceMap.getName(), read(sourceMap)));
}
- Assert.assertTrue(
- "A sourcemap of the dev bundle should refer to "
- + LIT_VIEW_SOURCE + ", only found " + sourceMaps,
- litViewFound);
+ assertHasSource(sources, LIT_VIEW_SOURCE);
+ assertHasSource(sources, USAGE_STATISTICS_SOURCE);
}
/**
- * Asserts that the given sourcemap can be used to map the chunk back to the
- * files it was built from, and tells whether the view source is one of
- * them.
+ * The plugin that keeps the usage statistics comment rewrites the module it
+ * is in, which is the one case where the plugin has to produce a sourcemap
+ * of its own instead of leaving the module alone.
*/
- private boolean assertSourceMapUsable(File sourceMap) throws IOException {
- String name = sourceMap.getName();
- JsonNode contents = JsonMapper.shared().readTree(
- FileUtils.readFileToString(sourceMap, StandardCharsets.UTF_8));
- JsonNode sources = contents.get("sources");
- JsonNode sourcesContent = contents.get("sourcesContent");
-
- Assert.assertNotEquals(name + " should have a sourcemap with sources",
- 0, sources.size());
- Assert.assertNotEquals(name + " should have a sourcemap with mappings",
- "", contents.get("mappings").asString());
- Assert.assertEquals(
- name + " should have the contents of every source in its "
- + "sourcemap",
- sources.size(), sourcesContent.size());
-
- boolean litViewFound = false;
- for (int i = 0; i < sources.size(); i++) {
- String source = sources.get(i).asString();
- Assert.assertNotEquals(
- name + " should have a sourcemap referring to the "
- + "original files, was " + source,
- "", source.trim());
- Assert.assertNotEquals(
- name + " should have the contents of " + source
- + " in its sourcemap",
- "", sourcesContent.get(i).asString().trim());
- litViewFound |= source.replace('\\', '/').endsWith(LIT_VIEW_SOURCE);
- }
- return litViewFound;
+ @Test
+ public void usageStatisticsComment_isRewrittenInTheBundle()
+ throws IOException {
+ File chunk = getDevBundleChunkOf("vaadin-dev-mode:start");
+ String contents = read(chunk);
+
+ Assert.assertTrue(
+ chunk.getName() + " should have the usage statistics comment "
+ + "rewritten so that it is kept in the bundle",
+ contents.contains("/*! vaadin-dev-mode:start"));
+ Assert.assertFalse(
+ chunk.getName() + " should no longer have the original "
+ + "usage statistics comment",
+ contents.contains("/** vaadin-dev-mode:start"));
+ }
+
+ private void assertHasSource(List sources, String source) {
+ Assert.assertTrue(
+ "A sourcemap of the dev bundle should refer to " + source
+ + ", only found " + sources,
+ sources.stream().anyMatch(name -> name.endsWith(source)));
}
private List getDevBundleSourceMaps() {
+ List sourceMaps = List.of(getDevBundleBuildFolder()
+ .listFiles((dir, name) -> name.endsWith(".js.map")));
+ Assert.assertFalse(
+ "The dev bundle should have been built with sourcemaps "
+ + "enabled, see vite.config.ts",
+ sourceMaps.isEmpty());
+ return sourceMaps;
+ }
+
+ private File getDevBundleChunkOf(String marker) throws IOException {
+ List chunks = List.of(getDevBundleBuildFolder()
+ .listFiles((dir, name) -> name.endsWith(".js")));
+ for (File chunk : chunks) {
+ if (read(chunk).contains(marker)) {
+ return chunk;
+ }
+ }
+ throw new AssertionError("No chunk of the dev bundle contains '"
+ + marker + "', looked at " + chunks);
+ }
+
+ private File getDevBundleBuildFolder() {
File baseDir = new File(System.getProperty("user.dir", "."));
File buildFolder = new File(baseDir, "target/"
+ Constants.DEV_BUNDLE_LOCATION + "/webapp/VAADIN/build");
Assert.assertTrue("The dev bundle should have been built into "
+ buildFolder.getPath(), buildFolder.isDirectory());
+ return buildFolder;
+ }
- List sourceMaps = new ArrayList<>(List.of(buildFolder
- .listFiles((dir, name) -> name.endsWith(".js.map"))));
- Assert.assertFalse(
- "The dev bundle should have been built with sourcemaps "
- + "enabled, see vite.config.ts",
- sourceMaps.isEmpty());
- return sourceMaps;
+ private String read(File file) throws IOException {
+ return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
}
}
diff --git a/flow-tests/test-frontend/vite-production/src/test/java/com/vaadin/viteapp/SourceMapsIT.java b/flow-tests/test-frontend/vite-production/src/test/java/com/vaadin/viteapp/SourceMapsIT.java
index da482cfdedf..ca20d56a3fd 100644
--- a/flow-tests/test-frontend/vite-production/src/test/java/com/vaadin/viteapp/SourceMapsIT.java
+++ b/flow-tests/test-frontend/vite-production/src/test/java/com/vaadin/viteapp/SourceMapsIT.java
@@ -24,8 +24,8 @@
import org.junit.Assert;
import org.junit.Test;
-import tools.jackson.databind.JsonNode;
-import tools.jackson.databind.json.JsonMapper;
+
+import com.vaadin.flow.testutil.SourceMapTestUtil;
public class SourceMapsIT extends BundleAccess {
@@ -55,8 +55,8 @@ public void bundleSourceMapsPointToOriginalSources() throws Exception {
// Bundler runtime helpers are emitted without a sourcemap
continue;
}
- assertSourceMapUsable(bundle, JsonMapper.shared()
- .readTree(download(BUILD_PATH + matcher.group(1))));
+ SourceMapTestUtil.assertSourceMapUsable(bundle,
+ download(BUILD_PATH + matcher.group(1)));
checkedBundles++;
}
@@ -64,30 +64,6 @@ public void bundleSourceMapsPointToOriginalSources() throws Exception {
checkedBundles);
}
- private void assertSourceMapUsable(String bundle, JsonNode sourceMap) {
- JsonNode sources = sourceMap.get("sources");
- JsonNode sourcesContent = sourceMap.get("sourcesContent");
- Assert.assertNotEquals(bundle + " should have a sourcemap with sources",
- 0, sources.size());
- Assert.assertNotEquals(
- bundle + " should have a sourcemap with mappings", "",
- sourceMap.get("mappings").asString());
- Assert.assertEquals(
- bundle + " should have the contents of every source in its "
- + "sourcemap",
- sources.size(), sourcesContent.size());
- for (int i = 0; i < sources.size(); i++) {
- Assert.assertNotEquals(
- bundle + " should have a sourcemap referring to the "
- + "original files, was " + sources.get(i),
- "", sources.get(i).asString().trim());
- Assert.assertNotEquals(
- bundle + " should have the contents of "
- + sources.get(i).asString() + " in its sourcemap",
- "", sourcesContent.get(i).asString().trim());
- }
- }
-
/**
* Collects the given bundle and the bundles it imports, directly or through
* another bundle. Names that are not served are left out, as not every file