import fs from 'fs/promises' import serveStatic from 'serve-static' import { Plugin } from 'vite' type MultipleSPAOptions = { defaultSite: string templatePath: string sites: Record> } /** * A custom plugin to create multiple virtual html files from a template. Will * generate distinct entry points and single-page application outputs. */ export function multipleSPA(options: MultipleSPAOptions): Plugin { const fillTemplate = async (siteName: string) => { const siteData = options.sites[siteName] const template = await fs.readFile(options.templatePath, 'utf-8') const filledTemplate = template .toString() .replace(/\{\{(.+)\}\}/g, (_match, p1) => siteData[(p1 as string).trim()]) return filledTemplate } return { name: 'multiple-spa', enforce: 'pre', configureServer(vite) { // TODO: this middleware is specific to the "mon-entreprise" app and // shouldn't be in the "multipleSPA" plugin. It allows to serve the // iframe integration script (already built) from the same server as the app. vite.middlewares.use( '/simulateur-iframe-integration.js', serveStatic(new URL('./dist', import.meta.url).pathname, { index: 'simulateur-iframe-integration.js', }) ) // eslint-disable-next-line @typescript-eslint/no-misused-promises vite.middlewares.use(async (req, res, next) => { const url = req.originalUrl?.replace(/^\/%2F/, '/') const firstLevelDir = url?.slice(1).split('/')[0] if (url && /\?.*html-proxy/.test(url)) { return next() } if (url && ['/', '/index.html'].includes(url)) { res.writeHead(302, { Location: '/' + options.defaultSite }).end() } // this condition is for the start:netlify script to match /mon-entreprise or /infrance else if ( firstLevelDir && url && Object.keys(options.sites) .map((site) => `/${site}.html`) .includes(url) ) { const siteName = firstLevelDir.replace('.html', '') const content = await vite.transformIndexHtml( '/' + siteName, await fillTemplate(siteName) ) res.end(content) } else if ( firstLevelDir && Object.keys(options.sites).some((name) => firstLevelDir === name) ) { const siteName = firstLevelDir const content = await vite.transformIndexHtml( url, await fillTemplate(siteName) ) res.end(content) } else { next() } }) }, config(config, { command }) { if (command === 'build' && !config.build?.ssr) { config.build = { ...config.build, rollupOptions: { ...config.build?.rollupOptions, input: Object.fromEntries( Object.keys(options.sites).map((name) => [ name, `virtual:${name}.html`, ]) ), }, } } }, resolveId(id) { const pathname = id.split('/').slice(-1)[0] if (pathname?.startsWith('virtual:')) { return pathname.replace('virtual:', '') } return null }, async load(id) { if ( Object.keys(options.sites).some((name) => id.endsWith(name + '.html')) ) { return await fillTemplate(id.replace(/\.html$/, '')) } }, } }