Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
manifests.js 3.56 KiB
import fetch from 'node-fetch'
import path from 'path'
import { URL } from 'url'
import { fileCache } from './files.js'
import { config } from './config.js'

export const loadViteManifests = (() => {
  let cache
  let lastCacheTime

  return async function loadViteManifests ({ useCache = true } = {}) {
    const CACHE_TTL = parseInt(process.env.CACHE_TTL)

    if (!cache || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) {
      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
      const viteManifest = viteManifests.reduce((memo, manifest) => Object.assign(memo, manifest), {})
      // only update that object if it really changed to prevent any further parsing from being triggered
      if (!cache || JSON.stringify(viteManifest) !== JSON.stringify(cache)) {
        cache = viteManifest
      }

      lastCacheTime = +new Date()
    }
    return cache
  }
})()

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)
      await fileCache.warmUp(viteManifest, depCache)
      prevViteManifest = viteManifest
    }
    return depCache
  }
})()