When rendering new fragments, Unpoly compares scripts and stylesheets in the <head>
and emits an event if anything changed.
It is up to your code to handle new asset versions, e.g. by notifying the user or loading new assets.
To detect changes in your frontend code, Unpoly must track your application's assets. By default an asset is either a script or a stylesheet with a remote source.
In the example document below, the highlighted elements are considered to be assets:
<html>
<head>
<title>AcmeCorp</title>
<link rel="stylesheet" href="/assets/frontend-5f3aa101.css"> <!-- mark-line -->
<script src="/assets/frontend-81ba23a9.js"></script> <!-- mark-line -->
<script>console.log('loaded!')</script>
<link rel="canonical" href="https://example.com/dresses/green-dresses">
</head>
<body>
...
</body>
</html>
Note how the inline <script>
is not considered an asset by default.
See [up-asset]
for ways to include or exclude elements for asset tracking.
When rendering, Unpoly compares the current assets on the page with the new assets
from the server response. If the assets don't match, an up:assets:changed
event is emitted.
There is no default behavior when assets have changed.
In particular no asset elements from the response
are updated in the current page. It is up to the developer to observe the up:assets:changed
event and
implement a behavior that fits their app.
Below you can find some popular ways to handle new asset versions.
A friendly way to handle new asset version is to show a notification banner informing that a new app version is available. The user can then choose to reload at their convenience, by clicking on the notification:
The code below inserts a clickable <div id="new-version">
banner when assets change:
up.on('up:assets:changed', function() {
// If we are already showing a notification, do nothing.
if (document.querySelector('#new-version')) return
// Append a <div id="new-version"> notification to the <body>
up.element.affix(document.body, 'button#new-version', {
text: 'A new app version is available. Click to reload.',
onclick: 'location.reload()',
})
})
Tip
The code snippet uses the
up.element.affix()
function to quickly create a DOM element from a CSS selector.
An invisible way to handle new app versions if to make a full page load when the user follows the next link. This will unload all scripts and stylesheets, and reload your app from scratch.
let assetsChanged = false
up.on('up:assets:changed', function() {
assetsChanged = true
})
up.on('up:link:follow', function(event) {
if (assetsChanged && isLoadPageSafe(event.renderOptions)) {
// Prevent the render pass
event.preventDefault()
// Make full page load without Unpoly
up.network.loadPage(event.renderOptions)
}
})
function isLoadPageSafe({ url, layer, method }) {
// Default to 'GET' and uppercase the method string
let isSafeRequest = url && up.util.normalizeMethod(method) === 'GET'
// To prevent any overlays from closing, we only make a full page load
// when the link is changing the root layer.
let isRootLayer = up.layer.current.isRoot() && layer !== 'new'
return isSafeRequest && isRootLayer
}
The up:assets:changed
event has { oldAssets, newAssets }
properties that you can use to manually
insert the new assets into the page.
The code below will update all <link rel="stylesheet">
elements whenever there is a change:
function isStylesheet(asset) {
return asset.matches('link[rel=stylesheet]')
}
up.on('up:assets:changed', function({ oldAssets, newAssets }) {
let oldStylesheets = up.util.filter(oldAssets, isStylesheet)
for (let oldStylesheet of oldStylesheets) {
oldStylesheet.remove()
}
let newStylesheets = up.util.filter(newAssets, isStylesheet)
for (let newStylesheet of newStylesheets) {
document.head.append(newStylesheet)
}
})
Unfortunately updating <script>
elements in that fashion is not as straightforward.
Scripts cannot be "unloaded" by removing a <script>
element.
For this reason, script changes are better handled using one of the other techniques demonstrated above.
If you want to detect asset changes without a user interaction, use polling to reload an empty fragment every few minutes.
This will reload an empty fragment #version-detector
from a URL /version
every 2 minutes:
<div id="version-detector" up-poll up-interval="120_000" up-source="/version"></div>
You can configure Unpoly to also emit the up:asset:changed
event after a new version of your backend code was deployed.
See Tracking the backend version for details.