mirror of
				https://github.com/jaandrle/deka-dom-el
				synced 2025-11-04 07:09:15 +01:00 
			
		
		
		
	⚡ irelands
This commit is contained in:
		@@ -4,7 +4,7 @@ echo("Building static documentation files…");
 | 
			
		||||
echo("Preparing…");
 | 
			
		||||
import { path_target, pages as pages_registered, styles, dispatchEvent, t } from "../docs/ssr.js";
 | 
			
		||||
import { createHTMl } from "./docs/jsdom.js";
 | 
			
		||||
import { register } from "../jsdom.js";
 | 
			
		||||
import { register, queue } from "../jsdom.js";
 | 
			
		||||
const pkg= s.cat("package.json").xargs(JSON.parse);
 | 
			
		||||
 | 
			
		||||
if(s.test("-d", path_target.root)){
 | 
			
		||||
@@ -31,6 +31,7 @@ for(const { id, info } of pages){
 | 
			
		||||
	serverDOM.document.body.append(
 | 
			
		||||
		el(page, { pkg, info }),
 | 
			
		||||
	);
 | 
			
		||||
	await queue();
 | 
			
		||||
 | 
			
		||||
	echo.use("-R", `Writing ${id}.html…`);
 | 
			
		||||
	dispatchEvent("oneachrender", document);
 | 
			
		||||
 
 | 
			
		||||
@@ -236,12 +236,11 @@ function registerClientPart(page_id){
 | 
			
		||||
		`),
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// Register our highlighting script to run after Shiki loads
 | 
			
		||||
	const scriptElement = el("script", { type: "module" });
 | 
			
		||||
 | 
			
		||||
	registerClientFile(
 | 
			
		||||
		new URL("./code.js.js", import.meta.url),
 | 
			
		||||
		scriptElement
 | 
			
		||||
		{
 | 
			
		||||
			head: el("script", { type: "module" }),
 | 
			
		||||
		}
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	is_registered[page_id]= true;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								docs/components/examples/ireland-test/counter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								docs/components/examples/ireland-test/counter.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import { el } from "deka-dom-el";
 | 
			
		||||
import { S } from "deka-dom-el/signals";
 | 
			
		||||
 | 
			
		||||
const className = "client-side-counter";
 | 
			
		||||
document.body.append(
 | 
			
		||||
	el("style").append(`
 | 
			
		||||
.${className} {
 | 
			
		||||
	border: 1px dashed #ccc;
 | 
			
		||||
	padding: 1em;
 | 
			
		||||
	margin: 1em;
 | 
			
		||||
}
 | 
			
		||||
		`.trim())
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export function CounterStandard() {
 | 
			
		||||
	// Create reactive state with a signal
 | 
			
		||||
	const count = S(0);
 | 
			
		||||
 | 
			
		||||
	// Create UI components that react to state changes
 | 
			
		||||
	return el("div", { className }).append(
 | 
			
		||||
		el("h4", "Client-Side Counter"),
 | 
			
		||||
		el("div", {
 | 
			
		||||
			// The textContent updates automatically when count changes
 | 
			
		||||
			textContent: S(() => `Count: ${count.get()}`),
 | 
			
		||||
		}),
 | 
			
		||||
		el("div", { className: "controls" }).append(
 | 
			
		||||
			el("button", {
 | 
			
		||||
				onclick: () => count.set(count.get() - 1),
 | 
			
		||||
				textContent: "-",
 | 
			
		||||
			}),
 | 
			
		||||
			el("button", {
 | 
			
		||||
				onclick: () => count.set(count.get() + 1),
 | 
			
		||||
				textContent: "+",
 | 
			
		||||
			})
 | 
			
		||||
		)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								docs/components/ireland.html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								docs/components/ireland.html.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
import { el, queue } from "deka-dom-el";
 | 
			
		||||
import { addEventListener, registerClientFile } from "../ssr.js";
 | 
			
		||||
import { relative } from "node:path";
 | 
			
		||||
 | 
			
		||||
const dir= new URL("./", import.meta.url).pathname;
 | 
			
		||||
const dirFE= "irelands";
 | 
			
		||||
// Track all component instances for client-side rehydration
 | 
			
		||||
const componentsRegistry = new Map();
 | 
			
		||||
/**
 | 
			
		||||
 * Creates a component that shows code and its runtime output
 | 
			
		||||
 * with server-side pre-rendering and client-side rehydration
 | 
			
		||||
 *
 | 
			
		||||
 * @param {object} attrs
 | 
			
		||||
 * @param {URL} attrs.src - Path to the file containing the component
 | 
			
		||||
 * @param {string} [attrs.exportName="default"] - Name of the export to use
 | 
			
		||||
 * @param {string} attrs.page_id - ID of the current page
 | 
			
		||||
 * @param {object} [attrs.props={}] - Props to pass to the component
 | 
			
		||||
 */
 | 
			
		||||
export function ireland({ src, exportName = "default", props = {} }) {
 | 
			
		||||
	// relative src against the current directory
 | 
			
		||||
	const path= "./"+relative(dir, src.pathname);
 | 
			
		||||
	const id = "ireland-" + generateComponentId(src);
 | 
			
		||||
	const element = el.mark({ type: "ireland", name: ireland.name });
 | 
			
		||||
	queue(import(path).then(module => {
 | 
			
		||||
		const component = module[exportName];
 | 
			
		||||
		element.replaceWith(el(component, props, mark(id)));
 | 
			
		||||
	}));
 | 
			
		||||
 | 
			
		||||
	if(!componentsRegistry.size)
 | 
			
		||||
		addEventListener("oneachrender", registerClientPart);
 | 
			
		||||
	componentsRegistry.set(id, {
 | 
			
		||||
		src,
 | 
			
		||||
		path: dirFE+"/"+path.split("/").pop(),
 | 
			
		||||
		exportName,
 | 
			
		||||
		props,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return element;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function registerClientPart(){
 | 
			
		||||
	const todo= Array.from(componentsRegistry.entries())
 | 
			
		||||
		.map(([ id, d ]) => {
 | 
			
		||||
			registerClientFile(d.src, {
 | 
			
		||||
				folder: dirFE,
 | 
			
		||||
				// not all browsers support importmap
 | 
			
		||||
				replacer(file){
 | 
			
		||||
					return file
 | 
			
		||||
						.replaceAll(/ from "deka-dom-el(\/signals)?";/g, ` from "./esm-with-signals.js";`);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			return [ id, d ];
 | 
			
		||||
		});
 | 
			
		||||
	const store = JSON.stringify(JSON.stringify(todo));
 | 
			
		||||
	registerClientFile(new URL("./ireland.js.js", import.meta.url));
 | 
			
		||||
	registerClientFile(new URL("../../dist/esm-with-signals.js", import.meta.url), { folder: dirFE });
 | 
			
		||||
	document.head.append(
 | 
			
		||||
		// not all browsers support importmap
 | 
			
		||||
		el("script", { type: "importmap" }).append(`
 | 
			
		||||
{
 | 
			
		||||
	"imports": {
 | 
			
		||||
		"deka-dom-el": "./${dirFE}/esm-with-signals.js",
 | 
			
		||||
		"deka-dom-el/signals": "./${dirFE}/esm-with-signals.js"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		`.trim())
 | 
			
		||||
	);
 | 
			
		||||
	document.body.append(
 | 
			
		||||
		el("script", { type: "module" }).append(`
 | 
			
		||||
import { loadIrelands } from "./ireland.js.js";
 | 
			
		||||
loadIrelands(new Map(JSON.parse(${store})));
 | 
			
		||||
		`.trim())
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
function mark(id) { return el=> el.dataset.ddeMark= id; }
 | 
			
		||||
const store_prev= new Map();
 | 
			
		||||
/** @param {URL} src */
 | 
			
		||||
function generateComponentId(src){
 | 
			
		||||
	const candidate= parseInt(relative((new URL("..", import.meta.url)).pathname, src.pathname)
 | 
			
		||||
		.split("")
 | 
			
		||||
		.map(ch=> ch.charCodeAt(0))
 | 
			
		||||
		.join(""), 10)
 | 
			
		||||
		.toString(36)
 | 
			
		||||
		.replace(/000+/g, "");
 | 
			
		||||
	const count= 1 + ( store_prev.get(candidate) || 0 );
 | 
			
		||||
	store_prev.set(candidate, count);
 | 
			
		||||
	return count.toString()+"-"+candidate;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								docs/components/ireland.js.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								docs/components/ireland.js.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
// not all browsers support importmaps
 | 
			
		||||
// import { el } from "deka-dom-el";
 | 
			
		||||
import { el } from "./irelands/esm-with-signals.js";
 | 
			
		||||
export function loadIrelands(store) {
 | 
			
		||||
	document.body.querySelectorAll("[data-dde-mark]").forEach(ireland => {
 | 
			
		||||
		const { ddeMark }= ireland.dataset;
 | 
			
		||||
		if(!store.has(ddeMark)) return;
 | 
			
		||||
		const { path, exportName, props }= store.get(ddeMark);
 | 
			
		||||
		import("./"+path).then(module => {
 | 
			
		||||
			ireland.replaceWith(el(module[exportName], props));
 | 
			
		||||
		})
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
@@ -104,6 +104,7 @@ export function page({ pkg, info }){
 | 
			
		||||
			el("li").append(...T`${el("strong", "Custom Elements")} — Building web components`),
 | 
			
		||||
			el("li").append(...T`${el("strong", "Debugging")} — Tools to help you build and fix your apps`),
 | 
			
		||||
			el("li").append(...T`${el("strong", "Extensions")} — Integrating third-party functionalities`),
 | 
			
		||||
			el("li").append(...T`${el("strong", "Ireland Components")} — Creating interactive demos with server-side pre-rendering`),
 | 
			
		||||
			el("li").append(...T`${el("strong", "SSR")} — Server-side rendering with DDE`)
 | 
			
		||||
		),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,10 @@ export function page({ pkg, info }){
 | 
			
		||||
	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 library authors.
 | 
			
		||||
				For regular users, this capability will hopefully be covered by third-party libraries or frameworks
 | 
			
		||||
				that provide simpler SSR integration using deka-dom-el.
 | 
			
		||||
				This part of the documentation is primarily intended for technical enthusiasts and documentation
 | 
			
		||||
				authors. 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 deka-dom-el.
 | 
			
		||||
			`)
 | 
			
		||||
		),
 | 
			
		||||
		el("p").append(...T`
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										360
									
								
								docs/p10-ireland.html.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										360
									
								
								docs/p10-ireland.html.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,360 @@
 | 
			
		||||
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 documentation
 | 
			
		||||
				authors. 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 deka-dom-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 deka-dom-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: "ireland", 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("strong", "Return a DOM element:")} The function should return a valid DOM element created with ${el("code", "el()")}
 | 
			
		||||
			`),
 | 
			
		||||
			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 deka-dom-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.
 | 
			
		||||
			`)
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								docs/ssr.js
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								docs/ssr.js
									
									
									
									
									
								
							@@ -13,16 +13,23 @@ export let pages= [];
 | 
			
		||||
 * @typedef registerClientFile
 | 
			
		||||
 * @type {function}
 | 
			
		||||
 * @param {URL} url
 | 
			
		||||
 * @param {HTMLScriptElement|HTMLLinkElement} [element_head]
 | 
			
		||||
 * @param {Object} [options]
 | 
			
		||||
 * @param {HTMLScriptElement|HTMLLinkElement} [options.head]
 | 
			
		||||
 * @param {string} [options.folder]
 | 
			
		||||
 * @param {function} [options.replacer]
 | 
			
		||||
 * */
 | 
			
		||||
export function registerClientFile(url, element_head){
 | 
			
		||||
export function registerClientFile(url, { head, folder= "", replacer }= {}){
 | 
			
		||||
	if(folder && !folder.endsWith("/")) folder+= "/";
 | 
			
		||||
	const file_name= url.pathname.split("/").pop();
 | 
			
		||||
	s.cat(url).to(path_target.root+file_name);
 | 
			
		||||
	s.mkdir("-p", path_target.root+folder);
 | 
			
		||||
	let content= s.cat(url)
 | 
			
		||||
	if(replacer) content= s.echo(replacer(content.toString()));
 | 
			
		||||
	content.to(path_target.root+folder+file_name);
 | 
			
		||||
 | 
			
		||||
	if(!element_head) return;
 | 
			
		||||
	element_head[element_head instanceof HTMLScriptElement ? "src" : "href"]= file_name;
 | 
			
		||||
	if(!head) return;
 | 
			
		||||
	head[head instanceof HTMLScriptElement ? "src" : "href"]= file_name;
 | 
			
		||||
	document.head.append(
 | 
			
		||||
		element_head
 | 
			
		||||
		head
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								index.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -72,6 +72,17 @@ export function assignAttribute<El extends SupportedElement, ATT extends keyof E
 | 
			
		||||
): ElementAttributes<El>[ATT]
 | 
			
		||||
 | 
			
		||||
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
 | 
			
		||||
export namespace el {
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a marker comment for elements
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {{ type: "component"|"reactive"|"ireland"|"later", name?: string, host?: "this"|"parentElement" }} attrs - Marker attributes
 | 
			
		||||
   * @param {boolean} [is_open=false] - Whether the marker is open-ended
 | 
			
		||||
   * @returns {Comment} Comment node marker
 | 
			
		||||
   */
 | 
			
		||||
  export function mark(attrs: { type: "component"|"reactive"|"ireland"|"later", name?: string, host?: "this"|"parentElement" }, is_open?: boolean): Comment;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function el<
 | 
			
		||||
	A extends ddeComponentAttributes,
 | 
			
		||||
	EL extends SupportedElement | ddeDocumentFragment
 | 
			
		||||
@@ -251,6 +262,27 @@ export function customElementWithDDE<EL extends (new ()=> HTMLElement)>(custom_e
 | 
			
		||||
export function lifecyclesToEvents<EL extends (new ()=> HTMLElement)>(custom_element: EL): EL
 | 
			
		||||
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is used primarly for server side rendering. To be sure that all async operations
 | 
			
		||||
 * are finished before the page is sent to the client.
 | 
			
		||||
 * ```
 | 
			
		||||
 *	// on component
 | 
			
		||||
 *	function component(){
 | 
			
		||||
 *		…
 | 
			
		||||
 *		queue(fetch(...).then(...));
 | 
			
		||||
 *	}
 | 
			
		||||
 *
 | 
			
		||||
 * // building the page
 | 
			
		||||
 * async function build(){
 | 
			
		||||
 *		const { component }= await import("./component.js");
 | 
			
		||||
 *		document.body.append(el(component));
 | 
			
		||||
 *		await queue();
 | 
			
		||||
 *		retutn document.body.innerHTML;
 | 
			
		||||
 *	}
 | 
			
		||||
 * ```
 | 
			
		||||
 * */
 | 
			
		||||
export function queue(promise?: Promise<unknown>): Promise<unknown>;
 | 
			
		||||
 | 
			
		||||
/* TypeScript MEH */
 | 
			
		||||
declare global{
 | 
			
		||||
	type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								jsdom.js
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								jsdom.js
									
									
									
									
									
								
							@@ -17,7 +17,7 @@ env.setDeleteAttr= function(obj, prop, value){
 | 
			
		||||
	if(value) return obj.setAttribute(prop, "");
 | 
			
		||||
	obj.removeAttribute(prop);
 | 
			
		||||
};
 | 
			
		||||
const keys= { H: "HTMLElement", S: "SVGElement", F: "DocumentFragment", M: "MutationObserver", D: "document" };
 | 
			
		||||
const keys= { H: "HTMLElement", S: "SVGElement", F: "DocumentFragment", D: "document" };
 | 
			
		||||
let env_bk= {};
 | 
			
		||||
let dom_last;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -100,5 +100,6 @@
 | 
			
		||||
		"jshint": "~2.13",
 | 
			
		||||
		"nodejsscript": "^1.0.2",
 | 
			
		||||
		"size-limit-node-esbuild": "~0.3"
 | 
			
		||||
	}
 | 
			
		||||
	},
 | 
			
		||||
	"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user