diff --git a/spec/headers_test.js b/spec/headers_test.js index f2122fd4f66b1d15d73185779dba16b953d26cd1..f499f03dc4f293d8261df7bee4ae4b0db5d5f1dc 100644 --- a/spec/headers_test.js +++ b/spec/headers_test.js @@ -37,7 +37,7 @@ describe('Responses contain custom headers', function () { it('index.html has version', async function () { const response = await request(app).get('/index.html') expect(response.statusCode).to.equal(200) - expect(response.headers.version).to.equal('3038606729') + expect(response.headers.version).to.equal('3215668592') }) it('javascript file contains dependencies', async function () { @@ -45,4 +45,75 @@ describe('Responses contain custom headers', function () { expect(response.statusCode).to.equal(200) expect(response.headers.dependencies).to.equal('main.css') }) + + describe('with different files', function () { + beforeEach(async function () { + td.reset() + mockConfig({ urls: ['http://ui-server/'] }) + mockFetch({ + 'http://ui-server': { + '/manifest.json': generateSimpleViteManifest({ + 'index.html': {} + }), + '/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } }) + } + }) + app = await mockApp() + }) + + it('index.html has version', async function () { + const response = await request(app).get('/index.html') + expect(response.statusCode).to.equal(200) + // important here is, that it is different than in the test without meta.json + expect(response.headers.version).to.equal('3961519424') + }) + }) + + describe('with meta.json', function () { + beforeEach(async function () { + td.reset() + mockConfig({ urls: ['http://ui-server/'] }) + mockFetch({ + 'http://ui-server': { + '/manifest.json': generateSimpleViteManifest({ + 'index.html': {} + }), + '/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } }), + '/meta.json': { commitSha: '1234567890' } + } + }) + app = await mockApp() + }) + + it('index.html has version', async function () { + const response = await request(app).get('/index.html') + expect(response.statusCode).to.equal(200) + // important here is, that it is different than in the test without meta.json + expect(response.headers.version).to.equal('1487554813') + }) + }) + + describe('with different meta.json', function () { + beforeEach(async function () { + td.reset() + mockConfig({ urls: ['http://ui-server/'] }) + mockFetch({ + 'http://ui-server': { + '/manifest.json': generateSimpleViteManifest({ + 'index.html': {} + }), + '/index.html': () => new Response('<html><head></head><body>it\'s me</body></html>', { headers: { 'content-type': 'text/html' } }), + '/meta.json': { commitSha: '0987654321' } + } + }) + app = await mockApp() + }) + + it('index.html has version', async function () { + const response = await request(app).get('/index.html') + expect(response.statusCode).to.equal(200) + // important here is, that it is different than in the test without meta.json + expect(response.headers.version).to.equal('319344871') + }) + }) }) diff --git a/src/createApp.js b/src/createApp.js index e6dcb3845a33d3d16c2a3674a4ec2cd6480f131e..f6998f6101b48958d3257e579a11b0153e6dea8c 100644 --- a/src/createApp.js +++ b/src/createApp.js @@ -42,7 +42,7 @@ export function createApp () { const startupCheck = new health.StartupCheck('warmup cache', async function () { const stopTimer = startUpTimeGauge.startTimer() try { - const viteManifests = await loadViteManifests() + const viteManifests = await loadViteManifests({ warmUp: false }) // also need to load ox manifests here, to make sure the cache is warm await getOxManifests() const deps = viteManifestToDeps(viteManifests) diff --git a/src/manifests.js b/src/manifests.js index 6801ea15aa9ff9850ba8e9408f96addf95516a18..1598854667dac2f53a7d941cd628aab076c78447 100644 --- a/src/manifests.js +++ b/src/manifests.js @@ -6,17 +6,42 @@ import { hash, viteManifestToDeps } from './util.js' import { logger } from './logger.js' export const loadViteManifests = (() => { - let cachePromise + let lastManifest let lastCacheTime let lastHash + async function getHash () { + await config.load() + const infos = await Promise.all(config.urls.map(async baseUrl => { + try { + const response = await fetch(new URL('meta.json', baseUrl)) + if (!response.ok) throw new Error() + const meta = await response.json() + const version = meta.commitSha || meta.buildDate || meta.version + if (!version) throw new Error() + return version + } catch (err) { + logger.debug(`UI container at ${baseUrl} does not have meta.json. Fall back to version hash based on manifest.`) + } + try { + const response = await fetch(new URL('manifest.json', baseUrl)) + if (!response.ok) throw new Error() + const manifest = await response.json() + return hash(manifest) + } catch (err) { + logger.error(`Cannot fetch manifest from ${baseUrl}. Version info will not be correct.`) + } + })) + return hash(infos) + } + async function reload () { await config.load() // vite manifests contains a set of objects with the vite-manifests // from the corresponding registered services const viteManifests = await Promise.all(config.urls.map(async baseUrl => { // fetch the manifests - const result = await fetch(new URL('/manifest.json', baseUrl)) + const result = await fetch(new URL('manifest.json', baseUrl)) if (!result.ok) throw new Error(`Failed to load manifest for url ${result.url} (Status: ${result.status}: ${result.statusText})`) try { const manifest = await result.json() @@ -32,55 +57,52 @@ export const loadViteManifests = (() => { })) // combine all manifests by keys. With duplicates, last wins - const viteManifest = viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {}) - Object.defineProperty(viteManifest, '__hash__', { - enumerable: false, - writable: true - }) - - try { - viteManifest.__hash__ = hash(viteManifests) - } catch (err) { - logger.error(`Failed to calculate hash: ${err.message}`) - } - - return viteManifest + return viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {}) } - return function loadViteManifests ({ useCache = true } = {}) { + return function loadViteManifests ({ useCache = true, warmUp = true } = {}) { const CACHE_TTL = parseInt(process.env.CACHE_TTL) const timeElapsed = () => +((+new Date() - lastCacheTime) / 1000).toFixed(2) - if (!cachePromise || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) { - cachePromise = reload() - cachePromise.then(manifests => { + if (!lastManifest || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) { + const promise = (async () => { + const newHash = await getHash() + if (lastManifest && lastHash === newHash) return + const manifest = await reload() if (useCache) logger.info(`reloaded manifests after ${timeElapsed()} seconds`) - // update cache promise - const newHash = manifests.__hash__ - if (newHash !== lastHash) { - if (lastHash) { - const deps = viteManifestToDeps(manifests) - // asynchronously rewarm the cache - fileCache.warmUp(manifests, deps) - } - lastHash = newHash + // cache data + lastHash = newHash + lastManifest = manifest + + if (warmUp) { + const deps = viteManifestToDeps(manifest) + // asynchronously rewarm the cache + fileCache.warmUp(manifest, deps) } - }) + + Object.defineProperty(manifest, '__hash__', { + enumerable: false, + writable: true, + value: newHash + }) + return manifest + })().catch(err => logger.error(`Could not reload manifests: ${err}`)) + lastManifest = lastManifest || promise + lastCacheTime = +new Date() } - return cachePromise + return lastManifest } })() -export async function getOxManifests () { - await loadViteManifests().catch(() => {}) +export function getOxManifests () { + loadViteManifests() return fileCache.oxManifests } -export async function getDependencies () { - // simply catch the error here. This might happen, when one of the UI containers is temporarily not available - await loadViteManifests().catch(() => {}) +export function getDependencies () { + loadViteManifests() return fileCache.dependencies } @@ -90,7 +112,7 @@ export async function getCSSDependenciesFor (file) { return dependencies.filter(dep => /\.css/i.test(dep)) } -export async function getVersion () { - await loadViteManifests().catch(() => {}) +export function getVersion () { + loadViteManifests() return fileCache.hash }