From 19971365335208a21e6348183e72dce12ea5cf8c 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:05:06 +0000 Subject: [PATCH 1/7] fix: keep dev bundle sourcemaps usable in the usage statistics plugin The plugin that preserves the usage statistics comment returned the code of every module without a sourcemap, so the bundler left all of them out of the maps of the chunks they ended up in. A dev bundle built with build.sourcemap enabled got .map files with no sources at all, and the browser could not show the original frontend files. The plugin now returns null for the modules it does not change, and a real map for the one module it does. Fixes #16679, Follow-up to #25691 --- .../src/main/resources/vite.generated.ts | 36 +++--- .../flow/frontend/DevBundleSourceMapsIT.java | 115 ++++++++++++++++++ .../vite.config.ts | 7 +- 3 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleSourceMapsIT.java diff --git a/flow-server/src/main/resources/vite.generated.ts b/flow-server/src/main/resources/vite.generated.ts index ad4a30d4adf..d51a2f8257c 100644 --- a/flow-server/src/main/resources/vite.generated.ts +++ b/flow-server/src/main/resources/vite.generated.ts @@ -24,6 +24,7 @@ import { } from 'vite'; import brotli from 'rollup-plugin-brotli'; +import MagicString from 'magic-string'; import checker from 'vite-plugin-checker'; import postcssLit from '#buildFolder#/plugins/rollup-plugin-postcss-lit-custom/rollup-plugin-postcss-lit.js'; import vaadinI18n from '#buildFolder#/plugins/rollup-plugin-vaadin-i18n/rollup-plugin-vaadin-i18n.js'; @@ -465,24 +466,31 @@ function preserveUsageStats() { return { name: 'vaadin:preserve-usage-stats', + // A hook that returns code must return a sourcemap for it as well, or + // return null to leave the module as it is. Code without a map makes the + // bundler drop the module from the sourcemap of the chunk it ends up in, + // and this hook sees every module of the bundle. transform(src: string, id: string) { - if (id.includes('vaadin-usage-statistics')) { - if (src.includes('vaadin-dev-mode:start')) { - const expectedComment = '/*! vaadin-dev-mode:start'; - const newSrc = src.replace(DEV_MODE_START_REGEXP, expectedComment); - if (newSrc === src) { - if (!src.includes(expectedComment)) { - console.error('vaadin-dev-mode:start tag not found'); - } - } else if (!newSrc.match(DEV_MODE_CODE_REGEXP)) { - console.error('New comment fails to match original regexp'); - } else { - return { code: newSrc }; - } + if (!id.includes('vaadin-usage-statistics') || !src.includes('vaadin-dev-mode:start')) { + return null; + } + + const expectedComment = '/*! vaadin-dev-mode:start'; + const magicString = new MagicString(src).replace(DEV_MODE_START_REGEXP, expectedComment); + if (!magicString.hasChanged()) { + if (!src.includes(expectedComment)) { + console.error('vaadin-dev-mode:start tag not found'); } + return null; + } + + const code = magicString.toString(); + if (!code.match(DEV_MODE_CODE_REGEXP)) { + console.error('New comment fails to match original regexp'); + return null; } - return { code: src }; + return { code, map: magicString.generateMap({ hires: true }) }; } }; } 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 new file mode 100644 index 00000000000..9275018a76b --- /dev/null +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/test/java/com/vaadin/flow/frontend/DevBundleSourceMapsIT.java @@ -0,0 +1,115 @@ +/* + * 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.frontend; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.json.JsonMapper; + +import com.vaadin.flow.server.Constants; +import com.vaadin.flow.testutil.ChromeBrowserTest; + +/** + * Verifies that the dev bundle can be mapped back to the original sources. This + * module builds it with {@code build.sourcemap} enabled, see + * {@code vite.config.ts}. + */ +public class DevBundleSourceMapsIT extends ChromeBrowserTest { + + @Before + public void init() { + // Opening a view makes the application build the dev bundle + open(); + $("lit-view").waitForFirst(); + } + + @Override + protected String getTestPath() { + return "/view/com.vaadin.flow.frontend.LitView"; + } + + /** + * A build plugin that returns the code of a module without a sourcemap + * makes the bundler leave that module out of the sourcemap of the chunk it + * ends up in. A plugin that sees every module then leaves the emitted .map + * files without any of the original sources. + */ + @Test + public void devBundleSourceMapsPointToOriginalSources() throws IOException { + File buildFolder = new File( + new File(System.getProperty("user.dir", "."), + "target/" + Constants.DEV_BUNDLE_LOCATION), + "webapp/VAADIN/build"); + Assert.assertTrue("Dev bundle should have been built", + buildFolder.isDirectory()); + + List sourceMaps = List.of( + buildFolder.listFiles((dir, name) -> name.endsWith(".map"))); + Assert.assertNotEquals("Dev bundle should contain sourcemaps", 0, + sourceMaps.size()); + + boolean litViewFound = false; + for (File sourceMap : sourceMaps) { + litViewFound |= assertSourceMapUsable(sourceMap); + } + Assert.assertTrue( + "The sourcemaps should refer to views/lit-view.ts, so that the " + + "browser can show the original source of the view", + litViewFound); + } + + /** + * Asserts that the given sourcemap has the original sources with their + * contents, and tells whether one of them is the Lit view of this + * application. + */ + private boolean assertSourceMapUsable(File sourceMap) throws IOException { + String name = sourceMap.getName(); + JsonNode json = JsonMapper.shared().readTree( + FileUtils.readFileToString(sourceMap, StandardCharsets.UTF_8)); + JsonNode sources = json.get("sources"); + JsonNode sourcesContent = json.get("sourcesContent"); + + Assert.assertNotEquals(name + " should have sources", 0, + sources.size()); + Assert.assertNotEquals(name + " should have mappings", "", + json.get("mappings").asString()); + Assert.assertEquals(name + " should have the contents of every source", + 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 refer to the original files, was " + source, + "", source.trim()); + Assert.assertNotEquals( + name + " should have the contents of " + source, "", + sourcesContent.get(i).asString().trim()); + litViewFound |= source.endsWith("views/lit-view.ts"); + } + return litViewFound; + } + +} 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 14b03a1b0b8c9f89dda473a2a11ac426d5b08b97 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:13:50 +0000 Subject: [PATCH 2/7] test: cover the dev mode comment rewriting of the usage statistics plugin The dev bundle of the test application had no module the plugin rewrites, so only its early return was covered and a broken rewrite would have gone unnoticed. The published @vaadin/vaadin-usage-statistics already carries the rewritten comment, so the application now has a frontend module that stands in for it with the plain comment form the plugin has to handle. --- .../frontend/vaadin-usage-statistics-stub.ts | 10 +++ .../flow/frontend/DevBundleJsModuleView.java | 3 + .../flow/frontend/DevBundleSourceMapsIT.java | 73 ++++++++++++++++--- 3 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts new file mode 100644 index 00000000000..6d004642894 --- /dev/null +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts @@ -0,0 +1,10 @@ +/** vaadin-dev-mode:start +console.log('Usage statistics stub'); +vaadin-dev-mode:end **/ + +// Stands in for @vaadin/vaadin-usage-statistics, so that the build sees a +// module whose name and dev mode comment the vaadin:preserve-usage-stats +// plugin rewrites. The published package already has the rewritten comment, +// this file has the plain form that the plugin has to turn into /*! for a +// minifier to keep it. See DevBundleSourceMapsIT. +document.documentElement.setAttribute('usage-statistics-stub', 'true'); diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java index 5bf17a0d96c..8d240251c3a 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java @@ -23,6 +23,9 @@ @Route("com.vaadin.flow.frontend.DevBundleJsModuleView") @JsModule("./test.ts") @JsModule("./js/test.js") +// Pulls a module named like @vaadin/vaadin-usage-statistics into the bundle, +// so that the build plugin that rewrites its dev mode comment runs +@JsModule("./vaadin-usage-statistics-stub.ts") public class DevBundleJsModuleView extends Div { static final String SPAN_ID = "test-js-module-frontend-span"; 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 9275018a76b..57386862ad5 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 @@ -57,15 +57,7 @@ protected String getTestPath() { */ @Test public void devBundleSourceMapsPointToOriginalSources() throws IOException { - File buildFolder = new File( - new File(System.getProperty("user.dir", "."), - "target/" + Constants.DEV_BUNDLE_LOCATION), - "webapp/VAADIN/build"); - Assert.assertTrue("Dev bundle should have been built", - buildFolder.isDirectory()); - - List sourceMaps = List.of( - buildFolder.listFiles((dir, name) -> name.endsWith(".map"))); + List sourceMaps = getBundleFiles(".map"); Assert.assertNotEquals("Dev bundle should contain sourcemaps", 0, sourceMaps.size()); @@ -79,6 +71,46 @@ public void devBundleSourceMapsPointToOriginalSources() throws IOException { litViewFound); } + /** + * The plugin that rewrites the dev mode comment of the usage statistics + * module is the one that has to return a sourcemap of its own. The + * rewritten comment starts with {@code /*!}, which tells a minifier to keep + * it, and the rewritten module has to stay in the sourcemap of its chunk. + */ + @Test + public void usageStatisticsCommentIsRewrittenAndStaysInSourceMap() + throws IOException { + File chunk = null; + for (File bundle : getBundleFiles(".js")) { + String contents = FileUtils.readFileToString(bundle, + StandardCharsets.UTF_8); + Assert.assertFalse( + bundle.getName() + " should have the dev mode comment " + + "rewritten into the form a minifier keeps", + contents.contains("/** vaadin-dev-mode:start")); + if (contents.contains("/*! vaadin-dev-mode:start")) { + chunk = bundle; + } + } + Assert.assertNotNull( + "The dev bundle should contain the dev mode comment of the " + + "usage statistics module", + chunk); + + File sourceMap = new File(chunk.getParentFile(), + chunk.getName() + ".map"); + Assert.assertTrue(chunk.getName() + " should have a sourcemap", + sourceMap.isFile()); + + boolean stubFound = false; + for (JsonNode source : readSourceMap(sourceMap).get("sources")) { + stubFound |= source.asString() + .endsWith("vaadin-usage-statistics-stub.ts"); + } + Assert.assertTrue("The rewritten module should be in the sourcemap of " + + chunk.getName(), stubFound); + } + /** * Asserts that the given sourcemap has the original sources with their * contents, and tells whether one of them is the Lit view of this @@ -86,8 +118,7 @@ public void devBundleSourceMapsPointToOriginalSources() throws IOException { */ private boolean assertSourceMapUsable(File sourceMap) throws IOException { String name = sourceMap.getName(); - JsonNode json = JsonMapper.shared().readTree( - FileUtils.readFileToString(sourceMap, StandardCharsets.UTF_8)); + JsonNode json = readSourceMap(sourceMap); JsonNode sources = json.get("sources"); JsonNode sourcesContent = json.get("sourcesContent"); @@ -112,4 +143,24 @@ private boolean assertSourceMapUsable(File sourceMap) throws IOException { return litViewFound; } + private JsonNode readSourceMap(File sourceMap) throws IOException { + return JsonMapper.shared().readTree( + FileUtils.readFileToString(sourceMap, StandardCharsets.UTF_8)); + } + + /** + * Returns the files of the built dev bundle whose name ends with the given + * suffix. + */ + private List getBundleFiles(String suffix) { + File buildFolder = new File( + new File(System.getProperty("user.dir", "."), + "target/" + Constants.DEV_BUNDLE_LOCATION), + "webapp/VAADIN/build"); + Assert.assertTrue("Dev bundle should have been built", + buildFolder.isDirectory()); + return List.of( + buildFolder.listFiles((dir, name) -> name.endsWith(suffix))); + } + } From d16c49352d53fa104b99125138bcd8218fdb2c5c Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 06:25:51 +0000 Subject: [PATCH 3/7] test: share the sourcemap assertions between the bundle tests The dev bundle and the production bundle tests checked the emitted sourcemaps with the same assertions, written twice. They now use one helper in flow-test-util, which returns the sources of the map so that a test can also assert which files it refers to. --- .../flow/testutil/SourceMapTestUtil.java | 78 +++++++++++ .../flow/frontend/DevBundleSourceMapsIT.java | 127 +++++++----------- .../vite-production/package.json | 68 +++++----- .../java/com/vaadin/viteapp/SourceMapsIT.java | 32 +---- 4 files changed, 168 insertions(+), 137 deletions(-) create mode 100644 flow-test-util/src/main/java/com/vaadin/flow/testutil/SourceMapTestUtil.java 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/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 57386862ad5..7c11654b827 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 @@ -18,17 +18,17 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -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; /** * Verifies that the dev bundle can be mapped back to the original sources. This @@ -37,6 +37,9 @@ */ 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-stub.ts"; + @Before public void init() { // Opening a view makes the application build the dev bundle @@ -57,95 +60,63 @@ protected String getTestPath() { */ @Test public void devBundleSourceMapsPointToOriginalSources() throws IOException { - List sourceMaps = getBundleFiles(".map"); - Assert.assertNotEquals("Dev bundle should contain sourcemaps", 0, - sourceMaps.size()); + List sourceMaps = getBundleFiles(".js.map"); + Assert.assertFalse( + "The dev bundle should have been built with sourcemaps " + + "enabled, see vite.config.ts", + sourceMaps.isEmpty()); - boolean litViewFound = false; + List sources = new ArrayList<>(); for (File sourceMap : sourceMaps) { - litViewFound |= assertSourceMapUsable(sourceMap); + sources.addAll(SourceMapTestUtil.assertSourceMapUsable( + sourceMap.getName(), read(sourceMap))); } - Assert.assertTrue( - "The sourcemaps should refer to views/lit-view.ts, so that the " - + "browser can show the original source of the view", - litViewFound); + + assertHasSource(sources, LIT_VIEW_SOURCE); + assertHasSource(sources, USAGE_STATISTICS_SOURCE); } /** * The plugin that rewrites the dev mode comment of the usage statistics * module is the one that has to return a sourcemap of its own. The * rewritten comment starts with {@code /*!}, which tells a minifier to keep - * it, and the rewritten module has to stay in the sourcemap of its chunk. + * it. */ @Test - public void usageStatisticsCommentIsRewrittenAndStaysInSourceMap() + public void usageStatisticsCommentIsRewrittenInTheBundle() throws IOException { - File chunk = null; - for (File bundle : getBundleFiles(".js")) { - String contents = FileUtils.readFileToString(bundle, - StandardCharsets.UTF_8); - Assert.assertFalse( - bundle.getName() + " should have the dev mode comment " - + "rewritten into the form a minifier keeps", - contents.contains("/** vaadin-dev-mode:start")); - if (contents.contains("/*! vaadin-dev-mode:start")) { - chunk = bundle; - } - } - Assert.assertNotNull( - "The dev bundle should contain the dev mode comment of the " - + "usage statistics module", - chunk); - - File sourceMap = new File(chunk.getParentFile(), - chunk.getName() + ".map"); - Assert.assertTrue(chunk.getName() + " should have a sourcemap", - sourceMap.isFile()); - - boolean stubFound = false; - for (JsonNode source : readSourceMap(sourceMap).get("sources")) { - stubFound |= source.asString() - .endsWith("vaadin-usage-statistics-stub.ts"); - } - Assert.assertTrue("The rewritten module should be in the sourcemap of " - + chunk.getName(), stubFound); + File chunk = getBundleChunkOf("vaadin-dev-mode:start"); + String contents = read(chunk); + + Assert.assertTrue( + chunk.getName() + " should have the usage statistics comment " + + "rewritten so that a minifier keeps it", + 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))); } /** - * Asserts that the given sourcemap has the original sources with their - * contents, and tells whether one of them is the Lit view of this - * application. + * Returns the chunk of the dev bundle that contains the given marker. */ - private boolean assertSourceMapUsable(File sourceMap) throws IOException { - String name = sourceMap.getName(); - JsonNode json = readSourceMap(sourceMap); - JsonNode sources = json.get("sources"); - JsonNode sourcesContent = json.get("sourcesContent"); - - Assert.assertNotEquals(name + " should have sources", 0, - sources.size()); - Assert.assertNotEquals(name + " should have mappings", "", - json.get("mappings").asString()); - Assert.assertEquals(name + " should have the contents of every source", - 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 refer to the original files, was " + source, - "", source.trim()); - Assert.assertNotEquals( - name + " should have the contents of " + source, "", - sourcesContent.get(i).asString().trim()); - litViewFound |= source.endsWith("views/lit-view.ts"); + private File getBundleChunkOf(String marker) throws IOException { + List chunks = getBundleFiles(".js"); + for (File chunk : chunks) { + if (read(chunk).contains(marker)) { + return chunk; + } } - return litViewFound; - } - - private JsonNode readSourceMap(File sourceMap) throws IOException { - return JsonMapper.shared().readTree( - FileUtils.readFileToString(sourceMap, StandardCharsets.UTF_8)); + throw new AssertionError("No chunk of the dev bundle contains '" + + marker + "', looked at " + chunks); } /** @@ -157,10 +128,14 @@ private List getBundleFiles(String suffix) { new File(System.getProperty("user.dir", "."), "target/" + Constants.DEV_BUNDLE_LOCATION), "webapp/VAADIN/build"); - Assert.assertTrue("Dev bundle should have been built", - buildFolder.isDirectory()); + Assert.assertTrue("The dev bundle should have been built into " + + buildFolder.getPath(), buildFolder.isDirectory()); return List.of( buildFolder.listFiles((dir, name) -> name.endsWith(suffix))); } + private String read(File file) throws IOException { + return FileUtils.readFileToString(file, StandardCharsets.UTF_8); + } + } diff --git a/flow-tests/test-frontend/vite-production/package.json b/flow-tests/test-frontend/vite-production/package.json index 04ed1503324..88415b7978a 100644 --- a/flow-tests/test-frontend/vite-production/package.json +++ b/flow-tests/test-frontend/vite-production/package.json @@ -6,7 +6,7 @@ "dependencies": { "@polymer/polymer": "3.5.2", "@vaadin/bundles": "../vite-test-assets/packages/@vaadin/bundles", - "@vaadin/common-frontend": "0.0.23", + "@vaadin/common-frontend": "0.0.24", "@vaadin/hilla-react-i18n": "../vite-test-assets/packages/@vaadin/hilla-react-i18n", "@vaadin/test-package-outside-npm": "file:../vite-test-assets/packages/@vaadin/test-package-outside-npm", "@vaadin/test-package2-outside-npm": "../vite-test-assets/packages/@vaadin/test-package2-outside-npm", @@ -15,64 +15,66 @@ "@vaadin/testscope-map": "../vite-test-assets/packages/@vaadin/testscope-map", "@vaadin/vaadin-themable-mixin": "25.2.9", "lit": "3.3.3", - "react": "19.2.7", - "react-dom": "19.2.7", - "react-router": "8.3.0" + "react": "19.3.0", + "react-dom": "19.3.0", + "react-router": "8.3.1" }, "devDependencies": { "@babel/core": "7.29.7", "@babel/plugin-transform-react-jsx-development": "7.29.7", "@babel/preset-react": "7.29.7", - "@babel/types": "7.29.7", - "@preact/signals-react-transform": "0.8.1", - "@rolldown/plugin-babel": "0.2.3", + "@babel/types": "7.29.8", + "@preact/signals-react": "3.12.0", + "@preact/signals-react-transform": "0.8.2", + "@rolldown/plugin-babel": "0.2.4", "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.4.0", - "@types/node": "26.1.0", - "@types/react": "19.2.17", - "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "6.0.3", - "magic-string": "0.30.21", + "@types/node": "26.5.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "6.1.1", + "magic-string": "1.3.1", "rollup-plugin-brotli": "3.1.0", - "rollup-plugin-visualizer": "7.0.1", + "rollup-plugin-visualizer": "7.1.1", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "6.0.3", - "vite": "8.0.16", - "vite-plugin-checker": "0.14.4" + "typescript": "7.0.2", + "vite": "8.3.0", + "vite-plugin-checker": "0.14.5" }, "vaadin": { "dependencies": { "@polymer/polymer": "3.5.2", - "@vaadin/common-frontend": "0.0.23", + "@vaadin/common-frontend": "0.0.24", "@vaadin/vaadin-themable-mixin": "25.2.9", "lit": "3.3.3", - "react": "19.2.7", - "react-dom": "19.2.7", - "react-router": "8.3.0" + "react": "19.3.0", + "react-dom": "19.3.0", + "react-router": "8.3.1" }, "devDependencies": { "@babel/core": "7.29.7", "@babel/plugin-transform-react-jsx-development": "7.29.7", "@babel/preset-react": "7.29.7", - "@babel/types": "7.29.7", - "@preact/signals-react-transform": "0.8.1", - "@rolldown/plugin-babel": "0.2.3", + "@babel/types": "7.29.8", + "@preact/signals-react": "3.12.0", + "@preact/signals-react-transform": "0.8.2", + "@rolldown/plugin-babel": "0.2.4", "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.4.0", - "@types/node": "26.1.0", - "@types/react": "19.2.17", - "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "6.0.3", - "magic-string": "0.30.21", + "@types/node": "26.5.1", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@vitejs/plugin-react": "6.1.1", + "magic-string": "1.3.1", "rollup-plugin-brotli": "3.1.0", - "rollup-plugin-visualizer": "7.0.1", + "rollup-plugin-visualizer": "7.1.1", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "6.0.3", - "vite": "8.0.16", - "vite-plugin-checker": "0.14.4" + "typescript": "7.0.2", + "vite": "8.3.0", + "vite-plugin-checker": "0.14.5" }, - "hash": "d30e148b8cac2720a95e16dabfb6b35c72b018ac0886fa86c3a71c7a60d869eb" + "hash": "73752634dad36a7ff18e3c7c19b419776671609e3908f5b59ddc6f3be30dc5bc" } } \ No newline at end of file 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 From 60d1a225bb39c36717037fdfa105b6066296cd1d Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 06:26:10 +0000 Subject: [PATCH 4/7] chore: restore the generated package.json of the production test app The frontend build regenerates the checked-in file with the dependency versions of the moment, which has nothing to do with this branch. --- .../vite-production/package.json | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/flow-tests/test-frontend/vite-production/package.json b/flow-tests/test-frontend/vite-production/package.json index 88415b7978a..04ed1503324 100644 --- a/flow-tests/test-frontend/vite-production/package.json +++ b/flow-tests/test-frontend/vite-production/package.json @@ -6,7 +6,7 @@ "dependencies": { "@polymer/polymer": "3.5.2", "@vaadin/bundles": "../vite-test-assets/packages/@vaadin/bundles", - "@vaadin/common-frontend": "0.0.24", + "@vaadin/common-frontend": "0.0.23", "@vaadin/hilla-react-i18n": "../vite-test-assets/packages/@vaadin/hilla-react-i18n", "@vaadin/test-package-outside-npm": "file:../vite-test-assets/packages/@vaadin/test-package-outside-npm", "@vaadin/test-package2-outside-npm": "../vite-test-assets/packages/@vaadin/test-package2-outside-npm", @@ -15,66 +15,64 @@ "@vaadin/testscope-map": "../vite-test-assets/packages/@vaadin/testscope-map", "@vaadin/vaadin-themable-mixin": "25.2.9", "lit": "3.3.3", - "react": "19.3.0", - "react-dom": "19.3.0", - "react-router": "8.3.1" + "react": "19.2.7", + "react-dom": "19.2.7", + "react-router": "8.3.0" }, "devDependencies": { "@babel/core": "7.29.7", "@babel/plugin-transform-react-jsx-development": "7.29.7", "@babel/preset-react": "7.29.7", - "@babel/types": "7.29.8", - "@preact/signals-react": "3.12.0", - "@preact/signals-react-transform": "0.8.2", - "@rolldown/plugin-babel": "0.2.4", + "@babel/types": "7.29.7", + "@preact/signals-react-transform": "0.8.1", + "@rolldown/plugin-babel": "0.2.3", "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.4.0", - "@types/node": "26.5.1", - "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", - "@vitejs/plugin-react": "6.1.1", - "magic-string": "1.3.1", + "@types/node": "26.1.0", + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "6.0.3", + "magic-string": "0.30.21", "rollup-plugin-brotli": "3.1.0", - "rollup-plugin-visualizer": "7.1.1", + "rollup-plugin-visualizer": "7.0.1", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "7.0.2", - "vite": "8.3.0", - "vite-plugin-checker": "0.14.5" + "typescript": "6.0.3", + "vite": "8.0.16", + "vite-plugin-checker": "0.14.4" }, "vaadin": { "dependencies": { "@polymer/polymer": "3.5.2", - "@vaadin/common-frontend": "0.0.24", + "@vaadin/common-frontend": "0.0.23", "@vaadin/vaadin-themable-mixin": "25.2.9", "lit": "3.3.3", - "react": "19.3.0", - "react-dom": "19.3.0", - "react-router": "8.3.1" + "react": "19.2.7", + "react-dom": "19.2.7", + "react-router": "8.3.0" }, "devDependencies": { "@babel/core": "7.29.7", "@babel/plugin-transform-react-jsx-development": "7.29.7", "@babel/preset-react": "7.29.7", - "@babel/types": "7.29.8", - "@preact/signals-react": "3.12.0", - "@preact/signals-react-transform": "0.8.2", - "@rolldown/plugin-babel": "0.2.4", + "@babel/types": "7.29.7", + "@preact/signals-react-transform": "0.8.1", + "@rolldown/plugin-babel": "0.2.3", "@rollup/plugin-replace": "6.0.3", "@rollup/pluginutils": "5.4.0", - "@types/node": "26.5.1", - "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", - "@vitejs/plugin-react": "6.1.1", - "magic-string": "1.3.1", + "@types/node": "26.1.0", + "@types/react": "19.2.17", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "6.0.3", + "magic-string": "0.30.21", "rollup-plugin-brotli": "3.1.0", - "rollup-plugin-visualizer": "7.1.1", + "rollup-plugin-visualizer": "7.0.1", "strip-css-comments": "5.0.0", "transform-ast": "2.4.4", - "typescript": "7.0.2", - "vite": "8.3.0", - "vite-plugin-checker": "0.14.5" + "typescript": "6.0.3", + "vite": "8.0.16", + "vite-plugin-checker": "0.14.4" }, - "hash": "73752634dad36a7ff18e3c7c19b419776671609e3908f5b59ddc6f3be30dc5bc" + "hash": "d30e148b8cac2720a95e16dabfb6b35c72b018ac0886fa86c3a71c7a60d869eb" } } \ No newline at end of file From b483835b4a44802761e53168b430fba1cd6a52fe Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 06:29:37 +0000 Subject: [PATCH 5/7] test: check every chunk for the original usage statistics comment The test looked at the first chunk that has the comment, which is the order the file system happens to return. A chunk the plugin misses while it rewrites another one was therefore found only by chance. --- .../flow/frontend/DevBundleSourceMapsIT.java | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) 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 7c11654b827..cd5585f0380 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 @@ -85,17 +85,19 @@ public void devBundleSourceMapsPointToOriginalSources() throws IOException { @Test public void usageStatisticsCommentIsRewrittenInTheBundle() throws IOException { - File chunk = getBundleChunkOf("vaadin-dev-mode:start"); - String contents = read(chunk); - + boolean rewritten = false; + for (File chunk : getBundleFiles(".js")) { + String contents = read(chunk); + Assert.assertFalse( + chunk.getName() + " should no longer have the original " + + "usage statistics comment", + contents.contains("/** vaadin-dev-mode:start")); + rewritten |= contents.contains("/*! vaadin-dev-mode:start"); + } Assert.assertTrue( - chunk.getName() + " should have the usage statistics comment " - + "rewritten so that a minifier keeps it", - 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")); + "A chunk of the dev bundle should have the usage statistics " + + "comment rewritten so that a minifier keeps it", + rewritten); } private void assertHasSource(List sources, String source) { @@ -105,20 +107,6 @@ private void assertHasSource(List sources, String source) { sources.stream().anyMatch(name -> name.endsWith(source))); } - /** - * Returns the chunk of the dev bundle that contains the given marker. - */ - private File getBundleChunkOf(String marker) throws IOException { - List chunks = getBundleFiles(".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); - } - /** * Returns the files of the built dev bundle whose name ends with the given * suffix. From 9aafe15cbd33692b089c4a1a12db0ca6ee7ee4a1 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 08:34:12 +0000 Subject: [PATCH 6/7] test: run the code from the usage statistics dev mode comment The code inside the comment is the code that is meant to run: the development mode detector reads it out of the source of the function it is in and evaluates it. The test only looked for the comment in the bundle, so it did not cover that. The stand-in module now goes through the detector like the published package does, and the code in its comment marks the document, which the test asserts after loading a view. The check for the rewritten comment is now matched per comment, because the detector brings a dev mode comment of its own into the bundle that nothing rewrites. --- .../frontend/vaadin-usage-statistics-stub.js | 21 +++++++ .../frontend/vaadin-usage-statistics-stub.ts | 10 --- .../flow/frontend/DevBundleJsModuleView.java | 4 +- .../flow/frontend/DevBundleSourceMapsIT.java | 61 ++++++++++++++----- 4 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js delete mode 100644 flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js new file mode 100644 index 00000000000..dc05204b2f1 --- /dev/null +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js @@ -0,0 +1,21 @@ +import { runIfDevelopmentMode } from '@vaadin/vaadin-development-mode-detector/vaadin-development-mode-detector.js'; + +// Stands in for @vaadin/vaadin-usage-statistics, which a Flow only +// application does not pull in. The published package has the comment below +// in the rewritten form already, this file has the plain form that the +// vaadin:preserve-usage-stats build plugin has to turn into /*! for a +// minifier to keep it. See DevBundleSourceMapsIT. +function maybeGatherAndSendStats() { + /** vaadin-dev-mode:start + document.documentElement.setAttribute('usage-statistics-stub-ran', 'true'); + vaadin-dev-mode:end **/ +} + +// The code inside the comment above is the code that is meant to run: the +// development mode detector reads it out of the source of this function and +// runs it, which only works as long as the build keeps the comment in the +// bundle. Development mode is forced on so that the test covers the build and +// not the heuristics the detector uses to recognize development mode. +window.Vaadin = window.Vaadin || {}; +window.Vaadin.developmentMode = true; +runIfDevelopmentMode(maybeGatherAndSendStats); diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts deleted file mode 100644 index 6d004642894..00000000000 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** vaadin-dev-mode:start -console.log('Usage statistics stub'); -vaadin-dev-mode:end **/ - -// Stands in for @vaadin/vaadin-usage-statistics, so that the build sees a -// module whose name and dev mode comment the vaadin:preserve-usage-stats -// plugin rewrites. The published package already has the rewritten comment, -// this file has the plain form that the plugin has to turn into /*! for a -// minifier to keep it. See DevBundleSourceMapsIT. -document.documentElement.setAttribute('usage-statistics-stub', 'true'); diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java index 8d240251c3a..2ba7326916c 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java @@ -16,6 +16,7 @@ package com.vaadin.flow.frontend; import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.dependency.NpmPackage; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.router.Route; @@ -25,7 +26,8 @@ @JsModule("./js/test.js") // Pulls a module named like @vaadin/vaadin-usage-statistics into the bundle, // so that the build plugin that rewrites its dev mode comment runs -@JsModule("./vaadin-usage-statistics-stub.ts") +@NpmPackage(value = "@vaadin/vaadin-development-mode-detector", version = "2.0.7") +@JsModule("./vaadin-usage-statistics-stub.js") public class DevBundleJsModuleView extends Div { static final String SPAN_ID = "test-js-module-frontend-span"; 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 cd5585f0380..13fe782c3fd 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 @@ -20,11 +20,14 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.openqa.selenium.By; import com.vaadin.flow.server.Constants; import com.vaadin.flow.testutil.ChromeBrowserTest; @@ -38,7 +41,15 @@ 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-stub.ts"; + private static final String USAGE_STATISTICS_SOURCE = "src/main/frontend/vaadin-usage-statistics-stub.js"; + + /** + * The comment the development mode detector reads the code to run out of, + * with the form of its opening marker and its contents as groups. The + * plugin has to turn the plain form into the one a minifier keeps. + */ + private static final Pattern DEV_MODE_COMMENT = Pattern.compile( + "/\\*([*!])\\s+vaadin-dev-mode:start([\\s\\S]*?)vaadin-dev-mode:end\\s+\\*\\*/"); @Before public void init() { @@ -77,27 +88,45 @@ public void devBundleSourceMapsPointToOriginalSources() throws IOException { } /** - * The plugin that rewrites the dev mode comment of the usage statistics - * module is the one that has to return a sourcemap of its own. The - * rewritten comment starts with {@code /*!}, which tells a minifier to keep - * it. + * The code inside the dev mode comment of the usage statistics module is + * the code that is meant to run: the development mode detector reads it out + * of the source of the function it is in. It can only do that as long as + * the build keeps the comment in the bundle. + */ + @Test + public void usageStatisticsCodeRunsFromTheBundle() { + Assert.assertEquals( + "The code inside the dev mode comment should have run", "true", + findElement(By.tagName("html")) + .getDomAttribute("usage-statistics-stub-ran")); + } + + /** + * The plugin rewrites the comment so that it starts with {@code /*!}, which + * tells a minifier to keep it. The detector accepts either form, so this is + * what the plugin is for and what the build has to keep doing. */ @Test public void usageStatisticsCommentIsRewrittenInTheBundle() throws IOException { - boolean rewritten = false; + int rewritten = 0; for (File chunk : getBundleFiles(".js")) { - String contents = read(chunk); - Assert.assertFalse( - chunk.getName() + " should no longer have the original " - + "usage statistics comment", - contents.contains("/** vaadin-dev-mode:start")); - rewritten |= contents.contains("/*! vaadin-dev-mode:start"); + Matcher matcher = DEV_MODE_COMMENT.matcher(read(chunk)); + while (matcher.find()) { + if (!matcher.group(2).contains("usage-statistics-stub-ran")) { + // A dev mode comment of another module, left as it is + continue; + } + Assert.assertEquals(chunk.getName() + + " should have the usage statistics comment rewritten " + + "into the form a minifier keeps", "!", + matcher.group(1)); + rewritten++; + } } - Assert.assertTrue( - "A chunk of the dev bundle should have the usage statistics " - + "comment rewritten so that a minifier keeps it", - rewritten); + Assert.assertNotEquals( + "No chunk of the dev bundle has the usage statistics comment", + 0, rewritten); } private void assertHasSource(List sources, String source) { From c9872a70bc5ff98f8bbe2e7c5fddcf182ded47b2 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 08:47:12 +0000 Subject: [PATCH 7/7] test: cover only the comment rewriting of the usage statistics plugin Running the code from the dev mode comment pulled the development mode detector package into the test application and covered how that package reads and evaluates the comment, which is not code of this repository. The stand-in module is a plain frontend file again, and the test checks what the plugin does: the comment is in the bundle in the rewritten form. --- .../frontend/vaadin-usage-statistics-stub.js | 18 ++++-------- .../flow/frontend/DevBundleJsModuleView.java | 2 -- .../flow/frontend/DevBundleSourceMapsIT.java | 29 +++++-------------- 3 files changed, 12 insertions(+), 37 deletions(-) diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js index dc05204b2f1..708c21b08fd 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/frontend/vaadin-usage-statistics-stub.js @@ -1,21 +1,13 @@ -import { runIfDevelopmentMode } from '@vaadin/vaadin-development-mode-detector/vaadin-development-mode-detector.js'; - // Stands in for @vaadin/vaadin-usage-statistics, which a Flow only // application does not pull in. The published package has the comment below // in the rewritten form already, this file has the plain form that the -// vaadin:preserve-usage-stats build plugin has to turn into /*! for a -// minifier to keep it. See DevBundleSourceMapsIT. +// vaadin:preserve-usage-stats build plugin turns into /*! so that a minifier +// keeps it. See DevBundleSourceMapsIT. function maybeGatherAndSendStats() { /** vaadin-dev-mode:start - document.documentElement.setAttribute('usage-statistics-stub-ran', 'true'); + console.log('vaadin-usage-statistics-stub'); vaadin-dev-mode:end **/ } -// The code inside the comment above is the code that is meant to run: the -// development mode detector reads it out of the source of this function and -// runs it, which only works as long as the build keeps the comment in the -// bundle. Development mode is forced on so that the test covers the build and -// not the heuristics the detector uses to recognize development mode. -window.Vaadin = window.Vaadin || {}; -window.Vaadin.developmentMode = true; -runIfDevelopmentMode(maybeGatherAndSendStats); +// Keeps the module, and with it the comment, out of reach of tree shaking +window.vaadinUsageStatisticsStub = maybeGatherAndSendStats; diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java index 2ba7326916c..3a70cc77f32 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/src/main/java/com/vaadin/flow/frontend/DevBundleJsModuleView.java @@ -16,7 +16,6 @@ package com.vaadin.flow.frontend; import com.vaadin.flow.component.dependency.JsModule; -import com.vaadin.flow.component.dependency.NpmPackage; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.router.Route; @@ -26,7 +25,6 @@ @JsModule("./js/test.js") // Pulls a module named like @vaadin/vaadin-usage-statistics into the bundle, // so that the build plugin that rewrites its dev mode comment runs -@NpmPackage(value = "@vaadin/vaadin-development-mode-detector", version = "2.0.7") @JsModule("./vaadin-usage-statistics-stub.js") public class DevBundleJsModuleView extends Div { static final String SPAN_ID = "test-js-module-frontend-span"; 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 13fe782c3fd..622cb51858d 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 @@ -27,7 +27,6 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.openqa.selenium.By; import com.vaadin.flow.server.Constants; import com.vaadin.flow.testutil.ChromeBrowserTest; @@ -44,9 +43,8 @@ public class DevBundleSourceMapsIT extends ChromeBrowserTest { private static final String USAGE_STATISTICS_SOURCE = "src/main/frontend/vaadin-usage-statistics-stub.js"; /** - * The comment the development mode detector reads the code to run out of, - * with the form of its opening marker and its contents as groups. The - * plugin has to turn the plain form into the one a minifier keeps. + * A dev mode comment in a built chunk, with the form of its opening marker + * and its contents as the groups. */ private static final Pattern DEV_MODE_COMMENT = Pattern.compile( "/\\*([*!])\\s+vaadin-dev-mode:start([\\s\\S]*?)vaadin-dev-mode:end\\s+\\*\\*/"); @@ -88,23 +86,9 @@ public void devBundleSourceMapsPointToOriginalSources() throws IOException { } /** - * The code inside the dev mode comment of the usage statistics module is - * the code that is meant to run: the development mode detector reads it out - * of the source of the function it is in. It can only do that as long as - * the build keeps the comment in the bundle. - */ - @Test - public void usageStatisticsCodeRunsFromTheBundle() { - Assert.assertEquals( - "The code inside the dev mode comment should have run", "true", - findElement(By.tagName("html")) - .getDomAttribute("usage-statistics-stub-ran")); - } - - /** - * The plugin rewrites the comment so that it starts with {@code /*!}, which - * tells a minifier to keep it. The detector accepts either form, so this is - * what the plugin is for and what the build has to keep doing. + * Rewriting the dev mode comment of the usage statistics module so that it + * starts with {@code /*!}, which tells a minifier to keep it in the bundle, + * is the one thing the plugin does to the code. */ @Test public void usageStatisticsCommentIsRewrittenInTheBundle() @@ -113,7 +97,8 @@ public void usageStatisticsCommentIsRewrittenInTheBundle() for (File chunk : getBundleFiles(".js")) { Matcher matcher = DEV_MODE_COMMENT.matcher(read(chunk)); while (matcher.find()) { - if (!matcher.group(2).contains("usage-statistics-stub-ran")) { + if (!matcher.group(2) + .contains("vaadin-usage-statistics-stub")) { // A dev mode comment of another module, left as it is continue; }