The course notes from Silicon Valley course link [Mustache Template engine of Silicon Valley Shao Shanhua (Kaula) Vue] with a lot of notes and rewriting

1. Introduction to the template engine

1.1 What is a template engine?

A template engine is a solution for turning data into views (HTML)

Data:View:Vue’s solution

<li v-for="item in arr"></li>
Copy the code

1.2 Where did the template engine come from? (Development History)

1. Use native DOM operations

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>01_ Data into view - pure DOM method</title>
</head>

<body>
  <ul id="list"></ul>
  
  <script>
    var arr = [
      { name: 'Ming'.age: 12.sex: 'male' },
      { name: 'little red'.age: 11.sex: 'woman' },
      { name: 'jack'.age: 13.sex: 'male'},]var list = document.getElementById('list')

    for (let i = 0; i < arr.length; i++) {
      // The DOM method is used to create the li tag each time the item is iterated
      let oLi = document.createElement('li')
      
      // Create the hd div
      let hdDiv = document.createElement('div')
      hdDiv.className = 'hd'
      hdDiv.innerText = arr[i].name + 'Basic Information'
      oLi.appendChild(hdDiv)

      // Create the bd div
      let bdDiv = document.createElement('div')
      bdDiv.className = 'bd'
      oLi.appendChild(bdDiv)
      
      // Create 3 p tags
      let p1 = document.createElement('p')
      p1.innerText = 'Name:' + arr[i].name
      bdDiv.appendChild(p1)
      let p2 = document.createElement('p')
      p2.innerText = 'Age:' + arr[i].age
      bdDiv.appendChild(p2)
      let p3 = document.createElement('p')
      p3.innerText = 'Gender:' + arr[i].sex
      bdDiv.appendChild(p3)
      
      // The created node is an orphan node, so it must be up the tree for the user to see
      list.appendChild(oLi)
    }
  </script>

</body>

</html>
Copy the code

2. Use the join method in the array

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>02_ Data into view - Array join method</title>
</head>

<body>
  <ul id="list">
  </ul>
  <script>
    var arr = [
      { name: 'Ming'.age: 12.sex: 'male' },
      { name: 'little red'.age: 11.sex: 'woman' },
      { name: 'jack'.age: 13.sex: 'male'},]var list = document.getElementById('list')
    
    // Iterate through the ARR array, adding the HTML string to the list as a string for each entry
    for (let i = 0; i < arr.length; i++) {
      list.innerHTML += [
        '<li>'.' 
       
'
+ arr[i].name + 'message '.'
'
.'

Name:'

+ arr[i].name + '</p>'.'

Age:'

+ arr[i].age + '</p>'.'

Gender:'

+ arr[i].sex + '</p>'.' '.'</li>' ].join(' ')}
</script> </body> </html> Copy the code

3. Use the ES6 backquotes method

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>03_ Data to view -ES6 backquotes method</title>
</head>

<body>
  <ul id="list">
  </ul>
  <script>
    var arr = [
      { name: 'Ming'.age: 12.sex: 'male' },
      { name: 'little red'.age: 11.sex: 'woman' },
      { name: 'jack'.age: 13.sex: 'male'},]var list = document.getElementById('list')
    // Iterate through the ARR array, adding the HTML string to the list as a string for each entry
    for (let i = 0; i < arr.length; i++) {
      list.innerHTML += `
        <li>
          <div class="hd">${arr[i].name}</div> <div class="bd">${arr[i].name}</p> <p>${arr[i].age}</p> <p>${arr[i].sex}</p>
          </div>
        </li>
      `
    }
  </script>

</body>

</html>

Copy the code

Mustache basic use

2.1 Brief introduction to Mustache Library

  • Official git:https://github.com/janl/mustache.js mustache
  • Mustache translates to “mustache” in Chinese.
  • Mustache is the original template engine library, very muchCreative, sensational

Introducing the mustache library

<script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js"></script>
Copy the code

Mustache’s template syntax

<ul>
{{#arr}}
  <li>
    <div class="hd">Basic information about {{name}}</div>
    <div class="bd">
      <p>Name: {{name}}</p>
      <p>Age: {{age}}</p>
      <p>Gender: {{sex}}</p>
    </div>
  </li>
{{/arr}}
</ul>
Copy the code

1. The simplest case — do not loop through an array of objects

  <div id="container"></div>
  <h1></h1>
  <script>
    var templateStr = '< H1 > I bought a {{thing}}, good {{mood}} ah '
    var data = {
      thing: Huawei Mobile phone.mood: 'happy'
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
Copy the code

2. Loop through the simplest array

  <div id="container"></div>
  <h1></h1>
  <script>
    var templateStr = ` 
       
    {{#arr}}
  • {{.}}
  • {{/arr}}
`
var data = { arr: ['apple'.'pear'.'banana']}var domStr = Mustache.render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
</script> Copy the code

3. Array of loop objects (similar to V-for)

  <div id="container"></div>
  <script>
    var templateStr = `
      <ul id="list">
        {{#arr}}
        <li>
          <div class="hd">{{name}}Basic information about</div>
          <div class="bd">
            <p>Name:{{name}}</p>
            <p>Age:{{age}}</p>
            <p>Gender:{{sex}}</p>
          </div>
        </li>
        {{/arr}}
      </ul>` var data = {arr: [{name: "xiao Ming, age: 12, sex: 'male'}, {name:" little red ", the age: 11, sex: 'female'}, {name: 'small strong', age: 13, sex: 'Mustache'},]} var var = Mustache. Render (Mustache, data) var container = document.getElementById('container') container.innerHTML = domStr</script>
Copy the code

4. Loop through nested object arrays and simple arrays

  <div id="container"></div>
  <h1></h1>
  <script>
    var templateStr = `
      <ul>
        {{#arr}}
          <li>{{name}}My hobbies are:<ol>
                {{#hobbies}}
                  <li>{{...}}</li>
                {{/hobbies}}
              </ol>
          </li>
        {{/arr}}  
      </ul>Var data = {arr: [{name: 'hobbies ', age: 12, hobbies: [' swimming ',' badminton ']}, {name: 'hobbies ', age: 11, hobbies: [' swimming ',' badminton '] [' programming ', 'composition', 'reading the newspaper]}, {name:' small strong ', age: 13, hobbies: [' play billiards ']}]} Mustache. data) var container = document.getElementById('container') container.innerHTML = domStr</script>
Copy the code

5. Controls the show and hide of elements — Boolean values

True Displays false does not display

  <div id="container"></div>
  <h1></h1>
  <script>
    var templateStr = '{{#m}} 

Hahaha

{{/m}}'
var data = { m: true } var domStr = Mustache.render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
</script> Copy the code

6. scriptTemplate method

Writing templates to the scirpt tag, as long as the type value is not text/javascript, will not be parsed as JS, so that templates can be written to the script tag, can be highlighted, and can be filled automatically

  <div id="container"></div>

  <! -- -- -- > templates
  <script type="text/template" id="mytemplate">
    <ul id="list">
        {{#arr}}
        <li>
          <div class="hd">{{name}}Basic information about</div>
          <div class="bd">
            <p>Name:{{name}}</p>
            <p>Age:{{age}}</p>
            <p>Gender:{{sex}}</p>
          </div>
        </li>
        {{/arr}}
      </ul>
  </script>

  <script>
    var templateStr = document.getElementById('mytemplate').innerHTML
    var data = {
      arr: [{name: 'Ming'.age: 12.sex: 'male' },
        { name: 'little red'.age: 11.sex: 'woman' },
        { name: 'jack'.age: 13.sex: 'male']}},var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>
Copy the code

The principle of the mustache

The 3.1 Replace () method and regular expressions implement the simplest template data populating

Preliminary knowledge

The replace () method

This method takes two arguments. The first argument can be a RegExp object or a string (which is not converted to a regular expression), and the second argument can be a string or a function

The second argument to replace() can be a function. When there is only one match, the function receives three arguments: the string that matches the entire pattern, the start position of the match in the string, and the entire string

console.log('I love football, I love talk shows.'.replace(/ I/g.function (a, b, c) {
  console.log(a, b, c)
  return 'you'
}))
Copy the code

Capture of re

/\}\}{(\w+)\}\}/ 
Copy the code

Captures multiple characters or numbers in the middle of {{}}

var templateStr = '

I bought a {{thing}}, good {{mood}} ah

console.log(templateStr.replace(/\{\{(\w+)\}\}/g.function (a, b, c, d) { console.log(a, b, c, d) return The '*' })) Copy the code

implementation

<div id="container"></div>

<script>
  var templateStr = '

I bought a {{thing}}, spent {{money}}, good {{mood}}

'
var data = { thing: Huawei Mobile phone.money: 5999.mood: 'happy' } // The simplest template engine implementation mechanism uses the replace() method in regular expressions // The second argument to replace() can be a function that takes an argument to the captured thing, that is, $1 in conjunction with the data object, which can be intelligently replaced Function {{thing}} function {{thing}} function {{thing}} function function render(templateStr, data) { return templateStr.replace(/\{\{(\w+)\}\}/g.function (findStr, $1) { return data[$1]})}var domStr = render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
</script> Copy the code

3.2 The implementation of Mustache

3.3 What are tokens?

Tokens are nested arrays of JS, template strings, and are the first of abstract syntax trees, virtual nodes, and so on

1. Simplest form

Template string

<h1>I bought a {{thing}}, good {{mood}} ah</h1>
Copy the code

tokens

[["text"."

I bought one"

], ["name"."thing"], ["text"."Good"], ["name"."mood"], ["text"."< / h1 >"]]Copy the code

2. Tokens in the case of circular arrays

3. Multiple loops

3.4 The point of implementing Mustache Library is

  1. willTemplate stringCompiled intotokens
  2. willtokensIn combination withData from the dataAnd resolve toDOM string

4. Implement Mustache Library by hand

4.1 Configuring the WebPack Environment

Build with Webpack and webpack-dev-server

Create a new directory YK_TemplateEngine

cd YK_TemplateEngine
Copy the code
npm init -yes
Copy the code
cnpm install -D webpack@4 webpack-cli@3 webpack-dev-server@3
Copy the code

The code for creating a new webpack.config.js file is as follows

const path = require("path");

module.exports = {
  mode: "development"./ / the entry
  entry: "./src/index.js"./ / export
  output: {
    filename: "bundle.js",},/ / configuration webpack - dev - server
  devServer: {
    // Static root directory
    contentBase: path.join(__dirname, "www"),
    / / no compression
    compress: false./ / the port number
    port: 8080.// The bundle.js file was not actually generated
    publicPath: "/xuni/",}};Copy the code

New SRC/index. Js

alert('nihao')
Copy the code

New WWW/index. HTML

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>

<body>
  <h1>I am index. HTML</h1>
  <script src="xuni/bundle.js"></script>
</body>

</html>
Copy the code

Add commands to package.json file:

{
  "scripts": {
    "dev": "webpack-dev-server",}}Copy the code

Terminal operationnpm run devAccess:http://localhost:8080/http://127.0.0.1:8080/xuni/bundle.jsAs you can seewww/index.htmlxuni/bundle.jsContents of the document

4.2 Implement Scanner Scanner class

Prepare knowledge JS string extraction string method

ECMAScript provides three methods for extracting substrings from strings: slice(), substr(), and substring(). Each of these three methods returns a substring of the string from which they were called, and each takes one or two arguments. The first argument represents where the substring begins, and the second argument represents where the substring ends. For slice() and substring(), the second argument is the location where the extraction ends (that is, the characters before that location are extracted). For substr(), the second argument represents the number of substrings returned. In any case, omitting the second parameter means extracting to the end of the string. Like the concat() method, slice(), substr(), and substring() do not modify the string from which they were called, but only return the extracted original new string value

src/Scanner.js

/** * scanner class */
export default class Scanner {
  constructor(tempalteStr) {
    this.tempalteStr = tempalteStr;
    / / pointer
    this.pos = 0;
    // The end string, which starts with the template string text
    this.tail = tempalteStr;
  }
  // Scan past the specified content {{or}}, no return value
  scan(tag) {
    if (this.tail.indexOf(tag) === 0) {
      // How long is the tag? For example, "{{" is 2, just move the pointer back
      this.pos += tag.length;
      this.tail = this.tempalteStr.substr(this.pos); }}// Let the pointer scan until it encounters the end of the specified {{or}} content, and returns the text that passed before the end
  scanUtil(stopTag) {
    // Record the value of pos at the start of execution
    const post_backup = this.pos;
    // If the end of the string does not start with stopTag, it indicates that stopTag has not been scanned yet
    while (!this.eos() && this.tail.indexOf(stopTag) ! = =0) {
      this.pos++;
      // Change the last character of the string from the current pointer to the last character
      this.tail = this.tempalteStr.substr(this.pos);
    }
    return this.tempalteStr.substring(post_backup, this.pos);
  }
  // Check whether the pointer has reached the end of the string
  eos() {
    return this.pos >= this.tempalteStr.length
  }
}
Copy the code

www/index.html

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>

<body>
  <h1>Hello!!!!!!</h1>
  <script src="xuni/bundle.js"></script>
  <script>
    var templateStr = '<h1>I bought one{{thing}}Good,{{mood}}ah</h1>Var data = {thing: 'thing ', mood:' happy '} SGG_TemplateEngine. Render (templateStr, data)</script>
</body>

</html>
Copy the code

src/index.js

import Scanner from "./Scanner";

// The YK_TemplateEngine object is provided globally
window.YK_TemplateEngine = {
  // Render method
  render(tempalteStr, data) {
    // Instantiate a scanner that is constructed with an argument that is a template string
    // The scanner works on the template string
    var scanner = new Scanner(tempalteStr);
    var word
    // The pos pointer does not end
    while(! scanner.eos()) { word = scanner.scanUtil({{" ");
      console.log(word+'* * *');
      scanner.scan({{" ");
      word = scanner.scanUtil("}}");
      console.log(word);
      scanner.scan("}}"); }}};Copy the code

4.3 Generate tokens Array

4.3.1 Complete a simple one-layer array

src/parseTemplateToTokens

import Scanner from "./Scanner";
/** * Convert template strings to tokens array */
export default function parseTemplateToTokens(tempalteStr) {
  var tokens = [];
  // Create scanner
  var scanner = new Scanner(tempalteStr);
  var words
  // Let the scanner work
  while(! scanner.eos()) {Collect the text before the start tag appears
    words = scanner.scanUtil({{" ");
    if(words ! = =' ') {
      tokens.push(['text', words])
    }
    scanner.scan({{" ");
    / / collection
    words = scanner.scanUtil("}}");
    if(words ! = =' ') {
      // This is something in the middle of {{}}
      if (words[0= = =The '#') {
        tokens.push([The '#', words.substring(1)])}else if (words[0= = ='/') {
        tokens.push(['/', words.substring(1)])}else {
        tokens.push(['name', words])
      }
    }
    scanner.scan("}}");
  }
  return tokens;
}
Copy the code

www/index.html

// Template string
/ / var tempalteStr = '< h1 > I bought a {{thing}}, good {{mood}} < / h1 >'
var tempalteStr = ` < div > < ol > {{# students}} {{name}} < li > the students hobby is < ol > < li > {{# hobbies}} {{...}} < / li > {{/ hobbies}} < / ol > < / li > {{/ students}}  `
/ / data
var data = {
  thing: 'phone'.mood: 'happy'
}
YK_TemplateEngine.render(tempalteStr, data)
Copy the code

src/index.js

import parseTemplateToTokens from './parseTemplateToTokens'

// The YK_TemplateEngine object is provided globally
window.YK_TemplateEngine = {
  // Render method
  render(tempalteStr, data) {
    // Call parseTemplateToTokens to change the template string to an array of tokens
    var tokens = parseTemplateToTokens(tempalteStr)
    console.log(tokens)
  },
};
Copy the code

4.3.2 Completing the Nesting of Arrays (Difficult points)

The stackTo solve the problem of # bump, bump/bump

src/nestTokens.js

Fold tokens between # XXX and/XXX into Array(n) as the end Array of XXX [“#”, “XXX “, Array(n)]

/** * Fold tokens between # XXX and/XXX into Array(n) as the end Array of XXX ["#", "XXX ", Array(n)] *@param {*} tokens* /
export default function nestTokens(tokens) {
  // The result array stores the last nested array
  var nestedTokens = [];

  // Add tokens between # / tokens
  // Fold the tokens between # XXX and/XXX into Array(n), ["#", "XXX ", Array(n)]
  // Unstack when/is encountered
  var sections = [];

  // Collect tokens for the top of the stack or the result array
  // Initially point to nestedTokens result array, reference type values, so point to the same array
  // After the stack, change the pointer: after the stack top end array token[2]
  Intersections [section.length-1][2] and nestedTokens
  var collector = nestedTokens;

  for (let token of tokens) {
    // What is the 0th element of the token
    switch (token[0]) {
      case "#":
        // Add the tokens to the collector (nestedTokens array)
        collector.push(token);
        / / into the stack
        sections.push(token);
        // Change the collector pointer to add the subscript 2 item to the token
        collector = token[2] = [];
        break;
      case "/":
        / / out of the stack
        sections.pop();
        // Change collector to top end sections array if the stack is not empty
        // If the stack is empty, it points directly to the result array
        collector =
          sections.length > 0 ? sections[sections.length - 1] [2] : nestedTokens;
        break;
      // Common token
      default:
        // If there are elements in the stack, enter the array at the end of the stack; If there are no elements on the stack, we enter the result arraycollector.push(token); }}return nestedTokens;
}
Copy the code

4.4 Parse tokens into DOM strings

index.html

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>

<body>
  <h1>This is the HTML</h1>
  <div id="container"></div>
  <script src="xuni/bundle.js"></script>
  <script>// Template string // var tempalteStr = '<h1>I bought one{{thing}}Good,{{mood}}ah</h1>'
    var tempalteStr = `
    <div>
        <ul>
          {{#students}}
          <li class="ii">students{{name}}Hobby is<ol>
              {{#hobbies}}
              <li>{{...}}</li>
              {{/hobbies}}
            </ol>
          </li>
          {{/students}}
        </ul>
      </div>` / var/data data = {students: [{name: "little red", hobbies: [' badminton ', 'taekwondo]}, {name: "xiao Ming, hobbies: [' football']}, {name: 'Xiao Wang ', hobbies: [' magic ', 'learning ',' game ']}]} let domStr = YK_TemplateEngine. Render (tempalteStr) data) console.log(domStr) let container = document.getElementById('container'); container.innerHTML = domStr;</script>
</body>

</html>
Copy the code

src/index.js

import parseTemplateToTokens from "./parseTemplateToTokens";
import renderTemplate from "./renderTemplate";

// The YK_TemplateEngine object is provided globally
window.YK_TemplateEngine = {
  // Render method
  render(tempalteStr, data) {
    // Call parseTemplateToTokens to change the template string to an array of tokens
    var tokens = parseTemplateToTokens(tempalteStr);
    var domStr = renderTemplate(tokens, data);
    return domStr
  },
};

Copy the code

SRC /lookup.js looks for the properties of the continuous point symbol in the object

/** * Look for the keyName property of the continuous point symbol in the dataObj object such as A.B.C {a:{b:{c:100}} *@param {object} dataObj
 * @param {string} keyName* /
export default function lookup(dataObj, keyName) {
  // Check if there is a dot in keyName, but it cannot be. itself
  if (keyName.indexOf(".")! = = -1&& keyName ! = ='. ') {
    let temp = dataObj; // Temporary variables are used for turnover, layer by layer
    let keys = keyName.split(".");
    for (let key of keys) {
      temp = temp[key];
    }
    return temp;
  }
  return dataObj[keyName]
}
Copy the code

Simplified version of the SRC/lookup. Js

export default function lookup(dataObj, keyName) {
  // There is only one element that does not affect the final result of the loop, so there is no need to check the dot symbol in keyName
  returnkeyName ! = ='. ' ? keyName.split('. ').reduce((prevValue, currentKey) = > prevValue[currentKey], dataObj) : dataObj[keyName]
}
Copy the code

src/renderTemplate.js

import lookup from './lookup'
import parseArray from './parseArray'
/** * Make the tokens array DOM string *@param {array} tokens
 * @param {object} data* /
export default function renderTemplate(tokens, data) {
  // Result string
  let resultStr = "";
  for (let token of tokens) {
    if (token[0= = ="text") {
      resultStr += token[1];
    } else if (token[0= = ="name") {
      resultStr += lookup(data, token[1]);
    } else if (token[0= = ="#") {
      // Call renderTemplate recursively
      resultStr += parseArray(token, data)
    }
  }
  return resultStr;
}

Copy the code

SRC/parsearray.js recursively calls renderTemplate

import lookup from "./lookup";
import renderTemplate from "./renderTemplate";
['#', 'arr', Array[n]]] {students: [{name: 'xiaohong ', hobbies: [' badminton ',' taekwondo ']}, {name: 'xiaoming ', hobbies: [' soccer ']}, {name: '小王', hobbies: [' magic ', 'learning ',' game ']}]} * parseArray() to recursively call renderTemplate function 3 times, array length =3 */
export default function parseArray(token, data) {
  // console.log(token, data);
  // Get the part of data that the array needs to use
  let newData = lookup(data, token[1]);
  // console.log(newData);
  // Result string
  let resultStr = ' ';
  for (let item of newData) {
    resultStr += renderTemplate(token[2] and {// Expand newData[I] and add the dot array. item,'. ': item
    })
  }
  return resultStr;
}
Copy the code

4.5 Improve the space problem

  1. Normal text Spaces are removed
  2. The whitespace in the tag cannot be removed, for example<div class="box"><></div>Can’t removeclassSpace before
// Collect the text before the start tag
words = scanner.scanUtil('{{')
/ / save it
if(words ! = =' ') {
  // Determine the space in the normal text or the space in the tag
  
      
<>
Cannot remove the space before the class
let isInJJH = false // A blank string var _words = ' ' for (let i = 0; i < words.length; i++) { // Check if it is in the tag if (words[i] === '<') { isInJJH = true } else if (words[i] === '>') { isInJJH = false } if (!/\s/.test(words[i])) { _words += words[i] } else { // If this item is a space, it will be concatenated only if it is inside the tag if (isInJJH) { _words += words[i] } } } tokens.push(['text', _words]) } Copy the code