diff --git a/Island.cfm b/Island.cfm index ab6269b..2530ab4 100644 --- a/Island.cfm +++ b/Island.cfm @@ -10,6 +10,11 @@ props (struct, optional) - props passed to the component (default {}) strategy (string, optional) - "load" | "idle" | "visible" | "client" (default "load") "client" skips SSR entirely (no server HTML, no CSS pre-render) + + Body / slots: + Anything between and is captured (after CFML + evaluation) and passed to the component as its default slot. Use a + in your Vue component (or {children} in React) to render it. ---> @@ -17,11 +22,18 @@ -// Custom tags execute twice (start + end). Only emit on start. -if (thisTag.executionMode neq "start") { - exit "exitTag"; +// Run on the END pass so thisTag.generatedContent contains the rendered +// children (the "default slot"). The start pass exits early via exitTemplate +// so the body still executes and CF still invokes us again in end mode. +// (exit "exitTag" would skip both the body AND the end-mode invocation.) +if (thisTag.executionMode neq "end") { + exit "exitTemplate"; } +slotHtml = trim(thisTag.generatedContent); +// Reset; we'll write the full island markup back at the end. +thisTag.generatedContent = ""; + // --- validate strategy validStrategies = ["load", "idle", "visible", "client"]; if (!arrayFindNoCase(validStrategies, attributes.strategy)) { @@ -31,10 +43,8 @@ if (!arrayFindNoCase(validStrategies, attributes.strategy)) { ); } -// "client" strategy is client-only: skip SSR entirely. clientOnly = (attributes.strategy == "client"); -// --- validate renderer shape if (!structKeyExists(attributes.framework, "render") || !isCustomFunction(attributes.framework.render)) { throw( type = "Coldspa.InvalidRenderer", @@ -42,24 +52,17 @@ if (!structKeyExists(attributes.framework, "render") || !isCustomFunction(attrib ); } -// --- resolve config (lazy fallback if Application.cfc didn't wire it) if (!structKeyExists(application, "coldspaConfig")) { application.coldspaConfig = new coldspa.ColdspaConfig().get(); } cfg = application.coldspaConfig; -// --- resolve asset path (dev: vite server URL; prod: manifest lookup) function resolveAsset(required string path) { if (cfg.isDev) { - // Strip leading "./" so we get a clean URL join var clean = reReplace(arguments.path, "^\./", ""); - // viteUrl override (from COLDSPA_VITE_URL env) lets the browser reach - // a Vite dev server that isn't on localhost (e.g. host.docker.internal). var viteBase = cfg.viteUrl ?: ("http://localhost:" & cfg.vitePort); return viteBase & "/" & clean; } - - // Production: look up content-hashed file in vite manifest var manifestPath = expandPath("/dist/.vite/manifest.json"); if (!fileExists(manifestPath)) { throw( @@ -81,32 +84,23 @@ function resolveAsset(required string path) { uid = lcase(replace(createUUID(), "-", "", "all")); mountId = "island-" & uid; // DOM id (hyphens fine) jsId = "island_" & uid; // JS-identifier-safe (no hyphens) +slotId = "slot-" & uid; //