This is the 17th day of my participation in the August More Text Challenge. For details, see “August More Text Challenge”.

  • When it comes to front-end development these days, front-end frameworks are essential. As front-end technologies continue to iterate and the documentation and community around the front-end framework becomes better, it becomes easier to get started. Tools and frameworks can be picked up quickly, but the design and principles are often overlooked.

  • A lack of in-depth understanding of the framework and tools will lead to a lack of direction when we encounter some side-door problems, which is not conducive to the expansion of personal knowledge and can not be a good choice of technology.

  • Today, I’ll take you through why front-end frameworks are so popular, and introduce you to the implementation of the template engine, the core capability of front-end frameworks. Along the way, some code will be used as examples in vue.js.

  • Let’s start by looking at why we use a front-end framework.

Why use a front-end framework

  • When a tool is used by most people and becomes popular, it can not be done without the historical process of the development of the relevant technology. By understanding why these tools and frameworks have emerged, we can keep abreast of the direction of technology development, stay sensitive to technology, and update our own knowledge, which will become our own competitiveness.

  • The same goes for the front-end frame. Before front-end frameworks existed, jQuery was a prerequisite for front-end development and was used in most projects. What happened in a few short years when front-end development became impossible without the front-end framework?

The rapid development of the front end

  • Once upon a time, everyone thought of the front end with jQuery. This was the age of jQuery, which was used for page development both front and back. Back then, front-end development tended to be more about scheming and refactoring, more about page style than logic, and more about stitching together JSP templates, PHP templates, and browser compatibility.

  • Why is jQuery so popular? In addition to the super convenient Sizzle engine element selector, the easy to use asynchronous request library Ajax, and the chain-call programming makes the code flow like a stream. The convenience provided by jQuery almost satisfies most of the front-end work at the time (so it’s not unreasonable to say that jQuery is a hassle).

  • Over the next few years, the front end went through an extraordinary amount of change. With the advent of Node.js, improved NPM package management, and a vibrant open source community, the front end is supported by millions of developers. From page development to library development, framework development, script development, and server-side development, single-threaded JavaScript continues to reinvent itself, broadening the domain and creating the empowered front end you see today.

  • So what is causing jQuery to fall out of favor and front-end frameworks to take center stage? There are many reasons for this, including the evolution of business scenarios, the evolution of technology, such as the increasing complexity of front-end applications, the emergence of single-page applications, front-end modularity, etc.

The emergence of front-end frames

  • From the user’s point of view, the browser generates the final render tree and rasterizes the page to display on screen, rendering the page is done.

  • In fact, browser pages are more than just static page rendering, but also include click, drag and drop event operations and dynamic interactive logic such as interface requests, data rendering to the page, so we often need to update the content of the page.

  • To understand why front-end frameworks are so important, you need to look at how front-end development was implemented to interact with users before the advent of frameworks.

  • For example, in the rush to answer activities often appear questions and multiple answers to choose, we now need to develop a management side, to manage these rush to answer cards. Assuming a question will have two answers, we can add a set of questions and answers by adding a card. The process of editing the card includes these steps.

    1. When you add a card, you add the card style by inserting a DOM node.

Copy the code

var index = 0;
// To add a new card, the card needs to fill in some content
function addCard() {
  // Get an element whose ID is the-dom
  var body = $("#the-dom");
  // Get the element whose class is the-class from the element
  var addDom = body.find(".the-class");
  // Insert a div in front of the a-class element
  addDom.before('<div class="col-lg-4" data-index="' + index + '"></div>');
  // Save the DOM node at the same time, easy to update the content
  var theDom = body.find('[data-index="' + index + '"]);
  theDom.innerHTML(
    ' Placeholder =" form_control option_b "> '
  );
  // After the top pile, the index will increase
  index++;
  return theDom;
}
Copy the code
    1. (Use jQuery to listen for input events in the input box and limit what you can enter).
// theDom uses the reference saved by the above code
// Problem binding value
theDom
  .on("keyup".".question".function (ev) {
    ev.target.value = ev.target.value.substr(0.20);
  })
  // Answer a binding value
  .on("keyup".".option-a".function (ev) {
    ev.target.value = ev.target.value.substr(0.10);
  })
  // Answer B binding value
  .on("keyup".".option-b".function (ev) {
    ev.target.value = ev.target.value.substr(0.10);
  });
Copy the code
  1. Gets the content in the input box (using jQuery to select the element and get the content) for submission to the background.
// Get the input value of the card
// theDom uses the reference saved by the above code
function getCardValue(index) {
  var body = $("#the-dom");
  var theDom = body.find('[data-index="' + index + '"]);
  var questionName = theDom.find(".question").val();
  var optionA = theDom.find(".option-a").val();
  var optionB = theDom.find(".option-b").val();
  return { questionName, optionA, optionB };
}
Copy the code

As you can see, it takes a lot of code just to edit a single question card, most of it to concatenate HTML content, get DOM nodes, and manipulate DOM nodes. The code logic, if implemented using Vue, is written as follows:

<template>
  <div v-for="card in cards">
    <input
      type="text"
      class="form-control question"
      v-model="card.questionName"
      placeholder="Your problem"
    />
    <input
      type="text"
      class="form-control option-a"
      v-model="card.optionA"
      placeholder=Replied "1"
    />
    <input
      type="text"
      class="form-control option-b"
      v-model="card.optionB"
      placeholder="Answer 2"
    />
  </div>
</template>
<script>
  export default {
    name: "Cards".data() {
      return {
        cards: [],}; },methods: {
      // Add a card
      addCard() {
        this.cards.push({
          questionName: "".optionA: "".optionB: ""}); },// Get the input value of the card
      getCardValue(index) {
        return this.cards[index]; ,}}};</script>
Copy the code
  • As you can see, the front-end framework provides convenient APIS for data binding, interface updates, event listening, and so on. We no longer need to manually update the content of the front-end page and maintain a lot of dynamic content with HTML and variable concatenation.

  • Using a front-end framework can greatly improve the development efficiency, but also to a certain extent to avoid code readability, maintainability and other problems. This is why front-end frameworks are so popular and why everyone uses them for development.

  • So how does the front-end framework do this? To implement these capabilities, the template engine is indispensable.

The core of the front-end framework is the template engine

  • When users operate the page and update the page content, we need to implement the following functional processes:

    • Monitoring operation;

    • Get data variables;

    • Use data variables to concatenate HTML templates;

    • Shove HTML content into the appropriate place on the page;

    • Bind events such as clicks within an HTML fragment that you want to listen for.

As you can see, the implementation logic can be complex and cumbersome.

  • Using a front-end framework, we can:

    • Control what is displayed by binding data variables to HTML templates;

    • With some condition judgment, condition cycle logic, control the specific logic of interaction;

    • By changing data variables, the framework automatically updates the content of the page.

In this way, we can develop the features quickly and efficiently, and the code is far more readable and maintainable than it would be if it were implemented by hand.

If you use a data-driven approach, you can also improve the maintainability of your code by decoupling the logic from the UI. Data binding, event binding and other functions, the front-end framework is dependent on the template engine to achieve.

Take Vue as an example. For Vue code written by the developer, Vue will render it to the page by:

  • Parsing syntax generates AST objects;

  • Data initialization is completed according to the generated AST object.

  • Generate virtual DOM objects according to AST objects and data data binding.

  • The virtual DOM object generates real DOM elements and inserts them into the page, at which point the page is rendered.

  • The template engine parses the template syntax and generates THE HTML DOM respectively. Using methods like HTML splicing (binding variables in the corresponding position, parsing instructions to obtain splicing logic, etc.), together with event management and virtual DOM design, can maximize the performance of the page.

These are the main work of the template engine, let’s take a look at each.

Parsing syntax generates AST objects

Abstract Syntax Tree, also known as AST Syntax Tree, refers to the Tree structure of source code Syntax. In fact, our DOM structure tree is also a kind of AST, the browser will parse the HTML DOM and generate the final page.
  • The process of generating an AST involves the principles of the compiler and generally goes through the following process.

  • Syntax analysis. The template engine needs to recognize specific syntax in this process, such as directives such as V-if /v-for, or custom DOM tags such as this, and simplified binding syntax such as @click/:props.

  • Semantic analysis. This process reviews the source program for semantic errors and gathers type information for the code generation phase, as well as general type checking. For example, if we bind a variable or event that does not exist, or if we use an undefined custom component, an error message will be reported at this stage.

Generate an AST object.

Taking Vue as an example, the process of generating an AST includes HTML template parsing, element checking, and preprocessing:

Copy the code

/** * compile HTML into an AST object * this code snippet is based on version VUe2.x */
export function parse(template: string, options: CompilerOptions) :ASTElement | void {
  // Return the AST object
  // Some pre-definitions are omitted because of length
  // Start parsing the HTML template
  parseHTML(template, {
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    start(tag, attrs, unary) {
      // Some pre-checks and Settings, compatibility processing are omitted
      // The element AST object that is initialized is defined here
      const element: ASTElement = {
        type: 1,
        tag,
        attrsList: attrs,
        attrsMap: makeAttrsMap(attrs),
        parent: currentParent,
        children: [],};// Check whether the element tag is valid (not reserved)
      if(isForbiddenTag(element) && ! isServerRendering()) { element.forbidden =true; process.env.NODE_ENV ! = ="production" &&
          warn(
            "Templates should only be responsible for mapping the state to the " +
              "UI. Avoid placing tags with side-effects in your templates, such as " +
              ` <${tag}> ` +
              ", as they will not be parsed."
          );
      }
      // Perform some pre-processing of elements
      for (let i = 0; i < preTransforms.length; i++) {
        preTransforms[i](element, options);
      }
      // Whether it is a native element
      if (inVPre) {
        // Handle some attributes of the element
        processRawAttrs(element);
      } else {
        // Processing instructions include v-for/ V-if/V-once /key and so on
        processFor(element);
        processIf(element);
        processOnce(element);
        processKey(element); // Delete the structure attributes
        // Determine if this is a simple elementelement.plain = ! element.key && ! attrs.length;// Handles properties such as ref/slot/ Component
        processRef(element);
        processSlot(element);
        processComponent(element);
        for (let i = 0; i < transforms.length; i++) {
          transforms[i](element, options);
        }
        processAttrs(element);
      }
      // The parent and child nodes are omitted
    },
    // Other omissions
  });
  return root;
}
Copy the code

Here, Vue parses the developer’s template code into an AST object. Let’s see how such AST objects generate DOM elements.

The AST object generates DOM elements

As mentioned earlier, during compilation parsing and rendering, the template engine recognizes and parses the template syntax semantics, generates AST objects, and finally generates the final DOM elements from the AST objects.

For example, let’s write the following HTML template:

Copy the code

<div>
  <a>123</a>
  <p>456<span>789</span></p>
</div>
Copy the code

The template engine can obtain such an AST object after syntax analysis, semantic analysis and other steps:

Copy the code

thisDiv = {
  dom: {
    type: "dom".ele: "div".nodeIndex: 0.children: [{type: "dom".ele: "a".nodeIndex: 1.children: [{ type: "text".value: "123"}],}, {type: "dom".ele: "p".nodeIndex: 2.children: [{type: "text".value: "456" },
          {
            type: "dom".ele: "span".nodeIndex: 3.children: [{ type: "text".value: "789"}],},],},],},},};Copy the code

This AST object maintains some of the information we need, including in HTML elements:

  • Which variables need to be bound (the content of this node needs to be updated when the variable is updated);

  • Whether there is any other logic that needs to be processed (such as logical instructions, such as V-if, V-for, etc.);

  • Which nodes are bound to listen for events (matching some common event capability support, such as @click).

  • The template engine will generate the final page fragment and logic according to the AST object. In this process, it will add special identification (such as element ID, attribute tag, etc.) to mark the DOM node. With the DOM element selection method and event listening method, the DOM node can be quickly located when the update is needed. And the node content update, so as to achieve the page content update.

Currently, the implementation of front-end template rendering is generally divided into the following two ways.

  • String template: Use concatenation to generate a DOM string and insert it directly into the page via innderHTML().

  • Node template: Dynamically insert DOM nodes using methods like createElement()/appendChild()/textContent.

When using the string template, we bind nodeIndex to the element attribute, primarily to trace the node for content updates when the data is updated.

When using a node template, we can save the node when we create it and use it directly for data update:

Copy the code

// Assume that this is a DOM generation process, including innerHTML and event listeners
function generateDOM(astObject) {
  const { dom, binding = [] } = astObject;
  // Generate the DOM, pretending that the current node is baseDom
  baseDom.innerHTML = getDOMString(dom);
  // For data binding, listen for updates
  baseDom.addEventListener("data:change".(name, value) = > {
    // Find a matching data binding
    const obj = binding.find((x) = > x.valueName == name);
    // If the corresponding node of the value binding is found, update its value.
    if (obj) {
      baseDom.find(`[data-node-index="${obj.nodeIndex}"] `).innerHTML = value; }}); }// Get the DOM string, which is simply spelled as string
function getDOMString(domObj) {
  // Invalid objects return ''
  if(! domObj)return "";
  const { type, children = [], nodeIndex, ele, value } = domObj;
  if (type == "dom") {
    // If there are child objects, return the generated string concatenation recursively
    const childString = "";
    children.forEach((x) = > {
      childString += getDOMString(x);
    });
    // Dom object, concatenation to generate the object string
    return ` <${ele} data-node-index="${nodeIndex}">${childString}</${ele}> `;
  } else if (type == "text") {
    // For textNode, return the value of text
    returnvalue; }}Copy the code

In this way, the front-end framework implements the generation of DOM elements from AST objects and renders or updates those DOM elements to the page.

You may be wondering: it was originally one

HTML template, through the AST to generate an object, and eventually to generate one

DOM nodes, which may seem redundant.

In fact, the templating engine can do much more in this process.

The templating engine can do more
  • HTML templates are parsed into AST objects, and DOM nodes are generated based on AST objects. In this process, the front-end framework can achieve the following functions:

  • Exclude invalid DOM elements (non-custom components and non-default component DOM elements), which can be found and reported in time during the construction phase;

  • Can identify the component from the definition, and render the corresponding component;

Conveniently realize data binding, event binding and other functions;

  • Laying the groundwork for the virtual DOM Diff process;

  • HTML escapes (to prevent XSS vulnerabilities).

Here’s a detailed look at how a template engine can avoid XSS attacks, using the example of # 5, PREVENTING XSS vulnerabilities.

Prevent XSS vulnerabilities

We know that the entire XSS attack process is roughly:

  • An attacker submits content containing malicious code (such as JavaScript scripts);

  • When the page is rendered, this content is loaded and processed without filtering, such as obtaining cookies, performing operations, etc.

  • Other users browsing the page will be attacked when the malicious code is loaded.

  • To avoid XSS attacks on website users, the main method is to filter the content submitted by users. Most front-end frameworks come with HTML escape functionality to avoid XSS attacks.

  • In Vue’s case, using the default data binding method (double braces, V-bind, etc.) HTML escapes and interprets the data as plain text rather than HTML code.

  • In addition to preventing XSS vulnerabilities, the front-end framework also provides some performance, security and other optimization, and also provides some tools for project development, including routing management, state and data management tools.