From d803ec8054acba9045107e35f4565f7f375642ce Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Mon, 4 May 2026 01:42:12 -0700 Subject: [PATCH] chore: fix CSS SSR rendering --- .gitignore | 1 + Island.cfm | 34 ++++++++++++++- coldspa/renderers/Vue.cfm | 11 +++-- coldspa/vite/ssr-server.js | 87 +++++++++++++++++++++++++++++++++----- dist-ssr/App.js | 33 --------------- dist-ssr/vue-ssr.js | 15 ------- 6 files changed, 118 insertions(+), 63 deletions(-) delete mode 100644 dist-ssr/App.js delete mode 100644 dist-ssr/vue-ssr.js diff --git a/.gitignore b/.gitignore index 66ec20c..9794208 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ WEB-INF/ crossdomain.xml node_modules/ dist/ +dist-ssr/ island-config.json \ No newline at end of file diff --git a/Island.cfm b/Island.cfm index c640890..be6b8d2 100644 --- a/Island.cfm +++ b/Island.cfm @@ -107,17 +107,40 @@ bootBody = rendered.body; // available, we just emit an empty div and the client mounts fresh -- so the // page works either way (just no JS-off rendering in that case). ssrHtml = ""; +ssrCss = ""; ssrError = ""; if (structKeyExists(attributes.framework, "ssrRender")) { ssrResult = attributes.framework.ssrRender(componentGlobKey, attributes.props); // Tolerate older renderers that returned a plain string. if (isStruct(ssrResult)) { ssrHtml = ssrResult.html ?: ""; + ssrCss = ssrResult.css ?: ""; ssrError = ssrResult.error ?: ""; } else { ssrHtml = ssrResult; } } + +// In prod the SSR sidecar can't easily inline component CSS (the SSR build +// doesn't emit CSS), so we surface it as tags from the client manifest. +// Each chunk in the manifest carries a `css` array of hashed asset filenames. +ssrCssLinks = []; +if (!cfg.isDev && len(ssrHtml) && structKeyExists(attributes.framework, "clientEntry")) { + try { + manifestPath = expandPath("/dist/.vite/manifest.json"); + if (fileExists(manifestPath)) { + manifest = deserializeJSON(fileRead(manifestPath)); + entryKey = reReplace(attributes.framework.clientEntry, "^\./", ""); + if (structKeyExists(manifest, entryKey) && structKeyExists(manifest[entryKey], "css")) { + for (cssFile in manifest[entryKey].css) { + arrayAppend(ssrCssLinks, "/dist/" & cssFile); + } + } + } + } catch (any e) { + // non-fatal: we'll just have a brief FOUC + } +} @@ -126,8 +149,17 @@ if (structKeyExists(attributes.framework, "ssrRender")) { - + + + + + + + + + +
#ssrHtml#
diff --git a/coldspa/renderers/Vue.cfm b/coldspa/renderers/Vue.cfm index f0ec053..6840f64 100644 --- a/coldspa/renderers/Vue.cfm +++ b/coldspa/renderers/Vue.cfm @@ -26,14 +26,19 @@ Vue = { } if (find("200", local.resp.statusCode)) { var parsed = deserializeJSON(local.resp.fileContent); - return { "html": parsed.html ?: "", "error": "" }; + return { + "html": parsed.html ?: "", + "css": parsed.css ?: "", + "error": "" + }; } return { - "html": "", + "html": "", + "css": "", "error": "SSR sidecar returned " & local.resp.statusCode & ": " & left(local.resp.fileContent, 500) }; } catch (any e) { - return { "html": "", "error": "SSR call failed: " & e.message }; + return { "html": "", "css": "", "error": "SSR call failed: " & e.message }; } }, diff --git a/coldspa/vite/ssr-server.js b/coldspa/vite/ssr-server.js index ef02ab5..331adbd 100644 --- a/coldspa/vite/ssr-server.js +++ b/coldspa/vite/ssr-server.js @@ -5,22 +5,21 @@ // Endpoints: // POST /render/:framework // body: { componentPath: string, props: object } -// 200: { html: string } +// 200: { html: string, css: string } // 500: { error: string } // // Modes: // - dev (default if NODE_ENV !== 'production'): uses Vite's middleware-mode // dev server + ssrLoadModule. Picks up component changes without -// rebuilding. +// rebuilding. Component CSS is collected from Vite's module graph +// and returned in the `css` field so CF can inline a