UNPKG

vite-plugin-foundryvtt

Version:

Plugin for building for foundryvtt. Enables hmr and manifest substitution.

184 lines (181 loc) 5.36 kB
import fs from 'node:fs'; import path from 'node:path'; import { compilePack } from '@foundryvtt/foundryvtt-cli'; import { ClassicLevel } from 'classic-level'; import { mergeConfig } from 'vite'; async function isLocked(dest) { try { const db = new ClassicLevel(dest); await db.open(); await db.close(); } catch { return true; } return false; } function foundryvtt(manifest, { substitutions = {}, type = "system", packDir = "src/packs", buildPacks = true, packOptions = { yaml: true } } = {}) { const config = { substitutions, type, packDir, buildPacks, packOptions }; return [ foundryWriteManifest(manifest, config), foundryCssFilename(), foundryVirtualEntryPoint(manifest), foundryBuildPacks(manifest, config), foundryHMR(manifest) ]; } function foundryWriteManifest(manifest, { substitutions = {}, type = "system" } = {}) { for (const [k, v] of Object.entries(substitutions)) if (v === void 0) delete substitutions[k]; return { name: "foundryvtt-write-manifest", generateBundle() { const data = mergeConfig(manifest, substitutions); const source = JSON.stringify(data, null, 2); this.emitFile({ type: "asset", fileName: `${type}.json`, source }); } }; } function foundryCssFilename() { return { name: "foundryvtt-css-filename", apply: "build", config(cfg) { cfg.base = "./"; cfg.build ??= {}; cfg.build.rolldownOptions ??= {}; cfg.build.rolldownOptions.output ??= {}; const afn = { assetFileNames: ({ names: [name] }) => name?.endsWith(".css") ? `styles/${name}` : name ?? "assets/[name]-[hash][extname]" }; if (Array.isArray(cfg.build.rolldownOptions.output)) { cfg.build.rolldownOptions.output.push(afn); } else { cfg.build.rolldownOptions.output = mergeConfig( cfg.build.rolldownOptions.output, afn ); } return cfg; } }; } function foundryVirtualEntryPoint(manifest) { let entryPoint; return { name: "foundry-virtual-entrypoints", apply(config, { command }) { return !!config.build?.lib && command === "serve"; }, configResolved(config) { const lib = config.build?.lib; if (!lib) return; entryPoint = (Array.isArray(lib.entry) ? lib.entry.at(0) : typeof lib.entry === "string" ? lib.entry : Object.entries(lib.entry).at(0)?.at(0)) ?? ""; }, async resolveId(source, _importer, { isEntry }) { const esmEntry = manifest.esmodules?.at(0); const cssEntry = (() => { const style = manifest.styles?.at(0); return typeof style === "string" ? style : style?.src; })(); if (esmEntry && source === `/${esmEntry}` || isEntry) { return { id: "\0virtual:entrypoint" }; } if (cssEntry && source.startsWith(`/${cssEntry}`)) { return { id: "\0virtual:styles" }; } }, load(id) { if (id === "\0virtual:entrypoint") { return { code: ` import "./${entryPoint}"; ` }; } if (id === "\0virtual:styles") { return { code: "" }; } } }; } function foundryBuildPacks(manifest, { packDir = "src/packs", buildPacks = true, packOptions = { yaml: true } } = {}) { let outdir; return { name: "foundryvtt-build-packs", apply(_config, { command }) { return buildPacks && command === "build" && (manifest.packs?.length ?? 0) !== 0; }, async configResolved(cfg) { outdir = cfg.build.outDir; for (const pack of manifest.packs ?? []) { if (!buildPacks) break; const dest = path.resolve( outdir, pack.path ?? path.resolve("packs", pack.name) ); if (await isLocked(dest)) buildPacks = false; } }, async writeBundle() { const packs = manifest.packs?.map((p) => ({ ...p, src: path.resolve(packDir, p.name), dest: path.resolve(outdir, p.path ?? path.resolve("packs", p.name)) })); if (!buildPacks || !packs) return; for (const p of packs) { try { await compilePack(p.src, p.dest, packOptions); this.info(`Built pack ${p.name}`); } catch (e) { if (e instanceof Error) this.warn(e); } } buildPacks = false; } }; } function foundryHMR(manifest) { const hotReload = manifest.flags?.hotReload || null; const extensions = hotReload?.extensions.map((e) => e); const paths = hotReload?.paths?.map((e) => e) ?? [""]; let outdir; let pubdir; return { name: "foundryvtt-hmr", configResolved(cfg) { outdir = cfg.build.outDir; pubdir = cfg.publicDir; }, handleHotUpdate({ file }) { if (!hotReload || !extensions) return; if (!file.startsWith(pubdir)) return; const file_path = path.relative(pubdir, file); const ext = path.extname(file_path).slice(1); if (!extensions.includes(ext) || !paths.some((p) => file_path.startsWith(p))) return; const dest = path.resolve(outdir, file_path); const k = { css: "css", json: "language", html: "template", hbs: "template", "": "" }; console.log(`Hot updating ${k[ext.toLowerCase()]} file '${file_path}'`); fs.copyFileSync(file, dest); } }; } export { foundryvtt as default };