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