Philipp Hauer's Blog

Java Ecosystem, Kotlin, Distributed Systems, Sociology of Software Development

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.

Vaadin 10+: SASS Integration and CSS Refresh during Development

TL;DR

The effect of a SASS change is immediately visible without any page reload or restart.

The effect of a SASS change is immediately visible without any page reload or restart.

  • 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 via web.ignoring().antMatchers("/js/cssrefresh.js") in your WebSecurityConfigurerAdapter.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 of Ctrl+S after changing the file. Or try to uncheck Use "safe write" under File > 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