Skip to content

Instantly share code, notes, and snippets.

@prp-e
Created January 21, 2023 07:23
Show Gist options
  • Save prp-e/3bba55d780d7a738afcb15c6381651c0 to your computer and use it in GitHub Desktop.
Save prp-e/3bba55d780d7a738afcb15c6381651c0 to your computer and use it in GitHub Desktop.
VirtualDOM in Python

The following code is just the concept of "Virtual DOM" but written in python:

import copy 

class Node: 
    def __init__(self, tag, attributes, children): 
        self.tag = tag
        self.attributes = attributes
        self.children = children

class VirtualDOM: 
    def __init__(self, node): 
        self.root = node
        self.current = node 
    
    def render(self): 
        return self.root.render()
    
    def diff(self, new_node): 
        patches = [] 
        self._diff(self.root, new_node, patches) 
        self.root = new_node 
        self.current = new_node
        return patches 
    
    def _diff(self, old_node, new_node, patches): 
        if old_node.tag != new_node.tag: 
            patches.append({'action': 'replace', 'node': copy.deepcopy(old_node)})
        elif old_node.attributes != new_node.attributes: 
            patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
        elif len(old_node.children) != len(new_node.children): 
            patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
        else: 
            for i in range(len(old_node.children)): 
                self._diff(old_node.children[i], new_node.children[i], patches) 
                
        return patches

And this is how you can test this code:

# Create the root node
attributes = {'class': 'container'}
root = Node('div', attributes, [])

# Create a Virtual DOM object
vdom = VirtualDOM(root)

# Create a new DOM node
new_attributes = {'class': 'container', 'id': 'main'}
new_node = Node('div', new_attributes, [])

# Get the patches
patches = vdom.diff(new_node)

print(patches)

# Output: [{'action': 'update', 'node': <Node tag="div", attributes={'class': 'container'}>}]

And then, we wrote the same concept of virtual DOM with ability to render reliable HTML pages:

import copy 

class Node: 
    def __init__(self, tag, attributes, children): 
        self.tag = tag
        self.attributes = attributes
        self.children = children
        
    def render(self):
        out = "<" + self.tag
        if self.attributes: 
            for attribute, value in self.attributes.items(): 
                out += " " + attribute + "=" + value
        out += ">"
        for child in self.children: 
            out += child.render()
        out += "</" + self.tag + ">"
        return out

class VirtualDOM: 
    def __init__(self, node): 
        self.root = node
        self.current = node 
    
    def render(self): 
        return self.root.render()
    
    def diff(self, new_node): 
        patches = [] 
        self._diff(self.root, new_node, patches) 
        self.root = new_node 
        self.current = new_node
        return patches 
    
    def _diff(self, old_node, new_node, patches): 
        if old_node.tag != new_node.tag: 
            patches.append({'action': 'replace', 'node': copy.deepcopy(old_node)})
        elif old_node.attributes != new_node.attributes: 
            patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
        elif len(old_node.children) != len(new_node.children): 
            patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
        else: 
            for i in range(len(old_node.children)): 
                self._diff(old_node.children[i], new_node.children[i], patches) 
                
        return patches
    
    def apply_patches(self, patches): 
        for patch in patches: 
            if patch['action'] == 'replace': 
                self.root = copy.deepcopy(patch['node'])
            elif patch['action'] == 'update': 
                self.root.attributes = copy.deepcopy(patch['node'].attributes)
                
        return self.render()
 
# Create the root node
attributes = {'class': 'container'}
root = Node('div', attributes, [])

# Create a Virtual DOM object
vdom = VirtualDOM(root)

# Create a new DOM node
new_attributes = {'class': 'container', 'id': 'main'}
new_node = Node('div', new_attributes, [])

# Get the patches
patches = vdom.diff(new_node)

# Apply the patches
print(vdom.apply_patches(patches))

# Output: <div class="container" id="main"></div>

And this is a "hello world" page which uses bootstrap, has a navbar and a container in the middle which says "Hello World" with the same concept:

import copy 

class Node: 
    def __init__(self, tag, attributes, children): 
        self.tag = tag
        self.attributes = attributes
        self.children = children
        
    def render(self): 
        out = "<" + self.tag
        if self.attributes: 
            for attribute, value in self.attributes.items(): 
                out += " " + attribute + "=" + value
        out += ">"
        for child in self.children: 
            out += child.render()
        out += "</" + self.tag + ">"
        return out

class VirtualDOM: 
    def __init__(self, node): 
        self.root = node
        self.current = node 
    
    def render(self): 
        return self.root.render()
    
    def diff(self, new_node): 
        patches = [] 
        self._diff(self.root, new_node, patches) 
        self.root = new_node 
        self.current = new_node
        return patches 
    
    def _diff(self, old_node, new_node, patches): 
        if old_node.tag != new_node.tag: 
            patches.append({'action': 'replace', 'node': copy.deepcopy(old_node)})
        elif old_node.attributes != new_node.attributes: 
            patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
        elif len(old_node.children) != len(new_node.children): 
            patches.append({'action': 'update', 'node': copy.deepcopy(old_node)})
        else: 
            for i in range(len(old_node.children)): 
                self._diff(old_node.children[i], new_node.children[i], patches) 
                
        return patches
    
    def apply_patches(self, patches): 
        for patch in patches: 
            if patch['action'] == 'replace': 
                self.root = copy.deepcopy(patch['node'])
            elif patch['action'] == 'update': 
                self.root.attributes = copy.deepcopy(patch['node'].attributes)
                
        return self.render()
 
# Create the root node
attributes = {'class': 'container'}
header_attributes = {'class': 'navbar'}
html_attributes = {'lang': 'en'}
root = Node('html', html_attributes, [
    Node('head', {}, []),
    Node('body', {}, [
        Node('header', header_attributes, []),
        Node('div', attributes, [
            Node('p', {}, ['Hello World!'])
        ])
    ])
])

# Create a Virtual DOM object
vdom = VirtualDOM(root)
print(vdom.render())

# Output: 
# <html lang="en">
#   <head></head>
#   <body>
#     <header class="navbar"></header>
#     <div>
#       <p>Hello World!</p>
#     </div>
#   </body>
# </html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment