UNPKG

nitropage

Version:

A free and open source, extensible visual page builder based on SolidStart.

201 lines (168 loc) 5.19 kB
import { Blueprint, BlueprintFn, Config } from "#../types"; import { baseFilename } from "#utils/string/baseFilename"; import { titleCase } from "#utils/string/titleCase"; import { once } from "es-toolkit"; import { Accessor, createEffect, createSignal, runWithOwner, Setter, startTransition, untrack, } from "solid-js"; import { produce, reconcile, SetStoreFunction } from "solid-js/store"; import { isServer } from "solid-js/web"; import { lazy } from "solid-lazy-plus"; import type { ViteHotContext } from "vite/types/hot.js"; import textData from "../../features/blueprintData/text"; const UNMOUNT = "$$unmount"; export const initBlueprint = async ( filePath: string, config: Config, setConfig: SetStoreFunction<Config>, ) => { if (!config.getBlueprints) return; const existingPromise = untrack(() => config.initPromises[filePath]); if (existingPromise) { await existingPromise; return; } const promise = (async () => { const blueprints = config.getBlueprints!(); if (!blueprints[filePath]) { console.error("Invalid blueprint, doesn't exist", filePath); return; } const m = await blueprints[filePath](); let id: string | undefined = m.id; if (!m.id || !m.default || !m.blueprint) { console.error( "Invalid blueprint, doesn't provide necessary exports (default, id, blueprint)", filePath, ); return; } let lastVersion: number = undefined!; const updateBlueprint = function () { let blueprintFn: BlueprintFn | typeof UNMOUNT = undefined!; let blueprint: Blueprint; try { blueprintFn = m.blueprint(); if (blueprintFn === UNMOUNT) { if (!id) return; console.info("Unmounting blueprint", id); // Another initBlueprint changed the version // Ergo the blueprint was moved to a different path // So we must skip the deletion const isRename = untrack( () => !!( config.blueprints[id!] && lastVersion !== config.blueprints[id!].version ), ); setConfig( produce((d) => { if (lastVersion != null && !isRename) delete d.blueprints[id!]; delete d.initPromises[filePath]; }), ); return; } } catch (err) { console.error(`Blueprint ${filePath} failed to run.`); console.error(err); return; } return (async function () { try { blueprint = (await blueprintFn({ admin: false, // ctx.admin, })) as Blueprint; } catch (err) { console.error(`Blueprint ${filePath} failed to run.`); console.error(err); return; } blueprint.component = import.meta.env.PROD ? lazy(async () => m) : m.default; blueprint.title = blueprint.title || once(() => { const fileName = baseFilename(filePath); const title = fileName !== "index" ? fileName : filePath.split("/").splice(-2)[0]; return titleCase(title); }); if (!blueprint.category || blueprint.category === "all") { blueprint.category = "misc"; } // blueprintFn._id -> id after hot module replace // m.id -> first exported id blueprint.id = blueprintFn._id || m.id || baseFilename(filePath).toLowerCase(); id = blueprint.id; blueprint.slots = blueprint.slots || {}; blueprint.data["id"] = textData({ sanitizeSlug: true, title() { return "ID #"; }, }); blueprint.version = Math.random(); lastVersion = blueprint.version; startTransition(() => setConfig( "blueprints", blueprint.id, reconcile(blueprint, { key: null, merge: true }), ), ); })(); }; if (isServer || import.meta.env.PROD) { return await updateBlueprint(); } else { runWithOwner(config.owner(), function () { createEffect(updateBlueprint); }); } })(); setConfig(produce((d) => (d.initPromises[filePath] = promise))); await promise; }; export const createBlueprint = function <T extends BlueprintFn>( fn: T, hot?: ViteHotContext, ): Accessor<T> { if (!hot) { return () => fn; } const signal: [state: Accessor<T>, setState: Setter<T>] = hot.data["npBlueprint"] || createSignal(fn); hot.data["npBlueprintFn"] = fn; hot.data["npBlueprint"] = signal; // Catch invalidate triggered by solid refresh hot.invalidate = () => {}; let hasError = false; hot.on("vite:error", function () { hasError = true; }); hot.accept(function (mod: any) { if (hasError) { hasError = false; return; } if (!mod?.blueprint) { signal[1](() => UNMOUNT as any); return; } hot.data["npBlueprintFn"]._id = mod.id; signal[1](() => hot.data["npBlueprintFn"]); }); return signal[0]; };