mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-11-04 07:09:15 +01:00 
			
		
		
		
	🔤 ssr
This commit is contained in:
		
							
								
								
									
										43
									
								
								docs/components/examples/ssr/async-data.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								docs/components/examples/ssr/async-data.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
// Handling async data in SSR
 | 
			
		||||
import { JSDOM } from "jsdom";
 | 
			
		||||
import { S } from "deka-dom-el/signals";
 | 
			
		||||
import { register, queue } from "deka-dom-el/jsdom";
 | 
			
		||||
 | 
			
		||||
async function renderWithAsyncData() {
 | 
			
		||||
	const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
 | 
			
		||||
	const { el } = await register(dom);
 | 
			
		||||
 | 
			
		||||
	// Create a component that fetches data
 | 
			
		||||
	function AsyncComponent() {
 | 
			
		||||
			const title= S("-");
 | 
			
		||||
			const description= S("-");
 | 
			
		||||
 | 
			
		||||
		// Use the queue to track the async operation
 | 
			
		||||
		queue(fetch("https://api.example.com/data")
 | 
			
		||||
			.then(response => response.json())
 | 
			
		||||
			.then(data => {
 | 
			
		||||
				title.set(data.title);
 | 
			
		||||
				description.set(data.description);
 | 
			
		||||
			}));
 | 
			
		||||
 | 
			
		||||
		return el("div", { className: "async-content" }).append(
 | 
			
		||||
				el("h2", title),
 | 
			
		||||
				el("p", description)
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Render the page
 | 
			
		||||
	dom.window.document.body.append(
 | 
			
		||||
		el("h1", "Page with Async Data"),
 | 
			
		||||
		el(AsyncComponent)
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// IMPORTANT: Wait for all queued operations to complete
 | 
			
		||||
	await queue();
 | 
			
		||||
 | 
			
		||||
	// Now the HTML includes all async content
 | 
			
		||||
	const html = dom.serialize();
 | 
			
		||||
	console.log(html);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
renderWithAsyncData();
 | 
			
		||||
							
								
								
									
										47
									
								
								docs/components/examples/ssr/basic-example.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								docs/components/examples/ssr/basic-example.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
// Basic SSR Example
 | 
			
		||||
import { JSDOM } from "jsdom";
 | 
			
		||||
import { register, queue } from "deka-dom-el/jsdom";
 | 
			
		||||
import { writeFileSync } from "node:fs";
 | 
			
		||||
 | 
			
		||||
async function renderPage() {
 | 
			
		||||
	// Create a jsdom instance
 | 
			
		||||
	const dom = new JSDOM("<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body></body></html>");
 | 
			
		||||
 | 
			
		||||
	// Register with deka-dom-el and get the el function
 | 
			
		||||
	const { el } = await register(dom);
 | 
			
		||||
 | 
			
		||||
	// Create a simple header component
 | 
			
		||||
	function Header({ title }) {
 | 
			
		||||
		return el("header").append(
 | 
			
		||||
			el("h1", title),
 | 
			
		||||
			el("nav").append(
 | 
			
		||||
				el("ul").append(
 | 
			
		||||
					el("li").append(el("a", { href: "/" }, "Home")),
 | 
			
		||||
					el("li").append(el("a", { href: "/about" }, "About")),
 | 
			
		||||
					el("li").append(el("a", { href: "/contact" }, "Contact"))
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create the page content
 | 
			
		||||
	dom.window.document.body.append(
 | 
			
		||||
		el(Header, { title: "My Static Site" }),
 | 
			
		||||
		el("main").append(
 | 
			
		||||
			el("h2", "Welcome!"),
 | 
			
		||||
			el("p", "This page was rendered with deka-dom-el on the server.")
 | 
			
		||||
		),
 | 
			
		||||
		el("footer", "© 2025 My Company")
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// Wait for any async operations
 | 
			
		||||
	await queue();
 | 
			
		||||
 | 
			
		||||
	// Get the HTML and write it to a file
 | 
			
		||||
	const html = dom.serialize();
 | 
			
		||||
	writeFileSync("index.html", html);
 | 
			
		||||
 | 
			
		||||
	console.log("Page rendered successfully!");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
renderPage().catch(console.error);
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/components/examples/ssr/intro.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/components/examples/ssr/intro.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.js
 | 
			
		||||
import { register, unregister, queue } from "deka-dom-el/jsdom";
 | 
			
		||||
							
								
								
									
										35
									
								
								docs/components/examples/ssr/pages.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/components/examples/ssr/pages.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
// ❌ WRONG: Static imports are hoisted and will register before JSDOM is created
 | 
			
		||||
import { register } from "deka-dom-el/jsdom";
 | 
			
		||||
import { el } from "deka-dom-el";
 | 
			
		||||
import { Header } from "./components/Header.js";
 | 
			
		||||
 | 
			
		||||
// ✅ CORRECT: Use dynamic imports to ensure proper initialization order
 | 
			
		||||
import { JSDOM } from "jsdom";
 | 
			
		||||
 | 
			
		||||
async function renderPage() {
 | 
			
		||||
	// 1. Create JSDOM instance first
 | 
			
		||||
	const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`);
 | 
			
		||||
 | 
			
		||||
	// 2. Dynamically import jsdom module
 | 
			
		||||
	const { register, queue } = await import("deka-dom-el/jsdom");
 | 
			
		||||
 | 
			
		||||
	// 3. Register and get el function
 | 
			
		||||
	const { el } = await register(dom);
 | 
			
		||||
 | 
			
		||||
	// 4. Dynamically import page components
 | 
			
		||||
	const { Header } = await import("./components/Header.js");
 | 
			
		||||
	const { Content } = await import("./components/Content.js");
 | 
			
		||||
 | 
			
		||||
	// 5. Render components
 | 
			
		||||
	const body = dom.window.document.body;
 | 
			
		||||
	el(body).append(
 | 
			
		||||
		el(Header, { title: "My Page" }),
 | 
			
		||||
		el(Content, { text: "This is server-rendered content" })
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// 6. Wait for async operations
 | 
			
		||||
	await queue();
 | 
			
		||||
 | 
			
		||||
	// 7. Get HTML and clean up
 | 
			
		||||
	return dom.serialize();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								docs/components/examples/ssr/start.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								docs/components/examples/ssr/start.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
// Basic jsdom integration example
 | 
			
		||||
import { JSDOM } from "jsdom";
 | 
			
		||||
import { register, unregister, queue } from "deka-dom-el/jsdom.js";
 | 
			
		||||
 | 
			
		||||
// Create a jsdom instance
 | 
			
		||||
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
 | 
			
		||||
 | 
			
		||||
// Register the dom with deka-dom-el
 | 
			
		||||
const { el } = await register(dom);
 | 
			
		||||
 | 
			
		||||
// Use deka-dom-el normally
 | 
			
		||||
dom.window.document.body.append(
 | 
			
		||||
  el("div", { className: "container" }).append(
 | 
			
		||||
    el("h1", "Hello, SSR World!"),
 | 
			
		||||
    el("p", "This content was rendered on the server.")
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Wait for any async operations to complete
 | 
			
		||||
await queue();
 | 
			
		||||
 | 
			
		||||
// Get the rendered HTML
 | 
			
		||||
const html = dom.serialize();
 | 
			
		||||
console.log(html);
 | 
			
		||||
 | 
			
		||||
// Clean up when done
 | 
			
		||||
unregister();
 | 
			
		||||
							
								
								
									
										44
									
								
								docs/components/examples/ssr/static-site-generator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								docs/components/examples/ssr/static-site-generator.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
// Building a simple static site generator
 | 
			
		||||
import { JSDOM } from "jsdom";
 | 
			
		||||
import { register, queue } from "deka-dom-el/jsdom";
 | 
			
		||||
import { writeFileSync, mkdirSync } from "node:fs";
 | 
			
		||||
 | 
			
		||||
async function buildSite() {
 | 
			
		||||
	// Define pages to build
 | 
			
		||||
	const pages = [
 | 
			
		||||
		{ id: "index", title: "Home", component: "./pages/home.js" },
 | 
			
		||||
		{ id: "about", title: "About", component: "./pages/about.js" },
 | 
			
		||||
		{ id: "docs", title: "Documentation", component: "./pages/docs.js" }
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
	// Create output directory
 | 
			
		||||
	mkdirSync("./dist", { recursive: true });
 | 
			
		||||
 | 
			
		||||
	// Build each page
 | 
			
		||||
	for (const page of pages) {
 | 
			
		||||
		// Create a fresh jsdom instance for each page
 | 
			
		||||
		const dom = new JSDOM("<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body></body></html>");
 | 
			
		||||
 | 
			
		||||
		// Register with deka-dom-el
 | 
			
		||||
		const { el } = await register(dom);
 | 
			
		||||
 | 
			
		||||
		// Import the page component
 | 
			
		||||
		const { default: PageComponent } = await import(page.component);
 | 
			
		||||
 | 
			
		||||
		// Render the page with its metadata
 | 
			
		||||
		dom.window.document.body.append(
 | 
			
		||||
			el(PageComponent, { title: page.title, pages })
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		// Wait for any async operations
 | 
			
		||||
		await queue();
 | 
			
		||||
 | 
			
		||||
		// Write the HTML to a file
 | 
			
		||||
		const html = dom.serialize();
 | 
			
		||||
		writeFileSync(`./dist/${page.id}.html`, html);
 | 
			
		||||
 | 
			
		||||
		console.log(`Built page: ${page.id}.html`);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
buildSite().catch(console.error);
 | 
			
		||||
							
								
								
									
										129
									
								
								docs/p08-ssr.html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								docs/p08-ssr.html.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
     import { T, t } from "./utils/index.js";
 | 
			
		||||
export const info= {
 | 
			
		||||
	title: t`Server-Side Rendering (SSR)`,
 | 
			
		||||
	description: t`Using deka-dom-el for server-side rendering with jsdom to generate static HTML.`,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
import { el } from "deka-dom-el";
 | 
			
		||||
import { simplePage } from "./layout/simplePage.html.js";
 | 
			
		||||
import { h3 } from "./components/pageUtils.html.js";
 | 
			
		||||
import { code } from "./components/code.html.js";
 | 
			
		||||
/** @param {string} url */
 | 
			
		||||
const fileURL= url=> new URL(url, import.meta.url);
 | 
			
		||||
 | 
			
		||||
/** @param {import("./types.d.ts").PageAttrs} attrs */
 | 
			
		||||
export function page({ pkg, info }){
 | 
			
		||||
	const page_id= info.id;
 | 
			
		||||
	return el(simplePage, { info, pkg }).append(
 | 
			
		||||
		el("h2", t`Server-Side Rendering with deka-dom-el`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			deka-dom-el isn't limited to browser environments. Thanks to its flexible architecture,
 | 
			
		||||
			it can be used for server-side rendering (SSR) to generate static HTML files.
 | 
			
		||||
			This is achieved through integration with for example ${el("a", { href: "https://github.com/tmpvar/jsdom",
 | 
			
		||||
			textContent: "jsdom" })}, a JavaScript implementation of web standards for Node.js.
 | 
			
		||||
		`),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/ssr/intro.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Why Server-Side Rendering?`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			SSR offers several benefits:
 | 
			
		||||
		`),
 | 
			
		||||
		el("ul").append(
 | 
			
		||||
			el("li", t`Improved SEO - Search engines can easily index fully rendered content`),
 | 
			
		||||
			el("li", t`Faster initial page load - Users see content immediately without waiting for JavaScript to load`),
 | 
			
		||||
			el("li", t`Better performance on low-powered devices - Less JavaScript processing on the client`),
 | 
			
		||||
			el("li", t`Content available without JavaScript - Useful for users who have disabled JavaScript`),
 | 
			
		||||
			el("li", t`Static site generation - Build files once, serve them many times`)
 | 
			
		||||
		),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`How jsdom Integration Works`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			The jsdom export in deka-dom-el provides the necessary tools to use the library in Node.js
 | 
			
		||||
			by integrating with jsdom. Here's what it does:
 | 
			
		||||
		`),
 | 
			
		||||
		el("ol").append(
 | 
			
		||||
			el("li", t`Creates a virtual DOM environment in Node.js using jsdom`),
 | 
			
		||||
			el("li", t`Registers DOM globals like HTMLElement, document, etc. for deka-dom-el to use`),
 | 
			
		||||
			el("li", t`Sets an SSR flag in the environment to enable SSR-specific behaviors`),
 | 
			
		||||
			el("li", t`Provides a promise queue system for managing async operations during rendering`),
 | 
			
		||||
			el("li", t`Handles DOM property/attribute mapping differences between browsers and jsdom`)
 | 
			
		||||
		),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/ssr/start.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Basic SSR Example`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			Here's a simple example of how to use deka-dom-el for server-side rendering in a Node.js script:
 | 
			
		||||
		`),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/ssr/basic-example.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Building a Static Site Generator`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			You can build a complete static site generator with deka-dom-el. In fact, this documentation site
 | 
			
		||||
			is built using deka-dom-el for server-side rendering! Here's how the documentation build process works:
 | 
			
		||||
		`),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Working with Async Content in SSR`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			The jsdom export includes a queue system to handle asynchronous operations during rendering.
 | 
			
		||||
			This is crucial for components that fetch data or perform other async tasks.
 | 
			
		||||
		`),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/ssr/async-data.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Working with Dynamic Imports for SSR`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			When structuring server-side rendering code, a crucial pattern to follow is using dynamic imports
 | 
			
		||||
			for both the deka-dom-el/jsdom module and your page components.
 | 
			
		||||
		`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			Why is this important?
 | 
			
		||||
		`),
 | 
			
		||||
		el("ul").append(
 | 
			
		||||
			el("li").append(...T`
 | 
			
		||||
				${el("strong", "Static imports are hoisted:")} JavaScript hoists import statements to the top of the file,
 | 
			
		||||
				executing them before any other code
 | 
			
		||||
			`),
 | 
			
		||||
			el("li").append(...T`
 | 
			
		||||
				${el("strong", "Environment registration timing:")} The jsdom module auto-registers the DOM environment
 | 
			
		||||
				when imported, which must happen ${el("em", "after")} you've created your JSDOM instance and
 | 
			
		||||
				${el("em", "before")} you import your components using ${el("code", "import { el } from \"deka-dom-el\";")}.
 | 
			
		||||
			`),
 | 
			
		||||
			el("li").append(...T`
 | 
			
		||||
				${el("strong", "Correct initialization order:")} You need to control the exact sequence of:
 | 
			
		||||
				create JSDOM → register environment → import components
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			Follow this pattern when creating server-side rendered pages:
 | 
			
		||||
		`),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/ssr/pages.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`SSR Considerations and Limitations`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			When using deka-dom-el for SSR, keep these considerations in mind:
 | 
			
		||||
		`),
 | 
			
		||||
		el("ul").append(
 | 
			
		||||
			el("li", t`Browser-specific APIs like window.localStorage are not available in jsdom by default`),
 | 
			
		||||
			el("li", t`Event listeners added during SSR won't be functional in the final HTML unless hydrated on the client`),
 | 
			
		||||
			el("li", t`Some DOM features may behave differently in jsdom compared to real browsers`),
 | 
			
		||||
			el("li", t`For large sites, you may need to optimize memory usage by creating a new jsdom instance for each page`)
 | 
			
		||||
		),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			For advanced SSR applications, consider implementing hydration on the client-side to restore
 | 
			
		||||
			interactivity after the initial render.
 | 
			
		||||
		`),
 | 
			
		||||
 | 
			
		||||
		el(h3, t`Real Example: How This Documentation is Built`),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			This documentation site itself is built using deka-dom-el's SSR capabilities.
 | 
			
		||||
			The build process collects all page components, renders them with jsdom, and outputs static HTML files.
 | 
			
		||||
		`),
 | 
			
		||||
		el(code, { src: fileURL("./components/examples/ssr/static-site-generator.js"), page_id }),
 | 
			
		||||
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
			The resulting static files can be deployed to any static hosting service,
 | 
			
		||||
			providing fast loading times and excellent SEO without the need for client-side JavaScript
 | 
			
		||||
			to render the initial content.
 | 
			
		||||
		`),
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user