Adding hotfixes for packages
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Fabian Stamm
2020-10-14 02:56:11 +02:00
parent 46d8f8b289
commit 1b2d85eeef
95 changed files with 12467 additions and 2 deletions

View File

@ -0,0 +1,37 @@
import type {
NodePropsType,
ComponentFunctionType,
NullableChildType,
} from '../types.ts';
import { NODE_TYPE } from '../constants.ts';
import { FragmentNode } from './FragmentNode.ts';
import { Node } from './Node.ts';
import { normalizeChildren } from './utils/normalizeChildren.ts';
export class ComponentNode extends Node {
type = NODE_TYPE.COMPONENT;
constructor(
public component: ComponentFunctionType,
public props: NodePropsType,
children: NullableChildType[],
) {
super(children);
}
async render(): Promise<string | any[]> {
return [].concat((await this.renderComponent()) as any).join('');
}
async renderComponent() {
const child = await this.component(this.props, this.children);
const children = normalizeChildren(
Array.isArray(child) ? child : [child],
);
if (children.length === 1) {
return children[0].render();
} else if (children.length > 1) {
return new FragmentNode(children).render();
}
}
}

View File

@ -0,0 +1,91 @@
import { NODE_TYPE } from '../constants.ts';
import type { NodePropsType, NullableChildType } from '../types.ts';
import { Node } from './Node.ts';
import { doubleQuoteEncode } from './utils/htmlEncode.ts';
const ELEMENT_PROP = {
INNER_HTML: 'innerHTML',
};
// List taken from http://w3c.github.io/html-reference/syntax.html
const VOID_ELEMENTS = new Set<string>([
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr',
]);
export class ElementNode extends Node {
type = NODE_TYPE.ELEMENT;
constructor(
public name: string,
public props: NodePropsType,
children: NullableChildType[],
) {
super(children);
}
async render(): Promise<string | any[]> {
const renderedProps = this.propsToHTML();
const renderedChildren =
typeof this.props[ELEMENT_PROP.INNER_HTML] === 'string'
? this.props[ELEMENT_PROP.INNER_HTML]
: (await this.renderChildren()).join('');
return renderedChildren || !VOID_ELEMENTS.has(this.name)
? `<${this.name}${renderedProps}>${renderedChildren || ''}</${
this.name
}>`
: `<${this.name}${renderedProps} />`;
}
private getValidProps() {
const props = this.props;
return Object.keys(this.props).filter((key) => {
if (key === ELEMENT_PROP.INNER_HTML) {
return false;
}
const val = props[key];
return (
typeof val === 'string' ||
typeof val === 'number' ||
val === true
);
});
}
private propsToHTML(): string {
const keys = this.getValidProps();
if (!keys.length) {
return '';
}
const props = this.props;
const pairs = keys.map((key) => {
if (!/^[a-zA-Z0-9-:\._]+$/.test(key)) {
throw new Error(`Invalid attribute name format ${key}`);
}
const val = props[key];
// https://html.spec.whatwg.org/multipage/dom.html#attributes
return val === true || val === ''
? key
: `${key}="${doubleQuoteEncode(val.toString())}"`;
});
return ` ${pairs.join(' ')}`;
}
}

View File

@ -0,0 +1,15 @@
import { NODE_TYPE } from '../constants.ts';
import type { ChildNodeType } from '../types.ts';
import { Node } from './Node.ts';
export class FragmentNode extends Node {
type = NODE_TYPE.FRAGMENT;
constructor(children: ChildNodeType[]) {
super(children);
}
render() {
return this.renderChildren();
}
}

29
jsx-html/node/Node.ts Normal file
View File

@ -0,0 +1,29 @@
import type { NODE_TYPE } from '../constants.ts';
import type { NullableChildType } from '../types.ts';
import { normalizeChildren } from './utils/normalizeChildren.ts';
export abstract class Node {
abstract type: NODE_TYPE;
constructor(public children: NullableChildType[]) {}
abstract async render(): Promise<string | any[]>;
async renderChildren() {
const result: string[] = [];
const children = normalizeChildren(this.children);
for (const child of children) {
const renderedChild = await child.render();
if (renderedChild) {
if (Array.isArray(renderedChild)) {
renderedChild.forEach(
(subchild) => subchild && result.push(subchild),
);
} else {
result.push(renderedChild);
}
}
}
return result;
}
}

12
jsx-html/node/TextNode.ts Normal file
View File

@ -0,0 +1,12 @@
import { NODE_TYPE } from '../constants.ts';
import { htmlEncode } from './utils/htmlEncode.ts';
export class TextNode {
type = NODE_TYPE.TEXT;
constructor(public text: string) {}
async render(): Promise<string | any[]> {
return htmlEncode(this.text);
}
}

View File

@ -0,0 +1,13 @@
export function doubleQuoteEncode(text: string): string {
return text
.replace(/"/g, '&quot;')
}
export function htmlEncode(text: string): string {
return doubleQuoteEncode(text
.replace(/&/g, '&amp;')
.replace(/\//g, '&#x2F;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#39;'));
}

View File

@ -0,0 +1,31 @@
import type { NullableChildType, ChildNodeType } from '../../types.ts';
import { TextNode } from '../TextNode.ts';
import { NODE_TYPE } from '../../constants.ts';
export function normalizeChildren(
children: NullableChildType[],
): ChildNodeType[] {
const result: any[] = [];
for (const child of children) {
if (child && typeof child !== 'boolean') {
if (typeof child === 'string' || typeof child === 'number') {
result.push(new TextNode(`${child}`));
} else if (Array.isArray(child)) {
normalizeChildren(child).forEach((normalized) =>
result.push(normalized),
);
} else if (
child.type === NODE_TYPE.ELEMENT ||
child.type === NODE_TYPE.TEXT ||
child.type === NODE_TYPE.COMPONENT
) {
result.push(child);
} else {
throw new TypeError(`Unrecognized node type: ${typeof child}`);
}
}
}
return result;
}