Saturday, August 3, 2013

Generate offline.manifest with every Java build

Introduction

In this post I will explain how to automatically generate a list of web resources for HTML5 offline caching. To keep the story short, you need to specify all CSS, JS, etc. in a manifest file. Then the browser will know that it needs to store them in cache. When you disconnect from the Internet, it will use those files instead of complaining about no connection available.

You can read more about building offline websites here.

Project structure

Lets assume you're using Maven and your project structure looks something like this:

|
+- src/
|  |
|  +- main/
|     |
|     +- webapp/
|        |
|        +- js/
|        |  |
|        |  +- ...a bunch of JS files
|        |
|        +- css/
|        |  |
|        |  +-...a bunch of CSS files
|        |
|        +- WEB-INF/
|        |  |
|        |  +- web.xml
|        |
|        +- offline.manifest
|        +- index.html
|
+- build.xml
+- pom.xml

index.html

To enable offline mode every html file of your website needs to include offline manifest. For example this is how index.html would look:

<!DOCTYPE html>
<html manifest="/offline.manifest">
  ...
</html>

offline.manifest

offline.manifest looks something like this:

CACHE MANIFEST
# 01/08/2013 16:00:00
/css/first.css
/css/second.css
...
/js/first.js
/js/second.js
...

It is a good practice to add some form of a timestamp during every build (# 01/08/2013 16:00:00). By doing this you let the browser know that it needs to reload the cached resources. Otherwise, if one of your files changes, but it is not a new file and the name haven't changed, there is a good chance it won't be reloaded.

We could add files manually to the offline.manifest, but that's quite error-prone. You can easily forget to add a file and then the offline mode won't work in a particular case. It's usually better to automatically generate it. Here we will use Apache Ant. The script logic will be placed in the build.xml.

build.xml

If you execute ant from the project directory:

/path/to/your/project$ ant 

it will automatically execute the generate-offline-manifest target:

<?xml version="1.0" encoding="UTF-8"?>
<project name="my-project" default="generate-offline-manifest">
    <target name="generate-offline-manifest">
        <property name="webapp-dir" value="${basedir}/src/main/webapp"/>

        <!-- 
                * pathsep -> separate files with new lines 
                * targetos -> make sure our files contain slashes (/) 
                              instead of windows backslashes (\) 
        -->
        <pathconvert property="list-of-files" pathsep="&#xA;" targetos="unix">
            <fileset dir="${webapp-dir}" >
                <include name="css/**/*" />
                <include name="js/**/*" />
            </fileset>

            <!-- make the path relative to the webappp dir (remove the absolute part) -->
            <map from="${webapp-dir}" to=""/>
        </pathconvert>

        <tstamp>
            <!-- generate timestamp - value will be stored in generation-time property -->
            <format property="generation-time" pattern="dd/MM/yyyy hh:mm:ss" />
        </tstamp>

        <!-- generate the offline.manifest -->
        <echo file="${webapp-dir}/offline.manifest">CACHE MANIFEST
# ${generation-time}
${list-of-files}</echo>
    </target>
</project>

pom.xml

In order to generate the offline.manifest every time the .war file is created, you can invoke the build.xml in your Maven build, during the prepare-package phase. You can accomplish this by adding the following snippet to your pom.xml:

<project>
    ...
    <build>
        ...
        <plugins>
            ...
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <version>1.7</version>
                <executions>
                    <execution>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <ant antfile="${basedir}/build.xml">
                                    <target name="generate-offline-manifest"/>
                                </ant>
                            </target>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The End

I hope you found this micro-tutorial useful. Happy coding!

1 comment: