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 Mar 14, 2019
1 parent 9e9a581 commit fbccc87
Show file tree
Hide file tree
Showing 13 changed files with 100 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ lib/extensions/_extensions.scss
.DS_Store
.start.pid
.port.tmp
app/version.txt
public
node_modules/*
.tmuxp.*
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Features:
# 8.5.0

Features:
- [#627 Add static asset caching (with cache busting)](https://github.com/alphagov/govuk-prototype-kit/pull/627)
- [#672 Replace ‘check answers’ pattern with updated code](https://github.com/alphagov/govuk-prototype-kit/pull/672)
- [#671 Update to GOV.UK Frontend version 2.5.0](https://github.com/alphagov/govuk-prototype-kit/pull/671)
Allows use of new components Accordion and Summary List
Expand Down
4 changes: 2 additions & 2 deletions app/views/includes/head.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--[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]-->

{% for stylesheetUrl in extensionConfig.stylesheets %}
<link href="{{ stylesheetUrl }}" rel="stylesheet" type="text/css" />
Expand Down
6 changes: 3 additions & 3 deletions app/views/includes/scripts.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<!-- Javascript -->
<script src="/public/javascripts/jquery-1.11.3.js"></script>
<script src="{{ publicPath }}/javascripts/jquery-1.11.3.js"></script>

{% for scriptUrl in extensionConfig.scripts %}
<script src="{{scriptUrl}}"></script>
{% endfor %}

<script src="/public/javascripts/application.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="/extension-assets/govuk-frontend/all.js"></script>
<script src="/public/javascripts/docs.js"></script>
<script src="{{ publicPath }}/javascripts/jquery-1.11.3.js"></script>
<script src="/extension-assets/{{ 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
23 changes: 21 additions & 2 deletions gulp/sass.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,31 @@
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 path = require('path')
const fs = require('fs')

const extensions = require('../lib/extensions/extensions')
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/'
.join('\n')
}
}

gulp.task('sass-extensions', function (done) {
const fileContents = '$govuk-extensions-url-context: "/extension-assets"; ' + extensions.getFileSystemPaths('sass')
.map(filePath => `@import "${filePath.split(path.sep).join('/')}";`)
Expand All @@ -24,6 +40,7 @@ gulp.task('sass-extensions', function (done) {
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 @@ -32,6 +49,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 @@ -42,6 +60,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
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)
})
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ requireDir('./gulp', { recurse: true })
// We'll keep our top-level tasks in this file so that they are defined at the end of the chain, after their dependencies.
gulp.task('generate-assets', gulp.series(
'clean',
'version',
'sass-extensions',
gulp.parallel(
'sass',
Expand Down
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 @@ -34,6 +34,7 @@
"gulp": "^4.0.0",
"gulp-nodemon": "^2.1.0",
"gulp-sass": "^4.0.1",
"gulp-sass-variables": "^1.2.0",
"gulp-sourcemaps": "^2.6.0",
"keypather": "^3.0.0",
"marked": "^0.4.0",
Expand Down
37 changes: 30 additions & 7 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 @@ -72,6 +73,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 @@ -108,11 +115,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('/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 from node_modules (so not to break pre-extenstions prototype kits)
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 +167,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,7 +183,9 @@ 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
Expand All @@ -181,6 +195,15 @@ app.locals.serviceName = config.serviceName
// extensionConfig sets up variables used to add the scripts and stylesheets to each page.
app.locals.extensionConfig = extensions.getAppConfig()

// 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 @@ -279,7 +302,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 fbccc87

Please sign in to comment.