import fetch from 'node-fetch' import path from 'path' import { URL } from 'url' import { fileCache } from './files.js' import { config } from './config.js' import { hash } from './util.js' export const loadViteManifests = (() => { let cachePromise let lastCacheTime let lastHash 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 url => { const { origin } = new URL(url) // fetch the manifests const result = await fetch(url) 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() for (const file in manifest) { manifest[file].meta = manifest[file].meta || {} manifest[file].meta.baseUrl = origin } return manifest } catch (err) { throw new Error(`Failed to load manifest for url ${result.url}: ${err}`) } })) // combine all manifests by keys. With duplicates, last wins return viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {}) } return function loadViteManifests ({ useCache = true } = {}) { const CACHE_TTL = parseInt(process.env.CACHE_TTL) if (!cachePromise || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) { cachePromise = reload() cachePromise.then(manifests => { // update cache promise const newHash = hash(manifests) if (newHash !== lastHash) { if (lastHash) { const deps = viteManifestToDeps(manifests) // asynchronously rewarm the cache fileCache.warmUp(manifests, deps) } lastHash = newHash Object.defineProperty(manifests, '__hash__', { enumerable: false, writable: true }) manifests.__hash__ = newHash } }) lastCacheTime = +new Date() } return cachePromise } })() export function viteToOxManifest (viteManifests) { const deps = viteManifestToDeps(viteManifests) return Object.values(viteManifests) .filter(manifest => Array.isArray(manifest?.meta?.manifests)) .map(manifest => manifest.meta.manifests.map(oxManifest => { const dependencies = deps[manifest.file] const data = { ...oxManifest, path: manifest.file.slice(0, -path.parse(manifest.file).ext.length) } if (dependencies?.length > 0) data.dependencies = dependencies return data }) ) .flat() } export const getOxManifests = (() => { let prevViteManifest let oxManifestCache return async function getOxManifests () { const viteManifest = await loadViteManifests() if (viteManifest !== prevViteManifest) { oxManifestCache = viteToOxManifest(viteManifest) prevViteManifest = viteManifest } return oxManifestCache } })() export function viteManifestToDeps (viteManifest) { const deps = {} for (const [codePoint, { isEntry, file, imports, css, assets }] of Object.entries(viteManifest)) { if (isEntry && codePoint.endsWith('.html')) deps[codePoint] = [file] if (Array.isArray(assets)) assets.forEach(asset => { deps[asset] = [] }) if (Array.isArray(css)) css.forEach(css => { deps[css] = [] }) deps[file] = [] .concat(imports?.map(path => viteManifest[path].file)) .concat(css) .concat(assets) .filter(Boolean) } return deps } export const getDependencies = (() => { let prevViteManifest let depCache return async function getDependencies () { const viteManifest = await loadViteManifests() if (viteManifest !== prevViteManifest) { depCache = viteManifestToDeps(viteManifest) prevViteManifest = viteManifest } return depCache } })() export async function getCSSDependenciesFor (file) { const allDependencies = await getDependencies() const dependencies = allDependencies[file] || [] return dependencies.filter(dep => /\.css/i.test(dep)) } export const getVersion = (() => { let prevViteManifest let version let versionString return async function getVersion () { const viteManifest = await loadViteManifests() if (viteManifest !== prevViteManifest) { const newVersion = hash(viteManifest) if (newVersion !== version) { versionString = `${+new Date()}.${newVersion}` version = newVersion } } return versionString } })()