Vaadin 10+: SASS Integration and CSS Refresh during Development
Posted on Apr 15, 2019. Updated on Apr 17, 2019
Since Vaadin 10, SASS is no longer supported out of the box. Fortunately, it’s really easy to integrate SASS in a Vaadin 10+ app and its Maven build. But how can we ensure a fast feedback cycle during the SASS development? Restarting the app is cumbersome. Even a page reload would reset the UI state, which leads to annoying clicking through the app. Luckily, there is an approach that automatically exchanges the changed CSS without any browser refresh or app restart.
TL;DR
- Integrating SASS in a Vaadin 10+ app and its Maven build is easy
- After a change in a SASS file, we can automatically trigger the SASS compilation and replace the new CSS in the browser. That happens without any page reload or application restart. This way, we don’t lose our UI state and don’t have to click through our app again and again.
SASS Integration
Let’s assume we are using Vaadin in conjunction with Spring Boot.
First, we put some SASS files under /src/main/resources/META-INF/resources/frontend/styles
.
src/main/resources
├── application.properties
├── META-INF
│ └── resources
│ ├── frontend
│ │ ├── images
│ │ └── styles
│ │ ├── exampleView.scss
│ │ ├── main.scss
│ │ └── variables.scss
│ └── js
│ └── cssrefresh.js
├── static
└── templates
Second, we add the sass-maven-plugin to the pom.xml.
<plugin>
<groupId>nl.geodienstencentrum.maven</groupId>
<artifactId>sass-maven-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<resources>
<resource>
<source>
<directory>${basedir}/src/main/resources/META-INF/resources/frontend/styles</directory>
<includes>
<include>**/*.scss</include>
</includes>
</source>
<relativeOutputDirectory>..</relativeOutputDirectory>
<destination>${basedir}/target/classes/META-INF/resources/frontend/styles</destination>
</resource>
</resources>
</configuration>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>update-stylesheets</goal>
</goals>
</execution>
</executions>
</plugin>
Now, we can trigger the SASS compilation with
mvn sass:update-stylesheets
This will create a main.css
. Finally, we only have to point to this CSS file in our RouterLayout
.
@Push
@StyleSheet("/frontend/styles/main.css")
@Viewport("width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes")
public class MainLayout extends Div implements RouterLayout {
public MainLayout() {
setClassName("main-layout");
}
}
Moreover, it’s important to refer to the MainLayout
in every view:
@Route(value = "", layout = MainLayout.class)
public class ExampleView extends VerticalLayout {
public ExampleView(){
addClassName("exampleView");
}
}
Voila, we successfully integrated SASS into our Vaadin 10+ application.
CSS Refresh Without a Page Reload During Development
The sass-maven-plugin provides the watch
goal that watches our SASS files for changes and automatically recompiles them to CSS.
mvn sass:watch
But how can we update the changed CSS in our browser on-the-fly? This is where Tony Findeisen’s cssrefresh comes in handy. Put this script under /src/main/resources/META-INF/resources/js/cssrefresh.js
.
(function() {
function cssRefresh(link) {
let href = link.href + '?x=' + Math.random();
fetch(href, {
method: 'HEAD'
})
.then(response => {
let lastModified = new Date(response.headers.get('last-modified'));
if (response.ok && (lastModified > link.lastModified || !link.lastModified)) {
link.lastModified = lastModified;
link.element.setAttribute('href', href);
}
setTimeout(() => {
cssRefresh(link);
}, 1000);
})
}
Array.prototype.slice.call(document.querySelectorAll('link'))
.filter(link => link.rel === 'stylesheet' && link.href)
.map(link => {
return {
element: link,
href: link.getAttribute('href').split('?')[0],
lastModified: false
};
})
.forEach(function(link) {
cssRefresh(link);
});
})();
This script checks every second if a css file has been changed (using the last-modified
header). If yes, it replaces the old CSS with the new one.
The best thing is that no page reload is required. This is especially useful because we don’t lose our UI state and can immediately see the effect of a SASS/CSS change. A browser refresh might force us to manually click through the UI again and again up to the point where the CSS changes are visible. That’s a huge relief.
The last step is to integrate cssrefresh in our Vaadin app. We want this script to be only active in the debug mode. It should be disabled in production mode. For this, just implement VaadinServiceInitListener
and add cssrefresh to the initial DOM of the Vaadin application.
@Component
public class CustomVaadinServiceListener implements VaadinServiceInitListener {
@Override
public void serviceInit(ServiceInitEvent event) {
if (!event.getSource().getDeploymentConfiguration().isProductionMode()) {
event.addBootstrapListener(new CustomBootstrapListener());
}
}
static class CustomBootstrapListener implements BootstrapListener {
@Override
public void modifyBootstrapPage(BootstrapPageResponse response) {
Element head = response.getDocument().head();
head.append("<script type=\"text/javascript\" src=\"/js/cssrefresh.js\"></script>");
}
}
}
Now we are ready to go.
- Start the application
- Run
mvn sass:watch
- Change a SASS file. Save changes.
- Profit (= the CSS is automatically updated in the browser without any page refresh)
Pitfalls
- If you are using the
@StyleSheet
annotation on Vaadin components, the referred CSS files are only loaded at the point when the component is visible in the UI. Currently, cssrefresh won’t detect changes in those CSS files. But it’s possible to adapt the script to watch out for new<link>
tags. However, we don’t use this annotation on components because we use a global CSS file which is included once and contains all styles for all components. Honestly, we don’t care about premature payload optimizations for internal tools. Moreover, we are still able to modularize our stylesheets with the capabilities of SASS. - Spring Security will block the path
/js/cssrefresh.js
on the login view because you are not logged in yet. Ignoring this path viaweb.ignoring().antMatchers("/js/cssrefresh.js")
in yourWebSecurityConfigurerAdapter.configure(WebSecurity)
implementation does the trick. It makes sense to only apply this matcher when in debug mode. - If you are using IntelliJ IDEA, you may run into troubles where the changes in IDEA are not detected by the sass plugin. In this case, try
Ctrl+Shift+F9
instead ofCtrl+S
after changing the file. Or try to uncheckUse "safe write"
underFile > Settings: Appearance & Behavior > System Settings
.
Source Code
The source code can be found in the project vaadin-10-sass-cssrefresh on GitHub. It uses Vaadin 13 and Spring Boot 2.1.
Contribution
I like to thank my collegues
- Tony Findeisen for writing the cssrefresh script.
- Martin Claus for coming up with a first integration of cssrefresh in our Vaadin 13 application.