vite-plugin-foundryvtt
Version:
Plugin for building for foundryvtt. Enables hmr and manifest substitution.
184 lines (181 loc) • 5.36 kB
JavaScript
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 };