1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-07-01 12:22:15 +02:00

🔤 UI/UX/wording

This commit is contained in:
2025-03-07 10:40:57 +01:00
parent 57a5ff2dfe
commit 7f4787d704
27 changed files with 514 additions and 359 deletions

View File

@ -1,28 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#333333" />
<stop offset="100%" stop-color="#222222" />
</linearGradient>
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#e32c2c" />
<stop offset="100%" stop-color="#ff5252" />
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="0.5" stdDeviation="0.5" flood-color="#000" flood-opacity="0.3"/>
</filter>
</defs>
<!-- Square background with rounded corners -->
<rect x="2" y="2" width="28" height="28" rx="4" ry="4" fill="url(#bgGradient)" />
<!-- Subtle code brackets as background element -->
<g opacity="0.15" fill="#fff">
<path d="M10,7.5 L6.25,16 L10,24.5" stroke="#fff" stroke-width="1" fill="none"/>
<path d="M22,7.5 L25.75,16 L22,24.5" stroke="#fff" stroke-width="1" fill="none"/>
</g>
<!-- lowercase dde letters -->
<text x="16" y="21" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="14" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#333333" />
<stop offset="100%" stop-color="#222222" />
</linearGradient>
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#e32c2c" />
<stop offset="100%" stop-color="#ff5252" />
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="0.5" stdDeviation="0.5" flood-color="#000" flood-opacity="0.3"/>
</filter>
</defs>
<!-- Square background with rounded corners -->
<rect x="2" y="2" width="28" height="28" rx="4" ry="4" fill="url(#bgGradient)" />
<!-- Subtle code brackets as background element -->
<g opacity="0.15" fill="#fff">
<path d="M10,7.5 L6.25,16 L10,24.5" stroke="#fff" stroke-width="1" fill="none"/>
<path d="M22,7.5 L25.75,16 L22,24.5" stroke="#fff" stroke-width="1" fill="none"/>
</g>
<!-- lowercase dde letters -->
<text x="16" y="21" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="14" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,33 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<!-- Gradients and effects -->
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#333333" />
<stop offset="100%" stop-color="#222222" />
</linearGradient>
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#e32c2c" />
<stop offset="100%" stop-color="#ff5252" />
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.3"/>
</filter>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<!-- Square background with rounded corners -->
<rect x="18" y="18" width="220" height="220" rx="20" ry="20" fill="url(#bgGradient)" />
<!-- Subtle code brackets as background element -->
<g opacity="0.15" fill="#fff" filter="url(#glow)">
<path d="M80,60 L50,128 L80,196" stroke="#fff" stroke-width="8" fill="none"/>
<path d="M176,60 L206,128 L176,196" stroke="#fff" stroke-width="8" fill="none"/>
</g>
<!-- lowercase dde letters with shadow effect -->
<text x="128" y="154" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="100" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
<!-- Gradients and effects -->
<defs>
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#333333" />
<stop offset="100%" stop-color="#222222" />
</linearGradient>
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#e32c2c" />
<stop offset="100%" stop-color="#ff5252" />
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.3"/>
</filter>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
</defs>
<!-- Square background with rounded corners -->
<rect x="18" y="18" width="220" height="220" rx="20" ry="20" fill="url(#bgGradient)" />
<!-- Subtle code brackets as background element -->
<g opacity="0.15" fill="#fff" filter="url(#glow)">
<path d="M80,60 L50,128 L80,196" stroke="#fff" stroke-width="8" fill="none"/>
<path d="M176,60 L206,128 L176,196" stroke="#fff" stroke-width="8" fill="none"/>
</g>
<!-- lowercase dde letters with shadow effect -->
<text x="128" y="154" text-anchor="middle" font-family="'Fira Code', 'JetBrains Mono', 'Source Code Pro', 'SF Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace" font-size="100" font-weight="bold" fill="url(#textGradient)" filter="url(#shadow)">dde</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -2,9 +2,9 @@ import { el } from "deka-dom-el";
// Create element with properties
const button = el("button", {
textContent: "Click me",
className: "primary",
disabled: true
textContent: "Click me",
className: "primary",
disabled: true
});
// Shorter and more expressive

View File

@ -6,12 +6,12 @@ button.disabled = true;
// Or using Object.assign()
const button2 = Object.assign(
document.createElement('button'),
{
textContent: "Click me",
className: "primary",
disabled: true
}
document.createElement('button'),
{
textContent: "Click me",
className: "primary",
disabled: true
}
);
// Add to DOM

View File

@ -2,7 +2,7 @@ import { el, on } from "deka-dom-el";
// Third approach - append with on addon
el("button", {
textContent: "click me"
textContent: "click me"
}).append(
on("click", (e) => console.log("Clicked!", e))
on("click", (e) => console.log("Clicked!", e))
);

View File

@ -2,6 +2,6 @@ import { el } from "deka-dom-el";
// Using events with HTML attribute style
el("button", {
textContent: "click me",
"=onclick": "console.log(event)"
textContent: "click me",
"=onclick": "console.log(event)"
});

View File

@ -2,7 +2,7 @@ import { el, on } from "deka-dom-el";
// Using events as addons - chainable approach
el("button", {
textContent: "click me",
},
on("click", (e) => console.log("Clicked!", e))
textContent: "click me",
},
on("click", (e) => console.log("Clicked!", e))
);

View File

@ -2,6 +2,6 @@ import { el } from "deka-dom-el";
// Using events with property assignment
el("button", {
textContent: "click me",
onclick: console.log
textContent: "click me",
onclick: console.log
});

View File

@ -4,11 +4,11 @@ const button = document.querySelector('button');
let count = 0;
button.addEventListener('click', () => {
count++;
document.querySelector('p').textContent =
'Clicked ' + count + ' times';
count++;
document.querySelector('p').textContent =
'Clicked ' + count + ' times';
if (count > 10) {
button.disabled = true;
}
if (count > 10) {
button.disabled = true;
}
});

View File

@ -4,11 +4,11 @@ const count = S(0);
// 2. React to state changes
S.on(count, value => {
updateUI(value);
if (value > 10) disableButton();
updateUI(value);
if (value > 10) disableButton();
});
// 3. Update state on events
button.addEventListener('click', () => {
count.set(count.get() + 1);
count.set(count.get() + 1);
});

View File

@ -3,28 +3,28 @@ import { S } from "deka-dom-el/signals";
// A HelloWorld component using the 3PS pattern
function HelloWorld({ emoji = "🚀" }) {
// PART 1: Create reactive state
const clicks = S(0);
return el().append(
// PART 2: Bind state to UI elements
el("p", {
className: "greeting",
// This paragraph automatically updates when clicks changes
textContent: S(() => `Hello World ${emoji.repeat(clicks.get())}`)
}),
// PART 3: Update state in response to events
el("button", {
type: "button",
textContent: "Add emoji",
// When clicked, update the state
onclick: () => clicks.set(clicks.get() + 1)
})
);
// PART 1: Create reactive state
const clicks = S(0);
return el().append(
// PART 2: Bind state to UI elements
el("p", {
className: "greeting",
// This paragraph automatically updates when clicks changes
textContent: S(() => `Hello World ${emoji.repeat(clicks.get())}`)
}),
// PART 3: Update state in response to events
el("button", {
type: "button",
textContent: "Add emoji",
// When clicked, update the state
onclick: () => clicks.set(clicks.get() + 1)
})
);
}
// Use the component in your app
document.body.append(
el(HelloWorld, { emoji: "🎉" })
el(HelloWorld, { emoji: "🎉" })
);

View File

@ -2,13 +2,13 @@ import { scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function customSignalLogic() {
// Create an isolated scope for a specific operation
scope.push(); // Start new scope
// Create an isolated scope for a specific operation
scope.push(); // Start new scope
// These signals are in the new scope
const isolatedCount = S(0);
const isolatedDerived = S(() => isolatedCount.get() * 2);
// These signals are in the new scope
const isolatedCount = S(0);
const isolatedDerived = S(() => isolatedCount.get() * 2);
// Clean up by returning to previous scope
scope.pop();
// Clean up by returning to previous scope
scope.pop();
}

View File

@ -2,44 +2,44 @@ import { el, scope } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
function CounterWithIsolatedTimer() {
const { host } = scope;
const { host } = scope;
// Main component state
const count = S(0);
// Main component state
const count = S(0);
// Create a timer in an isolated scope
scope.isolate(() => {
// These subscriptions won't be tied to the component lifecycle
// They would continue to run even if the component was removed
const timer = S(0);
// Create a timer in an isolated scope
scope.isolate(() => {
// These subscriptions won't be tied to the component lifecycle
// They would continue to run even if the component was removed
const timer = S(0);
// Not recommended for real applications!
// Just demonstrating scope isolation
setInterval(() => {
timer.set(timer.get() + 1);
console.log(`Timer: ${timer.get()}`);
}, 1000);
});
// Not recommended for real applications!
// Just demonstrating scope isolation
setInterval(() => {
timer.set(timer.get() + 1);
console.log(`Timer: ${timer.get()}`);
}, 1000);
});
// Normal component functionality within main scope
function increment() {
count.set(count.get() + 1);
}
// Normal component functionality within main scope
function increment() {
count.set(count.get() + 1);
}
return el("div").append(
el("p").append(
"Count: ",
el("#text", S(() => count.get()))
),
el("button", {
textContent: "Increment",
onclick: increment
}),
el("p", "An isolated timer runs in console")
);
return el("div").append(
el("p").append(
"Count: ",
el("#text", S(() => count.get()))
),
el("button", {
textContent: "Increment",
onclick: increment
}),
el("p", "An isolated timer runs in console")
);
}
// Usage
document.body.append(
el(CounterWithIsolatedTimer)
el(CounterWithIsolatedTimer)
);

View File

@ -20,7 +20,7 @@ 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 });
const element = el.mark({ type: "later", name: ireland.name });
queue(import(path).then(module => {
const component = module[exportName];
element.replaceWith(el(component, props, mark(id)));

View File

@ -104,7 +104,8 @@ 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", "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`
@ -112,4 +113,4 @@ export function page({ pkg, info }){
Let's get started with the basics of creating elements!
`),
);
}
}

View File

@ -55,21 +55,21 @@ export function page({ pkg, info }){
el(MyComponent);
function MyComponent() {
// 2. access the host element
const { host } = scope;
  // 2. access the host element
  const { host } = scope;
// 3. Add behavior to host
host(
on.click(handleClick)
);
  // 3. Add behavior to host
  host(
  on.click(handleClick)
  );
// 4. Return the host element
return el("div", {
className: "my-component"
}).append(
el("h2", "Title"),
el("p", "Content")
);
  // 4. Return the host element
  return el("div", {
  className: "my-component"
  }).append(
  el("h2", "Title"),
  el("p", "Content")
  );
}
`.trim()))
),
@ -118,9 +118,9 @@ function MyComponent() {
3. Component interactions happen
4. Component removed from DOM → disconnected event
5. Automatic cleanup of:
- Event listeners
- Signal subscriptions
- Custom cleanup code
  - Event listeners
  - Signal subscriptions
  - Custom cleanup code
`))
),
el(example, { src: fileURL("./components/examples/scopes/cleaning.js"), page_id }),
@ -167,6 +167,9 @@ function MyComponent() {
el("li").append(...T`
${el("strong", "Capture host early:")} Use ${el("code", "const { host } = scope")} at component start
`),
el("li").append(...T`
${el("strong", "Define signals as constants:")} ${el("code", "const counter = S(0);")}
`),
el("li").append(...T`
${el("strong", "Prefer declarative patterns:")} Use signals to drive UI updates rather than manual DOM manipulation
`),

View File

@ -202,15 +202,14 @@ export function page({ pkg, info }){
el("h4", t`Shadow DOM Encapsulation`),
el("pre").append(el("code", `
<my-custom-element>
  ┌─────────────────────────┐
    #shadow-root
┌─────────────────────────┐
#shadow-root
Created with DDE:
┌──────────────────┐
<div>
<h2>Title</h2>
<p>Content</p>
      Created with DDE:
    ┌──────────────────┐
      <div>
       <h2>Title</h2>
       <p>Content</p>
`))
),
el(example, { src: fileURL("./components/examples/customElement/shadowRoot.js"), page_id }),

View File

@ -42,8 +42,8 @@ export function page({ pkg, info }){
// Basic structure of an addon
function myAddon(config) {
return function(element) {
// Apply functionality to element
element.dataset.myAddon = config.option;
// Apply functionality to element
element.dataset.myAddon = config.option;
};
}
@ -67,18 +67,15 @@ el("div", { id: "example" }, myAddon({ option: "value" }));
// Third-party library addon with proper cleanup
function externalLibraryAddon(config, signal) {
return function(element) {
// Get an abort signal that triggers on element disconnection
const signal = on.disconnectedAsAbort(element);
// Initialize the third-party library
const instance = new ExternalLibrary(element, config);
// Initialize the third-party library
const instance = new ExternalLibrary(element, config);
// Set up cleanup when the element is removed
signal.addEventListener('abort', () => {
instance.destroy();
});
// Set up cleanup when the element is removed
signal.addEventListener('abort', () => {
instance.destroy();
});
return element;
return element;
};
}
// dde component
@ -157,8 +154,8 @@ function createEnhancedSignal(initialValue) {
// Return the original signal with added methods
return Object.assign(signal, {
increment,
decrement
increment,
decrement
});
}
@ -240,10 +237,12 @@ console.log(doubled.get()); // 10`, page_id }),
el("h4", t`Common Extension Pitfalls`),
el("dl").append(
el("dt", t`Leaking event listeners or resources`),
el("dd", t`Always use AbortSignal-based cleanup to automatically remove listeners when elements are disconnected`),
el("dd", t`Always use AbortSignal-based cleanup to automatically remove listeners when elements
are disconnected`),
el("dt", t`Tight coupling with library internals`),
el("dd", t`Focus on standard DOM APIs and clean interfaces rather than depending on deka-dom-el implementation details`),
el("dd", t`Focus on standard DOM APIs and clean interfaces rather than depending on deka-dom-el
implementation details`),
el("dt", t`Mutating element prototypes`),
el("dd", t`Prefer compositional approaches with addons over modifying element prototypes`),

View File

@ -18,10 +18,11 @@ 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 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.
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
deka-dom-el.
`)
),
el("p").append(...T`

View File

@ -2,7 +2,8 @@ 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.`,
description: t`Creating live, interactive component examples in documentation with server-side
rendering and client-side hydration.`,
};
import { el } from "deka-dom-el";
@ -19,10 +20,11 @@ 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 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.
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
deka-dom-el.
`)
),
@ -61,14 +63,15 @@ export function page({ pkg, info }){
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")}.
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",
src: fileURL("./components/examples/path/to/component.js"),
exportName: "NamedExport", // optional, defaults to "default",
})`, page_id }),
el("p").append(...T`
@ -96,27 +99,27 @@ 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");
// 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
@ -126,107 +129,107 @@ dispatchEvent("onssrend");
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);
// Ensure folder path ends with a slash
if(folder && !folder.endsWith("/")) folder += "/";
// 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);
// 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)));
}));
// Calculate relative path for imports
const path = "./"+relative(dir, src.pathname);
// 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,
});
// Generate unique ID for this component instance
const id = "ireland-" + generateComponentId(src);
return element;
// 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(\`
// 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"
}
"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(\`
\`.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())
);
\`.trim())
);
}
`, page_id }),
el("h4", t`Client-Side Hydration`),
@ -235,22 +238,22 @@ loadIrelands(new Map(JSON.parse(\${store})));
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));
});
});
// 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 }),
@ -287,7 +290,7 @@ export function loadIrelands(store) {
${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