Skip to content

Commit ee5139e

Browse files
fix: update stylesheet when a referenced resource changes (#22563)
Fixes #22514 Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>
1 parent de0e067 commit ee5139e

File tree

20 files changed

+778
-31
lines changed

20 files changed

+778
-31
lines changed

flow-server/src/main/java/com/vaadin/flow/hotswap/Hotswapper.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.vaadin.flow.server.VaadinService;
6161
import com.vaadin.flow.server.VaadinSession;
6262
import com.vaadin.flow.server.frontend.FrontendUtils;
63+
import com.vaadin.flow.shared.ApplicationConstants;
6364

6465
/**
6566
* Entry point for application classes hot reloads.
@@ -224,7 +225,10 @@ public void onHotswap(URI[] createdResources, URI[] modifiedResources,
224225
if (path.startsWith("/")) {
225226
path = path.substring(1);
226227
}
227-
liveReload.update(path, null);
228+
liveReload.update(
229+
ApplicationConstants.CONTEXT_PROTOCOL_PREFIX
230+
+ path,
231+
null);
228232
}
229233
}
230234
});

flow-server/src/main/java/com/vaadin/flow/internal/BrowserLiveReload.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,12 @@ default void refresh(boolean refreshLayouts) {
9999

100100
/**
101101
* Request an update of the resource with the given path.
102-
*
102+
* <p>
103+
* Path may start with the `context://` prefix, which indicates that the
104+
* resource is located in the context root.
105+
*
103106
* @param path
104-
* the path of the file to update, relative to the servlet path
107+
* the path of the file to update, relative to the servlet path.
105108
* @param content
106109
* the new content of the file
107110
*/

flow-server/src/main/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ private void addDevTools(Document indexDocument,
410410
devToolsConf.put("enable", config.isDevModeLiveReloadEnabled());
411411
devToolsConf.put("url",
412412
BootstrapHandlerHelper.getPushURL(session, request));
413+
devToolsConf.put("contextRelativePath",
414+
service.getContextRootRelativePath(request));
413415
maybeBackend.ifPresent(
414416
backend -> devToolsConf.put("backend", backend.toString()));
415417
if (liveReloadPort != null) {

flow-server/src/test/java/com/vaadin/flow/hotswap/HotswapperResourcesTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.vaadin.flow.server.MockVaadinServletService;
3232
import com.vaadin.flow.server.startup.ApplicationConfiguration;
3333
import com.vaadin.flow.server.startup.ApplicationConfigurationFactory;
34+
import com.vaadin.flow.shared.ApplicationConstants;
3435
import com.vaadin.tests.util.MockDeploymentConfiguration;
3536

3637
public class HotswapperResourcesTest {
@@ -84,7 +85,9 @@ public void cssResourceChange_triggersLiveReloadUpdateWithRelativePath()
8485

8586
// Expect BrowserLiveReload.update to be called with relative URL path
8687
// "styles/app.css"
87-
Mockito.verify(liveReload).update("styles/app.css", null);
88+
Mockito.verify(liveReload).update(
89+
ApplicationConstants.CONTEXT_PROTOCOL_PREFIX + "styles/app.css",
90+
null);
8891
Mockito.verifyNoMoreInteractions(liveReload);
8992
}
9093

flow-tests/test-live-reload/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
<webApp>
5959
<!-- use a non-root context -->
6060
<contextPath>/context</contextPath>
61+
<!-- Make sure CSS in target folder are loaded by Jetty -->
62+
<resourceBases>
63+
<resouceBase>${project.build.outputDirectory}/META-INF/resources</resouceBase>
64+
</resourceBases>
6165
</webApp>
6266
</configuration>
6367
</plugin>

flow-tests/test-live-reload/src/main/java/com/vaadin/flow/uitest/ui/AppShell.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616
package com.vaadin.flow.uitest.ui;
1717

18+
import com.vaadin.flow.component.dependency.StyleSheet;
1819
import com.vaadin.flow.component.page.AppShellConfigurator;
1920
import com.vaadin.flow.server.PWA;
2021
import com.vaadin.flow.theme.Theme;
2122

2223
@PWA(name = "Live Reload View", shortName = "live-reload-view")
2324
@Theme("mytheme")
25+
@StyleSheet("context://css/styles.css")
2426
public class AppShell implements AppShellConfigurator {
2527
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.uitest.ui;
17+
18+
import com.vaadin.flow.component.dependency.StyleSheet;
19+
import com.vaadin.flow.component.html.Div;
20+
import com.vaadin.flow.component.html.NativeButton;
21+
import com.vaadin.flow.internal.BrowserLiveReloadAccessor;
22+
import com.vaadin.flow.router.Route;
23+
import com.vaadin.flow.server.VaadinService;
24+
import com.vaadin.flow.uitest.servlet.ViewTestLayout;
25+
26+
@Route(value = "com.vaadin.flow.uitest.ui.StylesheetLiveReloadView", layout = ViewTestLayout.class)
27+
@StyleSheet("context://css/view/view.css")
28+
public class StylesheetLiveReloadView extends AbstractLiveReloadView {
29+
30+
public StylesheetLiveReloadView() {
31+
add(makeDiv("appshell-style", "css/styles.css"));
32+
add(makeDiv("appshell-imported", "css/imported.css"));
33+
add(makeDiv("appshell-nested-imported",
34+
"css/nested/nested-imported.css"));
35+
add(makeDiv("appshell-image", "css/images/gobo.png"));
36+
add(makeDiv("view-style", "css/view/view.css"));
37+
add(makeDiv("view-imported", "css/view/imported.css"));
38+
add(makeDiv("view-nested-imported",
39+
"css/view/nested/nested-imported.css"));
40+
add(makeDiv("view-image", "css/images/viking.png"));
41+
add(makeDiv("non-css-resource", "static/read.me"));
42+
}
43+
44+
private Div makeDiv(String cssClass, String cssFile) {
45+
Div div = new Div();
46+
div.setId(cssClass);
47+
div.setText("Style defined in " + cssFile);
48+
div.addClassName(cssClass);
49+
// Simulate Flow Hotswapper handling of CSS change
50+
NativeButton reloadButton = new NativeButton(
51+
"Trigger Stylesheet live reload", ev -> {
52+
BrowserLiveReloadAccessor
53+
.getLiveReloadFromService(
54+
VaadinService.getCurrent())
55+
.ifPresent(reload -> reload
56+
.update("context://" + cssFile, null));
57+
});
58+
reloadButton.setId("reload-" + cssClass);
59+
reloadButton.getElement().setAttribute("test-resource-file-path",
60+
cssFile);
61+
div.add(reloadButton);
62+
return div;
63+
}
64+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Marker file to be searched to get folder path.
1.59 KB
Loading
2.42 KB
Loading

0 commit comments

Comments
 (0)