mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-11-04 07:09:15 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			364 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { T, t } from "./utils/index.js";
 | 
						||
export const info= {
 | 
						||
	title: t`Ireland Components`,
 | 
						||
	fullTitle: t`Interactive Demo Components with Server-Side Pre-Rendering`,
 | 
						||
	description: t`Creating live, interactive component examples in documentation with server-side
 | 
						||
		rendering and client-side hydration.`,
 | 
						||
};
 | 
						||
 | 
						||
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";
 | 
						||
import { ireland } from "./components/ireland.html.js";
 | 
						||
/** @param {string} url */
 | 
						||
const fileURL= url=> new URL(url, import.meta.url);
 | 
						||
 | 
						||
/** @param {import("./types.js").PageAttrs} attrs */
 | 
						||
export function page({ pkg, info }){
 | 
						||
	const page_id= info.id;
 | 
						||
	return el(simplePage, { info, pkg }).append(
 | 
						||
		el("div", { className: "warning" }).append(
 | 
						||
			el("p").append(...T`
 | 
						||
				This part of the documentation is primarily intended for technical enthusiasts and authors of
 | 
						||
				3rd-party libraries. It describes an advanced feature, not a core part of the library. Most users will
 | 
						||
				not need to implement this functionality directly in their applications. This capability will hopefully
 | 
						||
				be covered by third-party libraries or frameworks that provide simpler SSR integration using
 | 
						||
				dd<el>.
 | 
						||
			`)
 | 
						||
		),
 | 
						||
 | 
						||
		el(h3, t`What Are Ireland Components?`),
 | 
						||
		el("p").append(...T`
 | 
						||
			Ireland components are a special type of documentation component that:
 | 
						||
		`),
 | 
						||
		el("ul").append(
 | 
						||
			el("li", t`Display source code with syntax highlighting`),
 | 
						||
			el("li", t`Pre-render components on the server during documentation build`),
 | 
						||
			el("li", t`Copy component source files to the documentation output`),
 | 
						||
			el("li", t`Provide client-side rehydration for interactive demos`),
 | 
						||
			el("li", t`Allow users to run and experiment with components in real-time`)
 | 
						||
		),
 | 
						||
 | 
						||
		el(h3, t`How Ireland Components Work`),
 | 
						||
		el("p").append(...T`
 | 
						||
			The Ireland component system consists of several parts working together:
 | 
						||
		`),
 | 
						||
 | 
						||
		el("ol").append(
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "Server-side rendering:")} Components are pre-rendered during the documentation build process
 | 
						||
			`),
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "Component registration:")} Source files are copied to the documentation output directory
 | 
						||
			`),
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "Client-side scripting:")} JavaScript code is generated to load and render components
 | 
						||
			`),
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "User interaction:")} The "Run Component" button dynamically loads and renders the component
 | 
						||
			`)
 | 
						||
		),
 | 
						||
 | 
						||
		el(h3, t`Implementation Architecture`),
 | 
						||
		el("p").append(...T`
 | 
						||
			The core of the Ireland system is implemented in ${el("code", "docs/components/ireland.html.js")}.
 | 
						||
			It integrates with the SSR build process using the ${el("code", "registerClientFile")} function
 | 
						||
			from ${el("code", "docs/ssr.js")}.
 | 
						||
		`),
 | 
						||
 | 
						||
		el(code, { content: `
 | 
						||
// Basic usage of an ireland component
 | 
						||
el(ireland, {
 | 
						||
	src: fileURL("./components/examples/path/to/component.js"),
 | 
						||
	exportName: "NamedExport", // optional, defaults to "default",
 | 
						||
})`, page_id }),
 | 
						||
 | 
						||
		el("p").append(...T`
 | 
						||
			During the build process (${el("code", "bs/docs.js")}), the following happens:
 | 
						||
		`),
 | 
						||
 | 
						||
		el("ol").append(
 | 
						||
			el("li", t`Component source code is loaded and displayed with syntax highlighting`),
 | 
						||
			el("li", t`Source files are registered to be copied to the output directory`),
 | 
						||
			el("li", t`Client-side scripts are generated for each page with ireland components`),
 | 
						||
			el("li", t`The component is wrapped in a UI container with controls`)
 | 
						||
		),
 | 
						||
 | 
						||
		el(h3, t`Core Implementation Details`),
 | 
						||
		el("p").append(...T`
 | 
						||
			Let's look at the key parts of the ireland component implementation:
 | 
						||
		`),
 | 
						||
 | 
						||
		el("h4", t`Building SSR`),
 | 
						||
		el(code, { content: `
 | 
						||
// From bs/docs.js - Server-side rendering engine
 | 
						||
import { createHTMl } from "./docs/jsdom.js";
 | 
						||
import { register, queue } from "../jsdom.js";
 | 
						||
import { path_target, dispatchEvent } from "../docs/ssr.js";
 | 
						||
 | 
						||
// For each page, render it on the server
 | 
						||
for(const { id, info } of pages) {
 | 
						||
	// Create a virtual DOM environment for server-side rendering
 | 
						||
	const serverDOM = createHTMl("");
 | 
						||
	serverDOM.registerGlobally("HTMLScriptElement");
 | 
						||
 | 
						||
	// Register dd<el> with the virtual DOM
 | 
						||
	const { el } = await register(serverDOM.dom);
 | 
						||
 | 
						||
	// Import and render the page component
 | 
						||
	const { page } = await import(\`../docs/\${id}.html.js\`);
 | 
						||
	serverDOM.document.body.append(
 | 
						||
	el(page, { pkg, info }),
 | 
						||
	);
 | 
						||
 | 
						||
	// Process the queue of asynchronous operations
 | 
						||
	await queue();
 | 
						||
 | 
						||
	// Trigger render event handlers
 | 
						||
	dispatchEvent("oneachrender", document);
 | 
						||
 | 
						||
	// Write the HTML to the output file
 | 
						||
	s.echo(serverDOM.serialize()).to(path_target.root+id+".html");
 | 
						||
}
 | 
						||
 | 
						||
// Final build step - trigger SSR end event
 | 
						||
dispatchEvent("onssrend");
 | 
						||
`, page_id }),
 | 
						||
		el("h4", t`File Registration`),
 | 
						||
		el(code, { content: `
 | 
						||
// From docs/ssr.js - File registration system
 | 
						||
export function registerClientFile(url, { head, folder = "", replacer } = {}) {
 | 
						||
	// Ensure folder path ends with a slash
 | 
						||
	if(folder && !folder.endsWith("/")) folder += "/";
 | 
						||
 | 
						||
	// Extract filename from URL
 | 
						||
	const file_name = url.pathname.split("/").pop();
 | 
						||
 | 
						||
	// Create target directory if needed
 | 
						||
	s.mkdir("-p", path_target.root+folder);
 | 
						||
 | 
						||
	// Get file content and apply optional replacer function
 | 
						||
	let content = s.cat(url);
 | 
						||
	if(replacer) content = s.echo(replacer(content.toString()));
 | 
						||
 | 
						||
	// Write content to the output directory
 | 
						||
	content.to(path_target.root+folder+file_name);
 | 
						||
 | 
						||
	// If a head element was provided, add it to the document
 | 
						||
	if(!head) return;
 | 
						||
	head[head instanceof HTMLScriptElement ? "src" : "href"] = file_name;
 | 
						||
	document.head.append(head);
 | 
						||
}
 | 
						||
`, page_id }),
 | 
						||
		el("h4", t`Server-Side Rendering`),
 | 
						||
		el(code, { content: `
 | 
						||
// From docs/components/ireland.html.js - Server-side component implementation
 | 
						||
export function ireland({ src, exportName = "default", props = {} }) {
 | 
						||
	// Calculate relative path for imports
 | 
						||
	const path = "./"+relative(dir, src.pathname);
 | 
						||
 | 
						||
	// Generate unique ID for this component instance
 | 
						||
	const id = "ireland-" + generateComponentId(src);
 | 
						||
 | 
						||
	// Create placeholder element
 | 
						||
	const element = el.mark({ type: "later", name: ireland.name });
 | 
						||
 | 
						||
	// Import and render the component during SSR
 | 
						||
	queue(import(path).then(module => {
 | 
						||
	const component = module[exportName];
 | 
						||
	element.replaceWith(el(component, props, mark(id)));
 | 
						||
	}));
 | 
						||
 | 
						||
	// Register client-side hydration on first component
 | 
						||
	if(!componentsRegistry.size)
 | 
						||
	addEventListener("oneachrender", registerClientPart);
 | 
						||
 | 
						||
	// Store component info for client-side hydration
 | 
						||
	componentsRegistry.set(id, {
 | 
						||
	src,
 | 
						||
	path: dirFE+"/"+path.split("/").pop(),
 | 
						||
	exportName,
 | 
						||
	props,
 | 
						||
	});
 | 
						||
 | 
						||
	return element;
 | 
						||
}
 | 
						||
 | 
						||
// Register client-side resources
 | 
						||
function registerClientPart() {
 | 
						||
	// Process all component registrations
 | 
						||
	const todo = Array.from(componentsRegistry.entries())
 | 
						||
	.map(([ id, d ]) => {
 | 
						||
		// Copy the component source file to output directory
 | 
						||
		registerClientFile(d.src, {
 | 
						||
		folder: dirFE,
 | 
						||
		// Replace bare imports for browser compatibility
 | 
						||
		replacer(file) {
 | 
						||
			return file.replaceAll(
 | 
						||
				/ from "deka-dom-el(\/signals)?";/g,
 | 
						||
				\` from "./esm-with-signals.js";\`
 | 
						||
			);
 | 
						||
		}
 | 
						||
	});
 | 
						||
		return [ id, d ];
 | 
						||
	});
 | 
						||
 | 
						||
	// Serialize the component registry for client-side use
 | 
						||
	const store = JSON.stringify(JSON.stringify(todo));
 | 
						||
 | 
						||
	// Copy client-side scripts to output
 | 
						||
	registerClientFile(new URL("./ireland.js.js", import.meta.url));
 | 
						||
	registerClientFile(new URL("../../dist/esm-with-signals.js", import.meta.url), { folder: dirFE });
 | 
						||
 | 
						||
	// Add import map for package resolution
 | 
						||
	document.head.append(
 | 
						||
	el("script", { type: "importmap" }).append(\`
 | 
						||
{
 | 
						||
	"imports": {
 | 
						||
	"deka-dom-el": "./\${dirFE}/esm-with-signals.js",
 | 
						||
	"deka-dom-el/signals": "./\${dirFE}/esm-with-signals.js"
 | 
						||
	}
 | 
						||
}
 | 
						||
	\`.trim())
 | 
						||
	);
 | 
						||
 | 
						||
	// Add bootstrap script to load components
 | 
						||
	document.body.append(
 | 
						||
	el("script", { type: "module" }).append(\`
 | 
						||
import { loadIrelands } from "./ireland.js.js";
 | 
						||
loadIrelands(new Map(JSON.parse(\${store})));
 | 
						||
	\`.trim())
 | 
						||
	);
 | 
						||
}
 | 
						||
`, page_id }),
 | 
						||
		el("h4", t`Client-Side Hydration`),
 | 
						||
		el(code, { content: `
 | 
						||
// From docs/components/ireland.js.js - Client-side hydration
 | 
						||
import { el } from "./irelands/esm-with-signals.js";
 | 
						||
 | 
						||
export function loadIrelands(store) {
 | 
						||
	// Find all marked components in the DOM
 | 
						||
	document.body.querySelectorAll("[data-dde-mark]").forEach(ireland => {
 | 
						||
	const { ddeMark } = ireland.dataset;
 | 
						||
 | 
						||
	// Skip if this component isn't in our registry
 | 
						||
	if(!store.has(ddeMark)) return;
 | 
						||
 | 
						||
	// Get component information
 | 
						||
	const { path, exportName, props } = store.get(ddeMark);
 | 
						||
 | 
						||
	// Dynamically import the component module
 | 
						||
	import("./" + path).then(module => {
 | 
						||
		// Replace the server-rendered element with the client-side version
 | 
						||
		ireland.replaceWith(el(module[exportName], props));
 | 
						||
	});
 | 
						||
	});
 | 
						||
}
 | 
						||
`, page_id }),
 | 
						||
 | 
						||
		el(h3, t`Live Example`),
 | 
						||
			el("p").append(...T`
 | 
						||
				Here's a live example of an Ireland component showing a standard counter.
 | 
						||
				The component is defined in ${el("code", "docs/components/examples/ireland-test/counter.js")} and
 | 
						||
				rendered with the Ireland component system:
 | 
						||
			`),
 | 
						||
 | 
						||
			el(code, {
 | 
						||
				src: fileURL("./components/examples/ireland-test/counter.js"),
 | 
						||
				page_id
 | 
						||
			}),
 | 
						||
			el(ireland, {
 | 
						||
				src: fileURL("./components/examples/ireland-test/counter.js"),
 | 
						||
				exportName: "CounterStandard",
 | 
						||
				page_id
 | 
						||
			}),
 | 
						||
 | 
						||
			el("p").append(...T`
 | 
						||
				When the "Run Component" button is clicked, the component is loaded and rendered dynamically.
 | 
						||
				The counter state is maintained using signals, allowing for reactive updates as you click
 | 
						||
				the buttons to increment and decrement the value.
 | 
						||
			`),
 | 
						||
 | 
						||
		el(h3, t`Creating Your Own Components`),
 | 
						||
		el("p").append(...T`
 | 
						||
			To create components for use with the Ireland system, follow these guidelines:
 | 
						||
		`),
 | 
						||
 | 
						||
		el("ol").append(
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "Export a function:")} Components should be exported as named or default functions
 | 
						||
			`),
 | 
						||
			el("li").append(...T`
 | 
						||
 | 
						||
			`),
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "Accept props:")} Components should accept a props object, even if not using it
 | 
						||
			`),
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "Manage reactivity:")} Use signals for state management where appropriate
 | 
						||
			`),
 | 
						||
			el("li").append(...T`
 | 
						||
				${el("strong", "Handle cleanup:")} Include any necessary cleanup for event listeners or signals
 | 
						||
			`)
 | 
						||
		),
 | 
						||
 | 
						||
		el(h3, t`Practical Considerations and Limitations`),
 | 
						||
			el("p").append(...T`
 | 
						||
				When implementing Ireland components in real documentation, there are several important
 | 
						||
				considerations to keep in mind:
 | 
						||
			`),
 | 
						||
 | 
						||
			el("div", { className: "warning" }).append(
 | 
						||
				el("h4", t`Module Resolution and Bundling`),
 | 
						||
				el("p").append(...T`
 | 
						||
					The examples shown here use bare module specifiers like ${el("code", "import { el } from \"deka-dom-el\"")}
 | 
						||
					which aren't supported in all browsers without importmaps. In a production implementation, you would need to:
 | 
						||
				`),
 | 
						||
				el("ol").append(
 | 
						||
					el("li", t`Replace bare import paths with actual paths during the build process`),
 | 
						||
					el("li", t`Bundle component dependencies to avoid multiple requests`),
 | 
						||
					el("li", t`Ensure all module dependencies are properly resolved and copied to the output directory`)
 | 
						||
				),
 | 
						||
				el("p").append(...T`
 | 
						||
					In this documentation, we replace the paths with ${el("code", "./esm-with-signals.js")} and provide
 | 
						||
					a bundled version of the library, but more complex components might require a dedicated bundling step.
 | 
						||
				`)
 | 
						||
			),
 | 
						||
 | 
						||
			el("div", { className: "note" }).append(
 | 
						||
				el("h4", t`Component Dependencies`),
 | 
						||
				el("p").append(...T`
 | 
						||
					Real-world components typically depend on multiple modules and assets. The Ireland system would need
 | 
						||
					to be extended to:
 | 
						||
				`),
 | 
						||
				el("ul").append(
 | 
						||
					el("li", t`Detect and analyze all dependencies of a component`),
 | 
						||
					el("li", t`Bundle these dependencies together or ensure they're properly copied to the output directory`),
 | 
						||
					el("li", t`Handle non-JavaScript assets like CSS, images, or data files`)
 | 
						||
				)
 | 
						||
			),
 | 
						||
 | 
						||
			el(h3, t`Advanced Usage`),
 | 
						||
			el("p").append(...T`
 | 
						||
				The Ireland system can be extended in several ways to address these limitations:
 | 
						||
			`),
 | 
						||
 | 
						||
			el("ul").append(
 | 
						||
				el("li", t`Integrate with a bundler like esbuild, Rollup, or Webpack`),
 | 
						||
				el("li", t`Add props support for configuring components at runtime`),
 | 
						||
				el("li", t`Implement module caching to reduce network requests`),
 | 
						||
				el("li", t`Add code editing capabilities for interactive experimentation`),
 | 
						||
				el("li", t`Support TypeScript and other languages through transpilation`),
 | 
						||
				el("li", t`Implement state persistence between runs`)
 | 
						||
			),
 | 
						||
 | 
						||
			el("p").append(...T`
 | 
						||
				This documentation site itself is built using the techniques described here,
 | 
						||
				showcasing how dd<el> can be used to create both the documentation and
 | 
						||
				the interactive examples within it. The implementation here is simplified for clarity,
 | 
						||
				while a production-ready system would need to address the considerations above.
 | 
						||
			`)
 | 
						||
	);
 | 
						||
}
 |