JSX is a flexible and powerful “template syntax”. It not only has the functions of common template syntax, such as the functions in Art and Jade, but also supports the extension of Javascript syntax, allowing us to write templates in Javascript. How JSX is converted to Vdom in React + Webpack + Babel framework.

This paper studies the basic process of JSX syntax to VDOM, and studies the three core transformation processes. The process is as follows:

  • JSX to AST conversion. Babel-parser is used to convert JSX AST.
  • AST clipping processing. Mainly Preact complete function replacement, createElement method/createFragment replacement.
  • CreateVnode. Generation and creation of virtual DOM.

1. JSX AST conversion

Based on React/Preact + Babel technology selection, we write JSX syntax, and ultimately convert JSX AST tree through Babel. JSX has multiple conversion tools, as shown below (github.com/facebook/js…) :

  • Babylon (@babel/ Parser) : Babel conversion tool, convert JSX to JSX AST Tree.
  • The flow – parser:
  • typescript
  • esprima
  • Acorn – JSX.

The most common one we use is @babel/ Parser. JSX processing is complicated, so let’s take a look at AST products. We see that a JSX element has something like the following structure:

  • JSXElement indicates that the current element is JSXElement.
  • JSXOpeningElemen, start tag element, key attribute tag name, attributes.
  • The children element stores the children of the current JSXElement.
  • JSXClosingElement: closes the tag element, and the name of the key attribute closes the tag.

JSX supports both rich JSX Attributes and can be combined with JS code, which is a powerful feature. In theory, we can pass any properties to React Props, including component, base type, reference type, function, JSX, etc. This allows us to pass any content to the child components via the React Props. On the other hand, the combination of JSX and JS code allows us to manipulate various data, update JSX elements, and maintain a high degree of flexibility.

2. AST processing

Babel after get JSX AST, React createElement method is how to convert/createFragment, its main use the Babel – plugin – transform – React – JSX to complete this function. For example, for our case in 1, use babel-plugin-ransform-react-jsx to translate to similar code.

"use strict";
React.createElement("div", 
  { className: "A" }, 
  React.createElement("div", 
   { className: "B" }, 
   "hello world!"));Copy the code

We take @bebel/ plugin-transform-React-jsx 7.11.3 version as an example to analyze the react JSX to react Function Call conversion. @bebel/ plugin-transform-React-jsx currently has two modes:

  • Classic mode. V6 to date has been supported modes, functional stability.
  • Automic, automatic mode. The mode added in V7.9.0 enables JSX compilation by default.

The functions of the two modes are similar. We analyze how Classic implements the Function of JSX to React Function Call. @bebel/plugin-transform-react-jsx /plugin-transform-react-jsx /plugin-transform-react-jsx

  • Rumtime: Mode selection, classic by default.
  • ThrowIfNamespace: specifies whether an error will be reported during XML naming. Default is true.
  • Pragma: JSX Expression Replace name, default React. CreateElement.
  • PragmaFrag: JSX fragments replace name, default React.Fragment.

After looking at the basic parameters of @bebel/ plugin-transform-React-jsx, we can look at the functions it needs to handle:

  • Convert JSX to React. CreateElement.
  • JSX Attributin conversion.
  • Processing of JSX Children.

2.1 @bebel/ plugin-transform-React-jsx code structure

Babel’s plug-in lies in the functional design of the visitor. We see the following visitor design for @Bebel/plugin-transform-React-Jsx:

  • JSXNamespacedName/JSXSpreadChild, does not support the function of logos.
  • Program, Program preprocessing.
  • JSXAttribute: Processing JSX attributes.
  • JSXElement, JSXElement node processing.
  • JSXFragment, JSXFragment node processing.

Based on the structure of visitor, we introduce and analyze the transformation process of JSX in terms of the “initialization” phase, JSXElement, and JSXFragment.

2.2 Initialization

As you can see in transform-classic.js, the functionality of Babel’s plug-in can be divided into four parts:

  • Initialize options and define Pre/Post.
  • The Program.
  • JSXAttribute.

2.2.1 Processing options and defining Pre/Post

Options initialization, we only care about pragma/pragmaFrag processing, in React is React. CreateElement /React.Fragment, Is in the Preact Preact. The createElement method/Preact. Fragments.

const DEFAULT = {
  pragma: "React.createElement".pragmaFrag: "React.Fragment"};export default declare((api, options) = >{.../ / set PRAGMA_DEFAULT/PRAGMA_FRAG_DEFAULT
  const PRAGMA_DEFAULT = options.pragma || DEFAULT.pragma;
  constPRAGMA_FRAG_DEFAULT = options.pragmaFrag || DEFAULT.pragmaFrag; . }Copy the code

The plug-in also defines pre/ POST methods for pre-processing JSXElement/JSXFragment. Pre functions are mainly used to process components and tags. Here we can see why components cannot start in lowercase. When we start in lowercase, react-jsx converts it to react.createElement (‘x’). const visitor = helper({ pre(state) { const tagName = state.tagName; const args = state.args; // If it starts with a lowercase letter, it is marked as a tag and is created as a string element // If it starts with a non-lowercase letter, it is marked as an element // github.com/babel/babel… if (t.react.isCompatTag(tagName)) { args.push(t.stringLiteral(tagName)); } else { args.push(state.tagExpr); }}}); Babeljs. IO /docs/en/bab…

post(state, pass) {
  state.callee = pass.get("jsxIdentifier") (); state.pure = PURE_ANNOTATION ?? pass.get("pragma") === DEFAULT.pragma;
},
Copy the code

2.2.2 the Program

Plugin-transform-react-jsx’s program handles comments and initializes some public parameters. See the Babel Issue for a comment, but let’s focus on the initialization of parameters. State is used to set the following key mappings:

  • JsxIdentifier. Create an identifier as React. CreateElement.
  • JsxFragIdentifier. Create a representation as React. CreateFragElement.
  • UsedFragment. Whether fragments are used.
  • Pragma. Setup of React. CreateElement.
  • PragmaSet. Pragma specifies whether to set.
  • PragmaFragSet. PragmaFrag Specifies whether to set.
visitor.Program = {
    enter(path, state) {
      const { file } = state;
      let pragma = PRAGMA_DEFAULT;
      let pragmaFrag = PRAGMA_FRAG_DEFAULT;
      letpragmaSet = !! options.pragma;letpragmaFragSet = !! options.pragma; .// Initialize the related parameters, which are handled by JSXElement
      state.set("jsxIdentifier", createIdentifierParser(pragma));
      state.set("jsxFragIdentifier", createIdentifierParser(pragmaFrag));
      state.set("usedFragment".false);
      state.set("pragma", pragma);
      state.set("pragmaSet", pragmaSet);
      state.set("pragmaFragSet", pragmaFragSet);
    },
    exit(path, state) {
      // usedFragment but pragmaFragSet is not set
      if (
        state.get("pragmaSet") &&
        state.get("usedFragment") &&
        !state.get("pragmaFragSet")) {throw new Error(
          "transform-react-jsx: pragma has been set but " +
            "pragmaFrag has not been set",); }}};Copy the code

2.2.3 JSXAttribute

The handling of JSXAttribute is simple, converting a JSXElement attribute node to a jsxExpressionContainer. } />

visitor.JSXAttribute = function (path) {
    if(t.isJSXElement(path.node.value)) { path.node.value = t.jsxExpressionContainer(path.node.value); }};Copy the code

2.3 JSXElement/JSXFragment

The processing of JSXElement/JSXFragment nodes is similar. Let’s focus on the processing of JSXElement. JSXElement processing, the key lies in three processing:

  • Convert JSXElement to React. CreateElement (Function Call).
  • JSX Attriebute extraction and processing.
  • Children processing and recursive processing.
visitor.JSXElement = {
    exit(path, file) {
      // Call buildElementCall to handle the Function Call
      const callExpr = buildElementCall(path, file);
      if(callExpr) { path.replaceWith(t.inherits(callExpr, path.node)); }}};Copy the code

2.3.1 Treatment of Children

In the JSXElement handler function, use the React buildChildren provided by T to complete the children node.

function buildElementCall(path, file) {
    if(opts.filter && ! opts.filter(path.node, file))return;
    const openingPath = path.get("openingElement");
    // build chilrenopeningPath.parent.children = t.react.buildChildren(openingPath.parent); . }Copy the code

The buildChildren function handles three types of nodes:

  • Text node, filter text after empty placeholders such as \r\n\t, if there is push to elements.
  • JSXExpressionContainer node. Sets the child to JSXExpressionContainer. Express, push to the elements.
  • Empty expression nodes are removed.
export default function buildChildren(node: Object) :Array<Object> {
  const elements = [];
  for (let i = 0; i < node.children.length; i++) {
    let child = node.children[i];
    if (isJSXText(child)) {
      cleanJSXElementLiteralChild(child, elements);
      continue;
    }
    if (isJSXExpressionContainer(child)) child = child.expression;
    if (isJSXEmptyExpression(child)) continue;
    elements.push(child);
  }
  return elements;
}
Copy the code

2.3.2 Attribute processing

The processing of attributes is complex and combines the setting of the useSpread and useBuiltIns attributes. You can read about the processing of attributes in detail if you are interested.

function buildElementCall(path, file) {...let attribs = openingPath.node.attributes;
    if (attribs.length) {
      attribs = buildOpeningElementAttributes(attribs, file);
    } else{ attribs = t.nullLiteral(); } args.push(attribs, ... path.node.children); . }Copy the code

2.3.3 Processing of JSXElement

The main Function of JSXElement is to convert JSXElement nodes into Function Call functions. Combined with code, its main functional flow is as follows:

  • Initialize Children, as analyzed in 2.3.1.
  • Set the ElementState. The plug-in creates the ElementState element to store related information, including nodes, names, parameters, call functions, and so on, to complete the initialization preparation of function.call.
  • The pre processing.
  • Property Settings.
  • Return the call. Set the final call,const call = state.call || t.callExpression(state.callee, args), the Function Call node is created.

What our code looks like in the actual business, as shown in the figure below, is based on the JSX transformation code obtained in Preact 10.4.6 DEV mode. We can go to JSXElement and eventually convert it into a nested call to preact.createElement.

var _ref3 =
/*#__PURE__*/
Object(preact__WEBPACK_IMPORTED_MODULE_0__["createElement"]) ("div", {
  className: "A",},Object(preact__WEBPACK_IMPORTED_MODULE_0__["createElement"]) ("div", {
  className: "B",},"hello world!"));
Copy the code

3. VDOM conversion

Using Preact10.4.6 as a baseline, let’s see how Preact implements the conversion from JSX to Vdom Tree.

3.1 createVNode

In createElement, createVNode is defined as follows:

export function createVNode(type, props, key, ref, original) {
    const vnode = {
        type,    // Node, corresponding label (div)/ Component
        props,   // Attribute value, props set
        key,     
      
ref, //
_children: null.// Children Set of nodes, null/Array _parent: null.// The parent node, the parent node of the child element _depth: 0.// Node depth _dom: null.// Native DOM node values, _nextDom: undefined.// Next sibling node _component: null.// Instantiate React Component constructor: undefined.// React Component constructor _original: original // Source node content }; // The origin node defaults to the current node if (original == null) vnode._original = vnode; // VNode initialization is complete if (options.vnode) options.vnode(vnode); return vnode; } Copy the code

Preact retains only the core node values, such as type, props, key, and ref. These attribute values will be used in subsequent DIff and HTML transformations, and subsequent analysis will analyze the related attribute values.

Outside of the node definition, we see that Preact’s createVNode processes Original, options.vnode, and then returns the created node.

export function createVNode(type, props, key, ref, original) {
    constvnode = {... }if (original == null) vnode._original = vnode;
    if (options.vnode) options.vnode(vnode);
    return vnode;
}
Copy the code

3.2 createElement method

In general, Webpack +Babel converts a JSX Element to the form createElement(App, null), which completes the creation and initialization of the node.

Preact10.4.6 source code is as follows, its basic process is as follows:

  • Normalize props. NormalizedProps after removing all props except key and ref.
  • The third and subsequent arguments are converted to the Child array and set normalizedProps. Children.
  • When type is a function (corresponding to Component), defaultProps is merged into normalizedProps.
  • CreateVNode is called to generate a virtual node and returns.
export function createElement(type, props, children) {
    let normalizedProps = {},
        i;
    for (i in props) {
        if(i ! = ='key'&& i ! = ='ref') normalizedProps[i] = props[i];
    
    if (arguments.length > 3) {
        children = [children];
        for (i = 3; i < arguments.length; i++) {
            children.push(arguments[i]);
        }
    
    if(children ! =null) {
        normalizedProps.children = children;
    }

    if (typeof type == 'function'&& type.defaultProps ! =null) {
        for (i in type.defaultProps) {
            if (normalizedProps[i] === undefined) { normalizedProps[i] = type.defaultProps[i]; }}}let result = createVNode(
        type,
        normalizedProps,
        props && props.key,
        props && props.ref,
        null
    );
    return result;
}
Copy the code

After analyzing the functions of Preact createNode/createElement, let’s look at the actual generated node structure in demo 1/2 as shown below.

4. To summarize

Through the transformation and compilation process of JSX in Preact, we can see the three core nodes from JSX to Vdom:

  • Babel AST conversion to complete JSX syntax construction.

  • Babel/plugin – trasnform – react – JSX transformation, converts JSXElement/JSXFragElement Function Call, complete compile time processing.

  • Preact createVnode/createElement method processing, complete vdom construction of the tree.

    Make a small advertisement, the team continues to expand, welcome each student to deliver. You can send them as follows:

  • Delivery (job.toutiao.com/s/JMpw6Rr) by the following link.

  • You can send your resume to my email ([email protected]) and I will deliver it to you.

5. Refer to the documents

  • Github.com/jamiebuilds…
  • Babeljs. IO/docs/en/bab…
  • github.com/babel/babel