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 much
Creative, 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></script>
Copy the code
4. Loop through nested object arrays and simple arrays
<div id="container"></div>
<h1></h1>
<script></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. script
Template 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"></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
- will
Template string
Compiled intotokens
- will
tokens
In combination withData from the data
And 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 dev
Access:http://localhost:8080/
和 http://127.0.0.1:8080/xuni/bundle.js
As you can seewww/index.html
和 xuni/bundle.js
Contents 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></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></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
- Normal text Spaces are removed
- The whitespace in the tag cannot be removed, for example
<div class="box"><></div>
Can’t removeclass
Space 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