The vanilla alternative for writing JSX-based React applications
git clone git://soophie.de/figure
6
```javascript7
import figure from '...';9
const { html, dyn } = figure({ createElement });9
// initialize figure10
const { dist } = figure({ createElement });11
const global = createContext({});12
// add your components to a bundle13
const ElementBundle = {14
button: Button,15
};13
function App() {14
const [name, setName] = useState('world');17
// add your bundles to the dictionary18
const html = dist({19
el: ElementBundle,20
});16
const context = {};22
function App() {23
return html`24
<main>25
<el:button26
type="primary"27
on:click=${() => console.log('It works!')}28
>29
<span>Click me</span>30
</el:button>31
</main>32
`;33
}35
function Button() {36
return html`19
${dyn(global.Provider, { value: context }), html`20
<h1>Hello ${name}!</h1>21
<p>Some description text.</p>22
`}37
<button38
on:click=${props?.onClick}39
class="btn btn--${props?.type}"40
>41
${props?.children}42
</button>43
`;44
}
1
import { Options, Props, ReactElement, ReactFunction, Refs, Slices, Values } from './types.ts';1
import { Options, Props, ReactElement, ReactFunction, Refs, Slices, Values, Dict } from './types.ts';3
export default function figure({ createElement }: Options) {
8
let count = 0;10
/**11
* Converts the template literal HTML syntax into React elements.12
* @param {Slices} slices - The template literal slices13
* @param {Values} values - The template literal values14
* @return {ReactElement[]} The converted HTML as React elements.11
* Returns the a function for rendering HTML.12
* @param {Dict} dict - The dictionary for resolving React components.13
* @return {Function} The function for rendering HTML.14
*/16
function html(slices: Slices, ...values: Values): ReactElement[] {17
const [html, refs] = compose(slices, values);18
let dom;19
try {20
dom = parser.parseFromString(html, 'text/html');21
}22
catch (error) {23
console.error(error);24
throw 'Invalid DOM structure!';15
function dict(dict?: Dict): (slices: Slices, ...values: Values) => ReactElement[] {17
/**18
* Converts the template literal HTML syntax into React elements.19
* @param {Slices} slices - The template literal slices20
* @param {Values} values - The template literal values21
* @return {ReactElement[]} The converted HTML as React elements.22
*/23
function html(slices: Slices, ...values: Values): ReactElement[] {24
const [html, refs] = compose(slices, values);25
let dom;26
try {27
dom = parser.parseFromString(html, 'text/html');28
}29
catch (error) {30
console.error(error);31
throw 'Invalid DOM structure!';32
}33
// collect all nodes from head and body34
const nodes = [...dom.head.childNodes, ...dom.body.childNodes];35
return nodes.map((node) => render(node, refs, dict ?? {}));36
}26
// collect all nodes from head and body27
const nodes = [...dom.head.childNodes, ...dom.body.childNodes];28
return nodes.map((node) => render(node, refs));38
return html;39
}41
/**
99
* @param {Refs} refs - The values mapped to there references100
* @return {ReactElement[]} The converted HTML node as React element101
*/92
function render(node: Node, refs: Refs): ReactElement[] {102
function render(node: Node, refs: Refs, dict: Dict): ReactElement[] {103
if (node.nodeType === Node.TEXT_NODE) {104
const text = node as Text;105
if (text.textContent == null) {
113
return [];114
}115
const element = node as HTMLElement;106
const tag = element.tagName;116
const tag = element.tagName.toLowerCase();117
const props: Props = {};118
// iterate over each attribute and add it to the props119
for (const attribute of element.attributes) {
167
}168
const children: ReactElement[] = [];169
// recursively render all child nodes160
(node.childNodes ?? []).forEach((child) => children.push(...render(child, refs)));161
return [createElement(tag, props, ...children)];162
}164
/**165
* Creates a dynamic component with its own state.166
* Should be used if the component is statefull (contains hooks).167
*168
* @param {ReactFunction} element - The string containing references169
* @param {Props} props - The component properties170
* @param {ReactElement[]} children - The child elements171
* @return {ReactElement} The created React component172
*/173
function dyn(element: ReactFunction, props?: Props, children?: ReactElement[]): ReactElement {174
return createElement(element, props, children);170
(node.childNodes ?? []).forEach((child) => children.push(...render(child, refs, dict)));171
const domain = tag.split(':');172
// look up tag name in dictionary173
// deno-lint-ignore no-explicit-any174
const component: ReactFunction | undefined = domain.reduce((dict: any, level) => {175
return dict?.[level];176
}, dict);177
// use React component or tag name178
return [createElement(component ?? tag, props, ...children)];179
}177
return { html, dyn };181
return { dict };182
}
1
export type ReactElement = unknown;2
export type ReactFunction = unknown;3
export type CreateElement = (element: ReactFunction, props?: Props, ...children: ReactElement[]) => ReactElement;2
export type ReactFunction = (props: unknown) => ReactElement;3
export type CreateElement = (element: ReactFunction | string, props?: Props, ...children: ReactElement[]) => ReactElement;5
export type Options = { createElement: CreateElement };6
export type Refs = Record<string, unknown>;7
export type Props = Record<string, unknown>;8
export type Slices = TemplateStringsArray;9
export type Values = unknown[];11
export interface Dict {12
[key: string]: ReactFunction | Dict;13
}