mdedetrich / sbt-digest-reverse-router

Obtained versioned assets created by sbt-digest

Version Matrix

Sbt Digest Reverse Router

A library designed to provide an easy way to get a versioned asset when using sbt-digest with sbt-web.

Note that if you are using Play then you should use their router since it has support for sbt-digest.

Also make sure you use sbt-digest version 1.1.4 or later due to this bug.

Installation

This library supports Scala Versions 2.12.x and 2.13.x. It only has a single dependency, typesafe config

Add the following to your build.sbt

libraryDependencies ++= Seq(
  "org.mdedetrich" %% "sbt-digest-reverse-router" % "0.2.0"
)

Usage

Sbt Digest Reverse Router works with resources that are product by sbt-web and digested by sbt-digest. The easiest way to use Sbt Digest Reverse Router is by configuration, below is the default application.conf

sbt-digest-reverse-router {
  digest-algorithm="md5"
  package-prefix="public/"
}
  • The digest-algorithm corresponds to sbt-digests DigestKeys.algorithms (if you are using multiple algorithms then pick one)
  • The package-prefix corresponds to sbt-web's WebKeys.packagePrefix in Assets := "public/"

Then for simple usage you can call the org.mdedetrich.SbtDigestReverseRouter.findVersionedAsset(assetFileName: String) if you have an asset in the root path or otherwise if the asset is located within a folder/s you can use org.mdedetrich.SbtDigestReverseRouter.findVersionedAsset(path: String, assetFileName: String).

As an example, if we want to locate a digested version of client's .js file generated by scala.js and sbt-web-scalajs we would simply do

scalajs.html.scripts("client",
  name => {
    val versioned = org.mdedetrich.SbtDigestReverseRouter.findVersionedAsset(name)
    s"/assets/$versioned"
  }, 
  name => getClass.getResource(s"/public/$name") != null
)

If we need to find the digested version of an asset, such as a main.css located in the assets/stylesheets folder, you would simply do

<link rel="stylesheet" media="screen" href = @(s"/assets/${org.mdedetrich.SbtDigestReverseRouter.findVersionedAsset("stylesheets/","main.css")}")>

If you are using twirl

or

link(rel := "stylesheet",
     media := "screen",
     href := s"/assets/${org.mdedetrich.SbtDigestReverseRouter.findVersionedAsset("stylesheets/","main.css")}"
 )

If you are using scalatags.

Note that sbt digest reverse router just uses pure Scala functions, so its not tied to any html templating framework (as long as your html templating framework can call Scala it should work fine)

Design

Under the scenes when you call one of the org.mdedetrich.SbtDigestReverseRouter.findVersionedAsset functions, sbt-digest-reverse-router will do the following

  1. Find sbt-digest's generated .algorithm file, i.e. if you have main.css file sbt-digest will generate a main.css.md5 if you are using md5 as an algorithm. This file contains the checksum of the original main.css file.
  2. If this file doesn't exist (should exist by default) then it will try and read the contents of the original asset file main.css to generate the checksum.
  3. We then simply output the versioned output of the file and we cache the result of the checksum, i.e. if the checksum of the main.css file is 9c119465f44ff0e091e7b1e2423d715b, sbt-digest-reverse-router would return 9c119465f44ff0e091e7b1e2423d715b-main.css

It is also recommended to aggressively cache the contents of the routes that serve your versioned assets i.e. you should set up a separate route specifically for your versioned assets (i.e. /versioned-assets/:file) and you should set the cache control header to something like Cache-Control: max-age=31556926 so that requests such as /versioned-assets/9c119465f44ff0e091e7b1e2423d715b-main.css are cached in the browser for a long time. Alternately you can also use a CDN for such assets.

sbt-digest-reverse-router will cache results with a memoized/cached with a java.util.concurrent.ConcurrentHashMap to improve on performance (the checksum of a specific file shouldn't change while your webserver is running). In general the code is also designed to be performant rather than being idiomatic Scala code (since this can be a hotspot in many cases). This means we use java libraries such as ByteArrayOutputStreamand InputStream and we do manual null checks in places where it makes sense to do so.

sbt-digest-reverse-router will throw a org.mdedetrich.SbtDigestReverseRouter.UnknownDigestAlgorithm exception if you are using an algorithm that is not md5 or sha1 (these are the only algorithms which sbt-digest currently supports). It will also throw a org.mdedetrich.SbtDigestReverseRouter.ResourceNotFound exception if its unable to find the resource asset (please see look at sbt-web's documentation to see where it places its assets).

Also note that while sbt-digest's DigestKeys.algorithms supports a list of algorithms, by default sbt-digest-reverse-router only works with a single algorithm. This is both to keep the code simple as well as the fact that in almost every situation you would only ever look up a digest with a single algorithm that you know upfront.