current position:Home>Mustache template engine for exploring Vue source code

Mustache template engine for exploring Vue source code

2022-04-29 13:02:55Portugal and bear

mustache Template engine pre knowledge

To put it bluntly , From the perspective of source code , Make it clear to me , How to get the specified value with double curly braces , And show it to the view .

One 、 What is a template engine

Template engine is the most elegant solution to turn data into view

1.0 The method of changing data into view in history

 pure DOM Law : Very clumsy ,  No practical value 
 Array join Law : Once upon a time, it was very popular ,  It was once the front-end must have knowledge 
ES6 Back quotation marks :ES6 In the new `${a}` Grammatical sugar ,  useful 
 template engine : The most elegant way to turn data into a view 
 Copy code 

1.1 The data becomes a view - pure DOM Law


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

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

<body>
    <ul id="list">
    </ul>

    <script> var arr = [ { "name": " Xiao Ming ", "age": 12, "sex": " male " }, { "name": " Xiaohong ", "age": 11, "sex": " Woman " }, { "name": " cockroach ", "age": 13, "sex": " male " } ]; var list = document.getElementById('list'); for (var i = 0; i < arr.length; i++) { //  Each traversal item , You have to use DOM Method to create li label  let oLi = document.createElement('li'); //  establish hd This div let hdDiv = document.createElement('div'); hdDiv.className = 'hd'; hdDiv.innerText = arr[i].name + ' Basic information of '; //  establish bd This div let bdDiv = document.createElement('div'); bdDiv.className = 'bd'; //  Create three p let p1 = document.createElement('p'); p1.innerText = ' full 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 you have to go up the tree to be seen by users  oLi.appendChild(hdDiv); //  The created node is an orphan node , So you have to go up the tree to be seen by users  oLi.appendChild(bdDiv); //  The created node is an orphan node , So you have to go up the tree to be seen by users  list.appendChild(oLi); } </script>
</body>

</html>   
 Copy code 

1.2 The data becomes a view - Array join Law

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

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

<body>
    <ul id="list"></ul>

    <script> var arr = [ { "name": " Xiao Ming ", "age": 12, "sex": " male " }, { "name": " Xiaohong ", "age": 11, "sex": " Woman " }, { "name": " cockroach ", "age": 13, "sex": " male " } ]; var list = document.getElementById('list'); //  Traverse arr Array , Each traversal item , From a string perspective HTML String added to list in  for (let i = 0; i < arr.length; i++) { list.innerHTML += [ '<li>', ' <div class="hd">' + arr[i].name + ' Information about </div>', ' <div class="bd">', ' <p> full name :' + arr[i].name + '</p>', ' <p> Age :' + arr[i].age + '</p>', ' <p> Gender :' + arr[i].sex + '</p>', ' </div>', '</li>' ].join('') } </script>
</body>

</html>

 Copy code 

1.3 The data becomes a view -ES6 Back quotation marks

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

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

<body>
    <ul id="list"></ul>

    <script> var arr = [ { "name": " Xiao Ming ", "age": 12, "sex": " male " }, { "name": " Xiaohong ", "age": 11, "sex": " Woman " }, { "name": " cockroach ", "age": 13, "sex": " male " } ]; var list = document.getElementById('list'); //  Traverse arr Array , Each traversal item , From a string perspective HTML String added to list in  for (let i = 0; i < arr.length; i++) { list.innerHTML += ` <li> <div class="hd">${arr[i].name} Basic information of </div> <div class="bd"> <p> full name :${arr[i].name}</p> <p> Gender :${arr[i].sex}</p> <p> Age :${arr[i].age}</p> </div> </li> `; } </script>
</body>

</html>

 Copy code 

Two 、mustache Basic use of

• mustache official git: github.com/janl/mustac…

• mustache yes “ Beard ” It means , Because of its embedded tag {{ }} Very much like a beard . you 're right , {{ }} The grammar of is also Vue Continue to use , This is what we learn mustache Why .

• mustache Is the earliest template engine library , Than Vue It was born much earlier , Its underlying implementation mechanism was very creative at that time 、 Sensational , It provides a new idea for the development of follow-up template engine .

• Must introduce mustache library , Can be in bootcdn.com Find it on the .

• mustache The syntax template is very simple .

2.1 mustache Basic use - Loop object array

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

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

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

    <!--  Templates   Be careful type No text/javascript-->
    <script type="text/template" id="mytemplate"> <ul> {{#arr}}<!--  Loop start  --> <li> <div class="hd">{{name}} Basic information of </div> <div class="bd"> <p> full name :{{name}}</p> <p> Gender :{{sex}}</p> <p> Age :{{age}}</p> </div> </li> {{/arr}}<!--  The loop ends  --> </ul> </script>

    <script src="jslib/mustache.js"></script>
    <script> var templateStr = document.getElementById('mytemplate').innerHTML; var data = { arr: [ { "name": " Xiao Ming ", "age": 12, "sex": " male " }, { "name": " Xiaohong ", "age": 11, "sex": " Woman " }, { "name": " cockroach ", "age": 13, "sex": " male " } ] }; var domStr = Mustache.render(templateStr, data); var container = document.getElementById('container'); container.innerHTML = domStr; </script>
</body>

</html>

 Copy code 

2.2 mustache Basic use - No circulation

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="container"></div>

    <script src="jslib/mustache.js"></script>
    <script> var templateStr = ` <h1> I bought one {{thing}}, good {{mood}} ah </h1> `; var data = { thing: ' Huawei mobile phones ', mood: ' Happy ' }; var domStr = Mustache.render(templateStr, data); var container = document.getElementById('container'); container.innerHTML = domStr; </script>
</body>
</html>

 Copy code 

2.3 mustache Basic use - Loop simple arrays

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="container"></div>

    <script src="jslib/mustache.js"></script>
    <script> var templateStr = ` <ul> {{#arr}} <li>{{.}}</li> {{/arr}} </ul> `; var data = { arr: ['A', 'B', 'C'] }; var domStr = Mustache.render(templateStr, data); var container = document.getElementById('container'); container.innerHTML = domStr; </script>
</body>
</html>

 Copy code 

2.4 mustache Basic use - Nesting of arrays


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="container"></div>

    <script src="jslib/mustache.js"></script>
    <script> var templateStr = ` <ul> {{#arr}} <li> {{name}} My hobby is : <ol> {{#hobbies}} <li>{{.}}</li> {{/hobbies}} </ol> </li> {{/arr}} </ul> `; var data = { arr: [ {'name': ' Xiao Ming ', 'age': 12, 'hobbies': [' swimming ', ' badminton ']}, {'name': ' Xiaohong ', 'age': 11, 'hobbies': [' Programming ', ' Write a composition ', ' Read the newspaper ']}, {'name': ' cockroach ', 'age': 13, 'hobbies': [' Playing billiards ']}, ] }; var domStr = Mustache.render(templateStr, data); var container = document.getElementById('container'); container.innerHTML = domStr; </script>
</body>
</html>
 Copy code 

2.5 mustache Basic use - Boolean value

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="container"></div>

    <script src="jslib/mustache.js"></script>
    <script> var templateStr = ` {{#m}} <h1> Hello </h1> {{/m}} `; var data = { m: false <!-- true You can see  --> }; var domStr = Mustache.render(templateStr, data); var container = document.getElementById('container'); container.innerHTML = domStr; </script>
</body>
</html>

 Copy code 

3、 ... and 、mustache Underlying core mechanism of

3.1 mustache Libraries cannot be implemented with simple regular expression ideas

image.png

3.2 Demonstrate how regular expressions implement simple template data filling

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

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

<body>
    <script> var templateStr = '<h1> I bought one {{thing}}, It took {{money}} element , good {{mood}}</h1>'; var data = { thing: ' Chinese cabbage ', money: 5, mood: ' excited ' }; //  The implementation mechanism of the simplest template engine , Using the in regular expressions replace() Method . // replace() The second parameter of can be a function , This function provides parameters for what is captured , Namely $1 //  combination data object , Intelligent replacement  function render(templateStr, data) { return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) { return data[$1]; }); } var result = render(templateStr, data); console.log(result); </script>
</body>

</html>

 Copy code 

3.3 mustache Mechanism library

image.png

3.4 What is? tokens?

image.pngimage.pngimage.pngimage.png

3.5 mustache The bottom layer of the library focuses on two things

  • Compile the template string into tokens form
  • take tokens Combine data , It can be interpreted as dom character string

3.6 Observe tokens

To put it bluntly , Just encounter curly braces , Count in a group

image.png

<!--  It's better to watch videos here , See clearly [[]]  The division inside -->
<!DOCTYPE html>
<html lang="en">

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

<body>
    <script src="jslib/mustache.js"></script>
    <script> var templateStr1 = ` <ul> {{#arr}} <li> <div class="hd">{{name}} Basic information of </div> <div class="bd"> <p> full name :{{name}}</p> <p> Gender :{{sex}}</p> <p> Age :{{age}}</p> </div> </li> {{/arr}} </ul> `; var templateStr2 = ` <ul> {{#arr}} <li> {{name}} My hobby is : <ol> {{#hobbies}} <li>{{.}}</li> {{/hobbies}} </ol> </li> {{/arr}} </ul> `; Mustache.render(templateStr2, {}); </script>
</body>

</html>

 Copy code 

With your handwriting mustache library

One 、 Use webpack and webpack-dev-server structure

• Modular packaging tools ( Also known as build tool ) Yes webpack(webpack-dev-server) 、 rollup、 Parcel etc.

• mustache The official library uses rollup Modular packaging , And today we use webpack

(webpack-dev-server) Modular packaging , This is because webpack(webpackdev-server) It makes it easier for us to ( instead of nodejs Environment ) Real time debugging program , comparison nodejs Console , Browser console is better to use , For example, you can click to expand each item of the array .

• Build library is UMD Of , This means that it can be in nodejs Use in the environment , It can also be used in the browser environment . Realization UMD It's not hard to , Just one “ Universal head ” that will do .

• Modular packaging tools include webpack(webpack-dev-server) 、 rollup、 Parcel etc.

image.pngimage.png

webpack.config.js file

image.png

• When learning source code , The idea of source code should be used for reference , Instead of copying . Be able to find the wonderful places written in the source code ;

• Disassemble the independent functions into independent js Done in file , Usually a separate class , Each individual function must be able to work independently “ unit testing ” ;

• It should focus on the central function , Finish the trunk first , Then prune the branches and leaves ;

• The function does not need to be in place in one step , The expansion of functions should be completed step by step , Some non core functions don't even need to be implemented ;

1.1 Create project 、 Environment configuration

 step :
 Create a new empty folder 
vscode open 
npm init
npm i -D [email protected]4 [email protected]3 [email protected]3
 newly build webpack.config.js file , Own match 

 Copy code 
//package.json
{
  "name": "sgg_templateengine",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
      //npm run dev  You can run 
    "dev": "webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"// If you make a mistake , Just install this webpack-dev-server
  }
}

 Copy code 
//webpack.config.js
const path = require('path');

module.exports = {
    //  Pattern , Development 
    mode: 'development',
    //  entrance 
    entry: './src/index.js',
    //  What file to package 
    output: {
        filename: 'bundle.js'
    },
    //  Configure it webpack-dev-server
    devServer: {
        //  Static file root   namely www/index.html page 
        //  In future projects , The path does not have to be the same , Know how to match , What's this for 
        contentBase: path.join(__dirname, "www"),
        //  Uncompressed 
        compress: false,
        //  Port number 
        port: 8080,
        //  The path of the virtual package ,bundle.js The file is not really generated 
        publicPath: "/xuni/"
    }
};

 Copy code 

1.2 Handwritten implementation scanner class

image.png

image.png

/*  Scanner class  */
export default class Scanner {
    constructor(templateStr) {
        //  Write the template string to the instance 
        this.templateStr = templateStr;
        //  The pointer 
        this.pos = 0;
        //  tail , From the beginning, it is the original template string 
        this.tail = templateStr;
    }

    //  Weak function , Is to walk through the specified content , no return value 
    scan(tag) {
        if (this.tail.indexOf(tag) == 0) {
            // tag How long is , such as {{ The length is 2, Just move the pointer back by how many bits 
            this.pos += tag.length;
            //  The tail will change , Change the tail to start with the character of the current pointer , All characters to the end 
            this.tail = this.templateStr.substring(this.pos);
        }
    }

    //  Let the pointer scan , Until the specified content is met , And can return the text passed before the end 
    scanUtil(stopTag) {
        //  Record when this method is executed pos Value 
        const pos_backup = this.pos;
        //  When the tail doesn't start stopTag When , It means you haven't scanned stopTag
        //  Write && It is necessary to , Because you can't find , Then the search should stop in the end 
        while (!this.eos() && this.tail.indexOf(stopTag) != 0) {
            this.pos++;
            //  Change the tail to start with the character of the current pointer , All characters to the end 
            this.tail = this.templateStr.substring(this.pos);
        }

        return this.templateStr.substring(pos_backup, this.pos);
    }

    //  Has the pointer reached the end , Returns a Boolean value .end of string
    eos() {
        return this.pos >= this.templateStr.length;
    }
};


 Copy code 

1.2 Handwriting will HTML Turn into Tokens, Will be scattered Tokens Nested

image.png

image.png

// src/parseTemplateToTokens.js

import Scanner from './Scanner.js';
import nestTokens from './nestTokens.js';
/*  Change template string to tokens Array  */
export default function parseTemplateToTokens(templateStr) {
    var tokens = [];
    //  Create a scanner 
    var scanner = new Scanner(templateStr);
    var words;
    //  Let the scanner work 
    while (!scanner.eos()) {
        //  Collect the text before the start tag appears 
        words = scanner.scanUtil('{{');
        if (words != '') {
            //  Try to write and remove the spaces , Intelligent judgment is the space of ordinary words , Or the space in the label 
            //  The space in the label cannot be removed , such as <div class="box"> Can't get rid of class The space in front of it 
            let isInJJH = false;
            //  Blank string 
            var _words = '';
            for (let i = 0; i < words.length; i++) {
                //  Determine whether it is in the label 
                if (words[i] == '<') {
                    isInJJH = true;
                } else if (words[i] == '>') {
                    isInJJH = false;
                }
                //  If this is not a space , Splicing 
                if (!/\s/.test(words[i])) {
                    _words += words[i];
                } else {
                    //  If this is a space , Only when it's in the label , Just splice 
                    if (isInJJH) {
                        _words += ' ';
                    }
                }
            }
            //  Save up , Remove space 
            tokens.push(['text', _words]);
        }
        //  Over double braces 
        scanner.scan('{{');
        //  Collect the text before the start tag appears 
        words = scanner.scanUtil('}}');
        if (words != '') {
            //  This words Namely {{}} Something in the middle . Judge the first character 
            if (words[0] == '#') {
                //  Save up , From the subscript for 1 Start saving items , Because the index is zero 0 The item is #
                tokens.push(['#', words.substring(1)]);
            } else if (words[0] == '/') {
                //  Save up , From the subscript for 1 Start saving items , Because the index is zero 0 The item is /
                tokens.push(['/', words.substring(1)]);
            } else {
                //  Save up 
                tokens.push(['name', words]);
            }
        }
        //  Over double braces 
        scanner.scan('}}');
    }

    //  Returns the data collected by folding tokens
    return nestTokens(tokens);
}
 Copy code 
// src/nestTokens.js
/*  The function is folding tokens, take # and / Between tokens Can be integrated , As its subscript 3 The item  */
export default function nestTokens(tokens) {
    //  Result array 
    var nestedTokens = [];
    //  The stack structure , Store small tokens, To the top of the stack ( Near the port , The latest entry ) Of tokens The current operation in the array tokens Small array 
    var sections = [];
    //  The collector , Born to nestedTokens Result array , Reference type value , So it points to the same array 
    //  The direction of the collector changes , When I meet # When , The collector will point to this token The subscript of is 2 New array 
    var collector = nestedTokens;

    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];

        switch (token[0]) {
            case '#':
                //  Put this in the collector token
                collector.push(token);
                //  Push 
                sections.push(token);
                //  The collector needs to be replaced . to token Add subscript 2 The item , And let the collector point to it 
                collector = token[2] = [];
                break;
            case '/':
                //  Out of the stack .pop() The item that just popped up is returned 
                sections.pop();
                //  Change the collector to stack structure queue tail ( The end of the team is the top of the stack ) The subscript of that item is 2 Array of 
                collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
                break;
            default:
                //  Never mind the current collector Who is it? , It could be the result nestedTokens, It could be some token The subscript of is 2 Array of , No matter who it is , push collctor that will do .
                collector.push(token);
        }
    }

    return nestedTokens;
};
 Copy code 

1.3 Handwriting will tokens Injection data , It can be interpreted as dom character string

// src/renderTemplate.js
import lookup from './lookup.js';
import parseArray from './parseArray.js';
/*  The function is to make tokens The array becomes dom character string  */
export default function renderTemplate(tokens, data) {
    //  Result string 
    var resultStr = '';
    //  Traverse tokens
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        //  Look at the type 
        if (token[0] == 'text') {
            //  Spell it together 
            resultStr += token[1];
        } else if (token[0] == 'name') {
            //  If it is name type , Then use its value directly , Of course want to use lookup
            //  Because this is “a.b.c” Comma form 
            resultStr += lookup(data, token[1]);
        } else if (token[0] == '#') {
            resultStr += parseArray(token, data);
        }
    }

    return resultStr;
}
 Copy code 

image.png

// src/lookup.js
/*  The function is that you can dataObj In the object , Look for symbols with continuous points keyName attribute   such as ,dataObj yes  { a: { b: { c: 100 } } }  that lookup(dataObj, 'a.b.c') The result is 100  Don't deceive everyone , This function is the interview question of a large factory  */
export default function lookup(dataObj, keyName) {
    //  have a look keyName Is there a dot symbol in the , But it can't be . In itself 
    if (keyName.indexOf('.') != -1 && keyName != '.') {
        //  If there is a symbol , Then take it apart 
        var keys = keyName.split('.');
        //  Set a temporary variable , This temporary variable is used for turnover , Go down one layer at a time .
        var temp = dataObj;
        //  Every floor , Just set it as a new temporary variable 
        for (let i = 0; i < keys.length; i++) {
            temp = temp[keys[i]];
        }
        return temp;
    }
    //  If there's no dot in it 
    return dataObj[keyName];
};
 Copy code 
// src/parseArray.js
import lookup from './lookup.js';
import renderTemplate from './renderTemplate.js';

/*  Handling arrays , combination renderTemplate Implement recursion   Be careful , The parameters received by this function are token! instead of tokens! token What is it? , It's a simple one ['#', 'students', [ ]]  This function is called recursively renderTemplate function , How many calls ???  Don't cover up ! The number of calls is determined by data decision   such as data It's in the form of : { students: [ { 'name': ' Xiao Ming ', 'hobbies': [' swimming ', ' Bodybuilding '] }, { 'name': ' Xiaohong ', 'hobbies': [' football ', ' blue ball ', ' badminton '] }, { 'name': ' cockroach ', 'hobbies': [' having dinner ', ' sleep '] }, ] };  that parseArray() Functions are called recursively renderTemplate function 3 Time , Because the array length is 3 */

export default function parseArray(token, data) {
    //  Get the overall data data The part of the array to use 
    var v = lookup(data, token[1]);
    //  Result string 
    var resultStr = '';
    //  Traverse v Array ,v It must be an array 
    //  Be careful , The following loop is probably the most difficult to think about in the whole package 
    //  It is traversal data , Instead of traversing tokens. There are several pieces of data in the array , You have to traverse a few .
    for(let i = 0 ; i < v.length; i++) {
        //  Here we need to make up one “.” attribute 
        //  Splicing 
        resultStr += renderTemplate(token[2], {
            ...v[i],
            '.': v[i]
        });
    }
    return resultStr;
};
 Copy code 
// www/index.html
<!DOCTYPE html>
<html lang="en">

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

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

    <script src="/xuni/bundle.js"></script>

    <script> //  Template string  var templateStr = ` <div>     <ul> {{#students}}         <li class="myli">  Student {{name}} My hobby is              <ol>                 {{#hobbies}}                 <li>{{.}}</li>                 {{/hobbies}}             </ol>         </li>         {{/students}}     </ul> </div> `; //  data  var data = { students: [ { 'name': ' Xiao Ming ', 'hobbies': [' Programming ', ' swimming '] }, { 'name': ' Xiaohong ', 'hobbies': [' Reading a book ', ' To play the piano ', ' Drawing a picture '] }, { 'name': ' cockroach ', 'hobbies': [' exercise '] } ] }; //  call render var domStr = SSG_TemplateEngine.render(templateStr, data); console.log(domStr); //  Render the tree  var container = document.getElementById('container'); container.innerHTML = domStr; </script>
</body>

</html>

 Copy code 
// src/index.js
import parseTemplateToTokens from './parseTemplateToTokens.js';
import renderTemplate from './renderTemplate.js';

//  Provide global SSG_TemplateEngine object 
window.SSG_TemplateEngine = {
    //  rendering method 
    render(templateStr, data) {
        //  call parseTemplateToTokens function , Let the template string become tokens Array 
        var tokens = parseTemplateToTokens(templateStr);
        //  call renderTemplate function , Give Way tokens The array becomes dom character string 
        var domStr = renderTemplate(tokens, data);
        
        return domStr;
    }
};
 Copy code 

1.4 Conclusion

• Mustache The bottom floor is so beautiful ! tokens The significance of is self-evident . without token, So the circular form of the array , It's hard to deal with . We passed this lesson , I really learned a lot , The field of vision becomes wider , I feel there are more things in my stomach ;

• stay Mustache Source code , also Context Classes and Writer class , In our code demonstration , They have been simplified , However, it does not affect the implementation of trunk functions . Our this “ Simplified version of ” The code is well worth handwriting , You will benefit a lot ! Of course , If you have the energy , You can study this thoroughly “ Simplified version of ” After code , I'm right Mustache Learn from the original package

copyright notice
author[Portugal and bear],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/04/202204291302485107.html

Random recommended