Skip to content

Commit

Permalink
Add static asset caching for one year
Browse files Browse the repository at this point in the history
Cache is busted for every deployment
  • Loading branch information
colinrotherham committed Dec 11, 2018
1 parent 6e014d6 commit 98f4f11
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ usage-data-config.json
.DS_Store
.start.pid
.port.tmp
app/version.txt
public
node_modules/*
.tmuxp.*
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Unreleased

New features:

- [#627 Add static asset caching (with cache busting)](https://github.com/alphagov/govuk-prototype-kit/pull/627)

Fixes:

- [#647 Fix link context in step-by-step templates](https://github.com/alphagov/govuk-prototype-kit/pull/647)
Expand Down
4 changes: 2 additions & 2 deletions app/views/includes/head.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<!--[if lte IE 8]><link href="/public/stylesheets/application-ie8.css" rel="stylesheet" type="text/css" /><![endif]-->
<!--[if gt IE 8]><!--><link href="/public/stylesheets/application.css" media="all" rel="stylesheet" type="text/css" /><!--<![endif]-->
<!--[if lte IE 8]><link href="{{ publicPath }}/stylesheets/application-ie8.css" rel="stylesheet" type="text/css" /><![endif]-->
<!--[if gt IE 8]><!--><link href="{{ publicPath }}/stylesheets/application.css" media="all" rel="stylesheet" type="text/css" /><!--<![endif]-->
8 changes: 4 additions & 4 deletions app/views/includes/scripts.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<!-- Javascript -->
<script src="/public/javascripts/jquery-1.11.3.js"></script>
<script src="/node_modules/govuk-frontend/all.js"></script>
<script src="/public/javascripts/application.js"></script>
<script src="{{ publicPath }}/javascripts/jquery-1.11.3.js"></script>
<script src="/node_modules/{{ cacheId }}/govuk-frontend/all.js"></script>
<script src="{{ publicPath }}/javascripts/application.js"></script>

{% if useAutoStoreData %}
<script src="/public/javascripts/auto-store-data.js"></script>
<script src="{{ publicPath }}/javascripts/auto-store-data.js"></script>
{% endif %}
8 changes: 4 additions & 4 deletions docs/views/includes/head.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<meta name="description" content="Use the GOV.UK Prototype Kit to quickly make realistic HTML prototypes of GOV.UK services.">

<!--[if lte IE 8]><link href="/public/stylesheets/application-ie8.css" rel="stylesheet" type="text/css" /><![endif]-->
<!--[if gt IE 8]><!--><link href="/public/stylesheets/application.css" media="all" rel="stylesheet" type="text/css" /><!--<![endif]-->
<!--[if lte IE 8]><link href="/public/stylesheets/docs-ie8.css" rel="stylesheet" type="text/css" /><![endif]-->
<!--[if gt IE 8]><!--><link href="/public/stylesheets/docs.css" media="all" rel="stylesheet" type="text/css" /><!--<![endif]-->
<!--[if lte IE 8]><link href="{{ publicPath }}/stylesheets/application-ie8.css" rel="stylesheet" type="text/css" /><![endif]-->
<!--[if gt IE 8]><!--><link href="{{ publicPath }}/stylesheets/application.css" media="all" rel="stylesheet" type="text/css" /><!--<![endif]-->
<!--[if lte IE 8]><link href="{{ publicPath }}/stylesheets/docs-ie8.css" rel="stylesheet" type="text/css" /><![endif]-->
<!--[if gt IE 8]><!--><link href="{{ publicPath }}/stylesheets/docs.css" media="all" rel="stylesheet" type="text/css" /><!--<![endif]-->
8 changes: 4 additions & 4 deletions docs/views/includes/scripts.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<!-- Javascript -->
<script src="/public/javascripts/jquery-1.11.3.js"></script>
<script src="/node_modules/govuk-frontend/all.js"></script>
<script src="/public/javascripts/docs.js"></script>
<script src="{{ publicPath }}/javascripts/jquery-1.11.3.js"></script>
<script src="/node_modules/{{ cacheId }}/govuk-frontend/all.js"></script>
<script src="{{ publicPath }}/javascripts/docs.js"></script>

{% if useAutoStoreData %}
<script src="/public/javascripts/auto-store-data.js"></script>
<script src="{{ publicPath }}/javascripts/auto-store-data.js"></script>
{% endif %}
2 changes: 1 addition & 1 deletion docs/views/layout_unbranded.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{% endblock %}

{% block head %}
<link href="/public/stylesheets/unbranded.css" media="all" rel="stylesheet" type="text/css">
<link href="{{ publicPath }}/stylesheets/unbranded.css" media="all" rel="stylesheet" type="text/css">
{% endblock %}

{% block header %}{% endblock %}
Expand Down
19 changes: 19 additions & 0 deletions gulp/sass.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,32 @@
also includes sourcemaps
*/

const fs = require('fs')
const gulp = require('gulp')
const path = require('path')
const sass = require('gulp-sass')
const sassVariables = require('gulp-sass-variables')
const sourcemaps = require('gulp-sourcemaps')

const config = require('./config.json')

// Default cache prefix
let cacheId = ''

// Inject Sass variables
const variables = () => {
cacheId = cacheId || fs.readFileSync(path.resolve('./app/version.txt'), 'utf-8').trim()

return {
'$govuk-assets-path': cacheId
? `/assets/${cacheId}/` : '/assets/'
}
}

gulp.task('sass', function () {
return gulp.src(config.paths.assets + '/sass/*.scss')
.pipe(sourcemaps.init())
.pipe(sassVariables(variables()))
.pipe(sass({outputStyle: 'expanded'}).on('error', sass.logError))
.pipe(sourcemaps.write())
.pipe(gulp.dest(config.paths.public + '/stylesheets/'))
Expand All @@ -22,6 +39,7 @@ gulp.task('sass', function () {
gulp.task('sass-documentation', function () {
return gulp.src(config.paths.docsAssets + '/sass/*.scss')
.pipe(sourcemaps.init())
.pipe(sassVariables(variables()))
.pipe(sass({outputStyle: 'expanded'}).on('error', sass.logError))
.pipe(sourcemaps.write())
.pipe(gulp.dest(config.paths.public + '/stylesheets/'))
Expand All @@ -32,6 +50,7 @@ gulp.task('sass-documentation', function () {
gulp.task('sass-v6', function () {
return gulp.src(config.paths.v6Assets + '/sass/*.scss')
.pipe(sourcemaps.init())
.pipe(sassVariables(variables()))
.pipe(sass({
outputStyle: 'expanded',
includePaths: [
Expand Down
1 change: 1 addition & 0 deletions gulp/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ gulp.task('default', function (done) {

gulp.task('generate-assets', function (done) {
runSequence('clean',
'version',
'sass',
'copy-assets',
'sass-documentation',
Expand Down
14 changes: 14 additions & 0 deletions gulp/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
version.js
===========
generates an incremental hash for cache-busting
*/

const fs = require('fs')
const gulp = require('gulp')
const path = require('path')

gulp.task('version', function (done) {
const version = (+new Date()).toString(36)
fs.writeFile(path.resolve('./app/version.txt'), version, done)
})
17 changes: 17 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,20 @@ exports.handleCookies = function (app) {
next()
}
}

// Remove cache ID from URL
exports.removeCacheId = function (req, res, next) {
const cacheId = req.app.locals.cacheId
const cachePath = `/${cacheId}/`

// Reset cache header if ID not found
if (!cacheId || !req.url.includes(cachePath)) {
res.setHeader('Cache-Control', 'public, max-age=0')
}

if (cacheId) {
req.url = req.url.replace(cachePath, '/')
}

next()
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"gulp-clean": "^0.4.0",
"gulp-nodemon": "^2.1.0",
"gulp-sass": "^4.0.1",
"gulp-sass-variables": "^1.2.0",
"gulp-sourcemaps": "^2.6.0",
"gulp-util": "^3.0.7",
"keypather": "^3.0.0",
Expand Down
38 changes: 30 additions & 8 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Core dependencies
const fs = require('fs')
const path = require('path')

// NPM dependencies
Expand Down Expand Up @@ -68,6 +69,12 @@ promoMode = promoMode.toLowerCase()
// Disable promo mode if docs aren't enabled
if (!useDocumentation) promoMode = 'false'

// Optional cache directory
var cacheId = ''
try {
cacheId = fs.readFileSync(`${__dirname}/app/version.txt`, 'utf-8').trim()
} catch (e) {}

// Force HTTPS on production. Do this before using basicAuth to avoid
// asking for username/password twice (for `http`, then `https`).
var isSecure = (env === 'production' && useHttps === 'true')
Expand Down Expand Up @@ -107,12 +114,16 @@ utils.addNunjucksFilters(nunjucksAppEnv)
// Set views engine
app.set('view engine', 'html')

// Cache assets for one year per deployment
const maxAge = 60 * 1000 * 60 * 24 * 365
app.use(utils.removeCacheId)

// Middleware to serve static assets
app.use('/public', express.static(path.join(__dirname, '/public')))
app.use('/assets', express.static(path.join(__dirname, 'node_modules', 'govuk-frontend', 'assets')))
app.use('/public', express.static(path.join(__dirname, '/public'), { maxAge }))
app.use('/assets', express.static(path.join(__dirname, 'node_modules', 'govuk-frontend', 'assets'), { maxAge }))

// Serve govuk-frontend in /public
app.use('/node_modules/govuk-frontend', express.static(path.join(__dirname, '/node_modules/govuk-frontend')))
app.use('/node_modules/govuk-frontend', express.static(path.join(__dirname, '/node_modules/govuk-frontend'), { maxAge }))

// Set up documentation app
if (useDocumentation) {
Expand Down Expand Up @@ -155,9 +166,9 @@ if (useV6) {
v6App.set('view engine', 'html')

// Backward compatibility with GOV.UK Elements
app.use('/public/v6/', express.static(path.join(__dirname, '/node_modules/govuk_template_jinja/assets')))
app.use('/public/v6/', express.static(path.join(__dirname, '/node_modules/govuk_frontend_toolkit')))
app.use('/public/v6/javascripts/govuk/', express.static(path.join(__dirname, '/node_modules/govuk_frontend_toolkit/javascripts/govuk/')))
app.use('/public/v6/', express.static(path.join(__dirname, '/node_modules/govuk_template_jinja/assets'), { maxAge }))
app.use('/public/v6/', express.static(path.join(__dirname, '/node_modules/govuk_frontend_toolkit'), { maxAge }))
app.use('/public/v6/javascripts/govuk/', express.static(path.join(__dirname, '/node_modules/govuk_frontend_toolkit/javascripts/govuk/'), { maxAge }))
}

// Add global variable to determine if DoNotTrack is enabled.
Expand All @@ -171,14 +182,25 @@ app.use(function (req, res, next) {

// Add variables that are available in all views
app.locals.gtmId = gtmId
app.locals.asset_path = '/public/'
app.locals.cacheId = cacheId
app.locals.publicUrl = app.locals.publicPath = '/public'
app.locals.assetUrl = app.locals.assetPath = '/assets'
app.locals.useAutoStoreData = (useAutoStoreData === 'true')
app.locals.useCookieSessionStore = (useCookieSessionStore === 'true')
app.locals.cookieText = config.cookieText
app.locals.promoMode = promoMode
app.locals.releaseVersion = 'v' + releaseVersion
app.locals.serviceName = config.serviceName

// Add cache directory
if (cacheId) {
app.locals.publicUrl = app.locals.publicPath = `/public/${cacheId}`
app.locals.assetUrl = app.locals.assetPath = `/assets/${cacheId}`
}

// Legacy asset_path for compatibility
app.locals.asset_path = `${app.locals.publicPath}/`

// Session uses service name to avoid clashes with other prototypes
const sessionName = 'govuk-prototype-kit-' + (Buffer.from(config.serviceName, 'utf8')).toString('hex')
let sessionOptions = {
Expand Down Expand Up @@ -277,7 +299,7 @@ if (useDocumentation) {
if (useV6) {
// Clone app locals to v6 app locals
v6App.locals = Object.assign({}, app.locals)
v6App.locals.asset_path = '/public/v6/'
v6App.locals.asset_path = `${app.locals.asset_path}v6/`

// Create separate router for v6
app.use('/', v6App)
Expand Down

0 comments on commit 98f4f11

Please sign in to comment.