/* eslint-env node */ import {readFileSync} from 'node:fs'; import {resolve, basename, extname, dirname, relative, join} from 'node:path'; import { getAssetEntrypoints, getScriptEntrypoints } from './entrypoints'; export function buildConfig ({ manifest: manifestPathRelative, outDir, scriptPlugins = [], sourcemap, }) { const manifestPath = resolve(process.cwd(), manifestPathRelative); const manifestDirname = dirname(manifestPath); // Load the manifest let manifestContent; try { manifestContent = JSON.parse(readFileSync(manifestPath, {encoding: 'utf-8'})); } catch (error) { throw new Error('Failed to load manifest'); } const uniqueFileNameSegmentCache = new Map(); function uniqueFileNameSegment (filepath, ext = extname(filepath).slice(1)) { // console.log('getting segment for', filepath); const cached = uniqueFileNameSegmentCache.get(filepath); if (cached) { return cached; } const idealName = basename(filepath, extname(filepath)); const buildName = (str, n) => n ? `${str}_${n}.${ext}` : `${str}.${ext}`; let uniquenessNum = 0; const existingNames = [...uniqueFileNameSegmentCache.values()]; while (existingNames.some(existingName => existingName.toLowerCase() === buildName(idealName, uniquenessNum).toLowerCase())) { uniquenessNum += 1; } const finalName = buildName(idealName, uniquenessNum); uniqueFileNameSegmentCache.set(filepath, finalName); return finalName; } function getOutputFilename (entryPath, ext?) { const base = relative(manifestDirname, dirname(entryPath)); return join(base, uniqueFileNameSegment(entryPath, ext)); } const scripts = getScriptEntrypoints(manifestContent); const assets = getAssetEntrypoints(manifestContent); return [ // Process each script entrypoint independently ...scripts.map(({path, replacePath}) => { // Figure out where this bundle will live in the output const outPath = getOutputFilename(path, 'js'); // Rewrite the manifest with that path replacePath(outPath); // Build the bundle return { input: relative(process.cwd(), path), output: { file: join(outDir, outPath), format: 'iife', sourcemap, }, plugins: scriptPlugins, }; }), // Special step that processes the manifest and injects other assets { input: manifestPathRelative, output: { file: join(outDir, 'manifest.json'), }, plugins: [ { // emit other assets buildStart () { assets.forEach(({path, replacePath}) => { // Figure out where the asset will live in output const outPath = getOutputFilename(path); // Rewrite the manifest with that path replacePath(outPath); // Emit the asset as part of the build step this.emitFile({ type: 'asset', fileName: outPath, source: readFileSync(path), }); }); }, // hacks to make sure the manifest is emitted as bare JSON load: id => id === manifestPath ? 'debugger;' : null, renderChunk: (_, chunk) => chunk.facadeModuleId === manifestPath ? JSON.stringify(manifestContent, null, '\t') : null, }, ], }, ]; }