mirror of
https://github.com/jaandrle/deka-dom-el
synced 2025-04-01 19:55:53 +02:00
⚡ irelands
This commit is contained in:
parent
b3356afa88
commit
57a5ff2dfe
@ -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"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user