This series of articles is to share my own step-by-step process of writing the entire framework, and those interested in XDM can refer to the source code. Git repository: github.com/sullay/art-…

Render DOM elements

In this article, the first in a series and the first step in writing a front-end framework, we first complete the most important and basic function of the framework: rendering DOM elements.

Here is a piece of HTML code that we often see.

<div style="margin: 100px auto; text-align: center;">
    <p style="color: red;">Hello World!</p>
</div>
Copy the code

Let’s consider how to abstract the ABOVE DOM structure with A JS object. Here is my answer.

{
    type: "div".props: {style"margin: 100px auto; text-align: center;"},
    children: [{type: "p".props: {style'color:red; '},
        children: ["Hello World!"]]}}Copy the code

With the JS object above, we can start thinking backwards about how rendering js objects into pages requires DOM elements.

We need to write a Render method that takes two arguments. The first argument, Element, is the js object we constructed above, and the second argument, parentDom, is the mount point of our rendered element.

function render(element, parentDom) {
  let dom;
  if (typeof element === "string") {
    dom = document.createTextNode(element)
  } else {
    const { type, props, children } = element;
    // Create the corresponding DOM according to the element type
    dom = document.createElement(type);
    // Set the properties
    for (let key in props) dom[key] = props[key];
    // Render the child element
    for (let child of children) render(child, dom);
  }
  // Bind to the parent element
  parentDom.appendChild(dom);
}
Copy the code

At this point we have written the render method, test it in the browser, and we can render the JS object to the page normally.

Virtual DOM and JSX

With the Render method, we are ready to render the JS object into the corresponding DOM element. But through THE HTML text we can more clearly understand the structure of the page, but only through the JS object we can hardly imagine what the page should look like, at this time we need to precompile the HTML text we write into JS objects.

JSX grammar

Considering that esbuild, Bable, and TS all support JSX syntax, we will use JSX syntax directly to simplify our js object creation. If you are not familiar with JSX syntax, you are advised to check it out before reading the rest.

Here we use esbuild to preprocess JSX files as follows:

esbuild xxx.jsx --bundle --jsx-factory=h --jsx-fragment=Fragment --outfile=xxx.out.js
Copy the code

We use esbuild to process the following code

// Pre-processing code
render(
  <div style="margin: 100px auto; text-align: center;">
    <p style="color:red;">Hello World!</p>
  </div>.document.getElementById('root'))// Post-processing code
(() = > {
  render(/* @__PURE__ */ h("div", {
    style: "margin: 100px auto; text-align: center;"
  }, /* @__PURE__ */ h("p", {
    style: "color:red;"
  }, "Hello World!")), document.getElementById("root")); }) ();Copy the code

You can see that there is an extra H method in the processed code. This method is the factory function that JSX uses to create JS objects. You can change the name of the function with the command line terminal jsX-factory parameter.

Before we start writing our factory function H, we can rearrange our JS objects.

function isEvent(key) {
  return key.startsWith('on');
}

function getEventName(key) {
  return key.toLowerCase().replace(/^on/."");
} 

// Common elements
class vNode {
  constructor(type = ' ', allProps = {}, children = []) {
    this.type = type;
    this.props = {};
    this.events = {};
    for (let prop in allProps) {
      if (isEvent(prop)) {
        this.events[getEventName(prop)] = allProps[prop];
      } else {
        this.props[prop] = allProps[prop]; }}// Handle literal elements in child elements
    this.children = children.map(child= > {
      return vNode.isVNode(child) ? child : new vTextNode(child);
    });
  }
  // Determine if it is a virtual Dom element
  static isVNode(node) {
    return node instanceof this; }}// Text element
class vTextNode extends vNode {
  constructor(text) {
    super(vTextNode.type, { nodeValue: text })
  }
  static type = Symbol('TEXT_ELEMENT');
}

// Create the element
export function h(type, props, ... children) {
  return new vNode(type, props, children);
} 
Copy the code

Here we write two classes to represent our normal and literal elements, separate events from other attributes, and then we write h methods to help us create virtual DOM objects.

Update our Render method to add event listeners.

function render(element, parentDom) {
  if(! vNode.isVNode(element))throw new Error("Incorrect render element type");
  const { type, props, events, children } = element;
  // Create the corresponding DOM according to the element type
  const dom = vTextNode.isVNode(element) ? document.createTextNode(' ') : document.createElement(type);
  // Set the properties
  for (let key in props) dom[key] = props[key];
  // Render the child element
  for (let child of children) render(child, dom);
  // Listen on events
  for (let event in events) dom.addEventListener(event, events[event])
  // Bind to the parent element
  parentDom.appendChild(dom);
}
Copy the code

At this point, dom rendering is almost complete for our framework.

The next article will focus on the implementation of custom components, so XDM should keep an eye out for future updates if you are interested.