Code Generation for BuilDubs Website Builder

If you have used our BuilDubs Website Builder, you might have wondered how we generate code from the drag-and-drop interface. In this blog post, we will take you through the process of code generation for our website builder.
When you drag and drop elements on the BuilDubs interface, we generate code in real-time. This code can then be copied by opening the Code Sheet.

But before we delve into the code generation process, let's understand the structure of the BuilDubs website builder.
BuilDubs Website Builder
The BuilDubs website builder is a drag-and-drop interface that allows you to create websites without writing a single line of code. You can drag elements like text, images, buttons, and more onto the canvas and arrange them as per your requirements.
All of these are rendered as Nodes
internally in the builder.
These Nodes
then make up the NodeTree
structure of the website which is used to create the live-preview as well as generate code.
Nodes all the way down

Everything, from the root container to the smallest text element, is a Node
. Each Node
has a type
which determines the kind of element it represents. Even the props are stored inside the Node
object.
Although we can directly access the NodeTree
, we retrieve it as a "flattened" json object. This is done for sake of readability and to avoid the complexity of nested objects.
Here's how we access the Flattened NodeTree:
const data = nodeTree.getSerializedNodes()
Here's how the data
looks:
{
"ROOT": {..},
"LbV6VnYuov": {...},
"D4GJsWkTyi": {...},
"zcJt8GYlLO": {...},
"t7GcNdReUF": {...},
}
We can access eact node by its id like this:
const node = data["LbV6VnYuov"]
Here's the ROOT
node as an example from data[ROOT]
:
ROOT: {
"type": {
"resolvedName": "Container"
},
"isCanvas": true,
"props": {
"flexDirection": "column",
"alignItems": "flex-start",
"justifyContent": "flex-start",
"fillSpace": "no",
"padding": [
"40",
"40",
"40",
"40"
],
"margin": [
"0",
"0",
"0",
"0"
],
"background": {
"r": 255,
"g": 255,
"b": 255,
"a": 1
},
"color": {
"r": 0,
"g": 0,
"b": 0,
"a": 1
},
"shadow": 0,
"radius": 0,
"width": "800px",
"height": "auto"
},
"displayName": "Container",
"custom": {
"displayName": "App"
},
"hidden": false,
"nodes": [
"LbV6VnYuov",
"D4GJsWkTyi",
"zcJt8GYlLO",
"t7GcNdReUF",
"yFl7gm9I2F",
"v7FKrL6rw5",
"a14M2Fy1WY"
],
"linkedNodes": {}
}
As you might have guess, the ROOT
node is the root container of the website. It has a type
of Container
and contains other nodes as its children which are in the nodes
array.
This is the APP
container that you see when creating website in DubsUI. New lets get started on code generation.
Code Generation
We recursively traverse the NodeTree
and if a node has children, we generate code for the children recursively or return the code for the current node.
Here's the code for recursive traversal of the tree:
const generateJSX = (node, data, level = 0, imports = new Set()) => {
const { type, props, nodes, linkedNodes, displayName } = node
let children = ""
if (nodes && nodes.length > 0) {
children = nodes
.map((childId) => generateJSX(data[childId], data, level + 1, imports))
.join("\n")
}
}
imports is used to keep track of the components that are used in the code. This is used to generate the import statements at the top of the code.
level is used to keep track of the indentation level of the code.
The function returns the code in form of a string. We then join the code for the children and return the code for the current node.
Here's how we generate code for a Container
node:
const generateJSX = (node, data, level = 0, imports = new Set()) => {
const { type, props, nodes, linkedNodes, displayName } = node
let children = ""
if (nodes && nodes.length > 0) {
children = nodes
.map((childId) => generateJSX(data[childId], data, level + 1, imports))
.join("\n")
}
switch (type.resolvedName) {
case "Container":
return ` ${indent(level)} <div style={{${containerStyle(
props,
level
)}}} ${indent(level)} > \n${children}\n ${indent(level)} </div>`
default:
return ""
}
}
generateProps
is a helper function that generates the props for the node. Its used to provide inline styling for now.
Now, we can just keep extending definitions for different types of nodes, and keep adding them to the Swich-Case statement
. Here's how we generate code for a Text
node:
case "Text":
return ` ${indent(level)} <p style={{${textStyle(props, level)}}} ${indent(level)} >${props.text}</p>`
Annnndddd....That's it:
It's that simple...
Ok, its not, I've abstracted away the process of how the styles are generated, how the imports are generated, how the code is formatted, or even how the NodeTree
itself is generated. But the core idea is the same.
Future plans
We are planning to add more features to the BuilDubs website builder, like the ability to add custom components, animations, and more. We will keep you updated on our progress.
Hope you enjoyed this blog post. If you have any questions or feedback, feel free to reach out to us.
Happy coding!
The Three Dubs,
DevsTomorrow,
@jayshiai