Languages
[Edit]
EN

Preact - convert HTML to Preact nodes / VNode-s

8 points
Created by:
Richard-Bevan
223

In this short article, we would like to show how to convert HTML to Preact nodes (virtual nodes).

Below solution is a trick to do not use:

<div dangerouslySetInnerHTML={{__html: myHtml}} />

or:

<div ref={myRef}> />   // with:  myRef.current.innerHTML

 

The motivation to convert from HTML to Preact virtual nodes are:

  1. dangerouslySetInnerHTML can lead to injecting unwanted source code that is dangerous,
  2. using ref with innerHTML properties is not safe too and do not let to render HTML on the server-side,
  3. converting HTML to VNodes we are able to filter tags and attributes with simple conditions in the code
    (you can modify convertElement function in the below code to alow use only few tags and few attributes making your site safer - just return null if you dont want to display some node),
  4. hydration used with server-side rendering (SSR) requires virtual preact nodes to detect changes inside injected HTML,
  5. it is good to do updates only on changed parts of the page - below solutions helps to reduce number of DOM changes, browser re-redering / re-painting, etc.

App.jsx file:

import { h } from "preact";
import convertHtml from './convertHtml';

const App = () => {
    const html = '<div>Some html...<br />Some html...<br />Some html...</div>';
    const nodes = convertHtml(html);
    return (
        <div class="app">
            {nodes}
        </div>
    );
};

export default App;

convertHtml.tsx file:

import { h, ComponentChild } from "preact";
//import { DomHandler, Parser } from 'htmlparser2'; // required in Node.js

const parseHtml = (html: string): NodeListOf<ChildNode> => {

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // this case works in the web browser
    //
    const hRoot = document.createElement('div');
    hRoot.innerHTML = HTML;

    return hRoot.childNodes;

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // this case works in Node.js (useful when we want to render on the server)
    //     Check: https://dirask.com/posts/DdZVWj
    //            to know how to install DomHandler and Parser classes.
    //
    // const handler = new DomHandler();
    // const parser = new Parser(handler);
    // parser.write(html);
    // parser.end();
    //
    // return handler.root.childNodes as any; // it is good to some common types
};

const createType = (tag: string): string => {
    return tag.toLowerCase();
};

const createProperties = (attributes: NamedNodeMap): Record<string, any> => {
    const properties: Record<string, any> = {};
    for (let i = 0; i < attributes.length; ++i) {
        const attribute = attributes[i];
        properties[attribute.name] = attribute.value;
    }
    return properties;
};

const convertElement = (hElement: Element): ComponentChild => {
    const type = createType(hElement.tagName);
    const properties = createProperties(hElement.attributes);
    const children = convertNodes(hElement.childNodes);
    return h(type, properties, children);
};

const convertText = (hText: Text): string => {
    return hText.data;
};

const convertNode = (hNode: ChildNode): ComponentChild | null => {
    if (hNode.nodeType === 1) { // Node.ELEMENT_NODE
        return convertElement(hNode as Element);
    }
    if (hNode.nodeType === 3) { // Node.TEXT_NODE
        return convertText(hNode as Text);
    }
    return null;
};

const convertNodes = (hNodes: NodeListOf<ChildNode>): Array<ComponentChild> | null => {
    if (hNodes.length > 0) {
        const nodes: Array<ComponentChild> = [];
        for (let i = 0; i < hNodes.length; ++i) {
            const hNode = convertNode(hNodes[i]);
            if (hNode == null) {
                continue;
            }
            nodes.push(hNode);
        }
        return nodes;
    }
    return null; // we want to get self-closed elements in rendering to string on the server when elements are ampty (it means: `render` function returns `<br></br>` instread of `<br />` when empty array is returned)
};

/**
 * Converts HTML to preact nodes.
 *
 * @param html input html
 * @returns preact element created from input HTML or null
 */
const convertHtml = (html?: string | null): Array<ComponentChild> | null => {
    if (html) {
        const hChildren = parseHtml(html);
        return convertNodes(hChildren);
    }
    return null;
};

export default convertHtml;

References

  1. Node.js - parse html to DOM with htmlparser2 library 
Native Advertising
🚀
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

❤️💻 🙂

Join