initial commit (it all starts here...)
This commit is contained in:
commit
ac7da8cc6d
36 changed files with 4550 additions and 0 deletions
85
src/render.ts
Normal file
85
src/render.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Element, HTMLElement } from "./component";
|
||||
|
||||
const HTML_ESCAPES: { [char: string]: string } = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
};
|
||||
|
||||
const escapeHtml = (text: string): string =>
|
||||
text.replace(/[&<>]/g, (t) => HTML_ESCAPES[t]);
|
||||
|
||||
const escapeAttr = (text: string): string => text.replace(/"/g, """);
|
||||
|
||||
const renderElement = (elem: Element): string => {
|
||||
// Ignore null and true/false to support nicer JSX conditional syntax with
|
||||
// &&, ||, !! operators.
|
||||
if (elem == null || typeof elem === "boolean") {
|
||||
return "";
|
||||
}
|
||||
if (typeof elem === "string") {
|
||||
return escapeHtml(elem);
|
||||
}
|
||||
|
||||
let output = "";
|
||||
output += startTag(elem);
|
||||
for (const child of elem.children) {
|
||||
output += renderElement(child);
|
||||
}
|
||||
output += endTag(elem);
|
||||
return output;
|
||||
};
|
||||
|
||||
const startTag = (elem: HTMLElement): string => {
|
||||
let output = `<${escapeHtml(elem.tag)}`;
|
||||
|
||||
for (const [attrName, attrValue] of Object.entries(elem.attributes)) {
|
||||
// Handle boolean attributes with a false value by not outputting the
|
||||
// attribute at all.
|
||||
if (attrValue === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let normalizedAttrName = escapeHtml(attrName.toLowerCase());
|
||||
if (normalizedAttrName === "classname") {
|
||||
normalizedAttrName = "class";
|
||||
}
|
||||
if (attrValue === true) {
|
||||
output += ` ${normalizedAttrName}=""`;
|
||||
} else {
|
||||
output += ` ${normalizedAttrName}="${escapeAttr(
|
||||
attrValue.toString()
|
||||
)}"`;
|
||||
}
|
||||
}
|
||||
|
||||
output += ">";
|
||||
return output;
|
||||
};
|
||||
|
||||
const endTag = (elem: HTMLElement): string => `</${escapeHtml(elem.tag)}>`;
|
||||
|
||||
/**
|
||||
* Render a complete HTML page from an HTMLElement. Note that the root element
|
||||
* must represent a valid HTML tag.
|
||||
*
|
||||
* @param rootElem HTML element representing the root of the document.
|
||||
*
|
||||
* @return Fully rendered HTML document as a string.
|
||||
*/
|
||||
export const renderPage = (rootElem: HTMLElement): string => {
|
||||
if (rootElem.tag.toLowerCase() !== "html") {
|
||||
throw new Error(
|
||||
`attempted to render page with non-HTML root element ${rootElem.tag}`
|
||||
);
|
||||
}
|
||||
|
||||
let output = "<!DOCTYPE html>";
|
||||
output += renderElement(rootElem);
|
||||
return output;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue