Search code examples
javascriptreactjsdesign-patternsarchitecturesoftware-design

Visual builder to work with manually hard-coded templates/components


I wonder, is it possible to create a visual no-code builder to work with JS components (e.g. React JSX) if them are manually hard-coded before?

Let me explain what I mean.

Suppose a simple React component pages/index.js previously written by a developer manually:

function HomePage() {
    return <div>Welcome to Next.js!</div>
}

export default HomePage

How can we change such a component in the front-end using a visual builder?

For example, we want to add a new HTML element (e.g. H1) inside the existing div.

As I can understand, the builder first needs to know in which JS file the HTML markup is stored to update it. So we can add id="homepage" to the div first and then store a relation as a config like

{"homepage": "pages/index.js"}

And now if we add a new element inside <div id="homepage">, the builder adds the element to the DOM of the div at the client, then takes the whole updated DOM of the div and writes back to the file index.js according to the config

Ok, but the file contains not only HTML markup - it's JS (React) code.

How to keep all the JS code e.g. function HomePage(), return, export default and so on ?

As an option, we can separately load all the JS code as HTML including non-HTML code as #text nodes. Then update the DOM and re-write everything back to the file.

But it sounds complicated and may cause unexpected issues.

So what solution would be the best?

Or maybe there is a ready React-specific solution?

Or maybe it's a bad idea at all to parse and re-write manually hard-coded components by visual builder and the only solution is to store everything as JSON like "homepage":{"div", {"class":""}, "Welcome..."} which is more easy for re-writing ? (but requires a new render)


Solution

  • It depends

    I believe the answer that you are looking for very much depends on the level of flexibility that you want your no-code builder to have.

    Depending on that, your project could benefit of the trade-offs and advantages of different solutions.

    Let's briefly remember that basically a React component will need some props that then will be taken through a render template and output a working HTML. This is assuming a basic case where you don't need your react components to be smarter. Additionally, JSX is just sugar coating over function calls, so you could basically just compose functions to output a React component independently of using the JSX syntax. Hence no need to declare HTML, just changing the output of your no-code tool to JS instead of HTML.

    For example, if you can modify how the no-code tool render, you can specify that when moving an element inside another you basically:

    Highly Flexible & Customisable

    In a highly flexible setup, I will recommend going through the last option you numbered, having a Data-Driven UI is the most common of the cases for complex systems. For example, Figma has an option to export the designs as react components, you can read how they do it here. Basically they take the tag output from figma which is a JSON of tags and create some React componets using templates. If you define "you own UI language" you could have quite a good control over what blocks you can build and define the way of interacting with them (e.g. adding a img component could be quite easy if you know the props and what it needs to render, then creating a React template for it is easy). Limitations: you require to plan quite well the API of the parser and interaction between different sets of items.

    Simple no-code builder

    For simpler scenarios you could go with the first approach that you mention, you won't even need to add ids, some tools like React Developer Tools can already inspect the VirtualDOM to understand which part of the render belongs to which React Element (using react internals, which could take some time to understand, but for example you can inspect in the rendered how they use the data-reactid for identification). Knowing this, you can already define the template functions for the render() method (so the JSX output), and separate it code wise, so that when you generate the code, the HTML template is split from the React code as much as possible.

    Silly example of how it could look:

    // htmlBlockTemplate.js
    export const helloPageTemplate = (props) => 
    `<div> <h1>${props.title}</h1> </div>` // This can be generated from the `no-code`
    
    // page.jsx
    export const Page = (props) => { 
       return helloPageTemplate(props)
    }
    

    Using functions it could look like:

       const Page = (props) =>
        return React.createElement('div', null, 
              React.createElement('h1', title: prop.title, `The title ${title}`)
       );
    }
    

    Limitations: eventho you could, adding custom components (like another React Component or a web component), it becomes more difficult since you will also have to deal with the import graph and probably.

    Inevitably you will need to tweak how the render of the component works (rather by creating a parser from your UI language, or changing how the react component is written. If you have a good control of AST, then writing a parser for either of the cases should not be a problem.