The previous link – the basis of this article – referred to KOA and wrote a rough Web framework in 5 steps
For more information on how to implement the Router, please check out Koa’s easy Router knocking guide
Reference warehouse: point me
Get (path,(CTX,next)=>{ctx.body=… }) are written directly in HTML, which makes the code too difficult to maintain. Templates were invented, and they were used to manage pages. Each HTML is placed in a separate file, making it easy to call and reuse. Here I use EJS syntax to write the template engine middleware.
So, let’s start with the simplest static page
STEP 1 Static page call
Calling the file isn’t too hard, just read it and assign it to ctx.body:
const fs=require("fs")
const path=require("path")
let indexTPL=fs.readFileSync(path.join(__dirname,"/pages/template.ejs"),"utf-8")
ctx.body=indexTPL;
Copy the code
I’m going to focus on logic here, so I use readFileSync as a synchronous method rather than an asynchronous one.
STEP 2 encapsulates a middleware View
Here, we create a new middleware called View for template nesting.
const fs=require("fs")
const path=require("path")
function View(path){
let tpl="";
return async (ctx,next)=>{
tpl = fs.readFileSync(path.join(__dirname,path),"utf-8") ctx.body= tpl await next(); }}Copy the code
We can then apply the middleware directly to the project.
let view=require("./Views")
let router=new Router()
router.get("/",view("/pages/template.ejs"))
Copy the code
or
app.use(view("/pages/template.ejs"))
Copy the code
It works because I’m creating standard middleware
STEP 3 Extract template labels
Why do we use templates?! For dynamic pages, of course! So we need to replace the template tag <%= parameter name %> with the value we want. Templates also need to support functions such as an array loop to fill a list.
So the first step we need is to extract this tag and replace it with our own tag
This allows you to define a special label for placeholders.
That’s right. Extract, replace! So there’s no escaping the regular expression, it’s already on its way to abusing me…
Because there is a big difference between simply assigning and executing a function, I separate them. If you have a better way, please recommend it to me. (Regular dregs shudder)
let allTags=[];
function getTags(){// First fetch the function that needs to be executed, i.e"="Put a pair of tags into the array, and replace the execution function with a placeholder.letoperators = tpl.match(/<%(? ! =)([\s\S]*?) %>([\s\S]*?) The < % (? ! =)([\s\S]*?) %>/ig)||[] operators.forEach((element,index )=> { tpl=tpl.replace(element,`<! --operator${index}-- > `)}); // Select * from '='; // Select '='lettags=tpl.match(/<%=([\s\S]*?) %>/ig)||[] tags.forEach((element,index) => { tpl=tpl.replace(element,`<! --operator${index+operators.length}-- > `)}); AllTags =[...operators,...tags]; }Copy the code
STEP 4 Replace template labels
Now we’re going to do the template substitution, the value that we passed in. The important thing to note here is that we replace allTags one by one with executable JS text, then execute JS, and the resulting string is temporarily stored in an array. And so on the completion of the execution of the previous
The placeholder is replaced.
${} : <%=%> = ${} :
let str="Let TMPL = '<p> string template:${test}< / p > < ul > < li > for loop < / li > < % for (let the user of the users) {% > < li >${user}</li>
<% } %>
</ul>`
return tmpl"
Copy the code
Then we remove the <%%> of the executable function and add the closing string to it, like this:
let str="Let TMPL = '<p> string template:${test}< / p > < ul > < li > for loop < / li > ` for (let the user of the users) + = {TMPL ` < li >${user}</li>`
}
`</ul>`
return tmpl"
Copy the code
But this is a string, so we need to use a method called the Function constructor
We can new a Function and turn the string into an executable JS.
New Function ([arg1[, arg2[,…argN]],] functionBody); new Function ([arg1[, arg2[,…argN]]); Three help us to turn Object into a single parameter to put in.
Here’s an example:
let data={
test:"admin", the users: [1, 2, 3]}Copy the code
Keys (data), extract the field name, and then use the three-point extension operator… , into the test, the users
new Function(... Object.keys(data), method string)Copy the code
Which is the same thing as
new Function(test,users, method string)Copy the code
Let’s merge the strings at the top. This is what the executable template js looks like.
function xxx(test,users){
letTMPL = '<p> String template:${test}</p>
<ul>
<li>forCycle < / li > `for(let user of users){
tmpl+=`<li>${user}</li>`
}
`</ul>`
return tmpl;
}
Copy the code
Feel to become executable JS, the principle is not difficult, is very complex to put together.
Below is the complete execution code:
function render(){// Get the tag getTags(); AllTags =allTags. Map ((e, I)=>{// Start combining the contents of each tag, then turn the text into an executable jslet str = `let tmpl=' '\r\n`;
str += 'tmpl+=`\r\n'; STR + = e / / label to replace the first assignment STR = STR. Replace (/ < % = ([\ s \ s] *?) %>/ig,function () {
return '${'+arguments[1]+'} '}) // Replace the function method, remember the first"`"The closing tag STR = str.replace(/<%([\s\ s]*?) %>/ig,function () {
return '`\r\n'+arguments[1] +"\r\ntmpl+=`"
})
str += '`\r\n return tmpl'; // Extract the key value of an objectfunctionThe parameters of thelet keys=Object.keys(data);
letfnStr = new Function(... keys,str);returnfnStr(... keys.map((k)=>data[k])); }) allTags.forEach((element,index )=> { tpl=tpl.replace(`<! --operator${index}-->`,element)
});
}
Copy the code
STEP + If you want to read files asynchronously, I recommend:
Make readFile a Promise and await it in middleware
If you do not know async/await, popular portal.
const util=require("util")
const fs=require("fs")
const path=require("path")
let readFile=util.promisify(fs.readFile)
function view(p,data){
let tpl="";
let allTags=[];
function getTags() {/ / slightly}function render() {/ / slightly}return async (ctx,next)=>{
tpl = await readFile(path.join(__dirname,p),"utf-8"// Don't forget to run render() instead of the template tag render(); ctx.body=tpl; await next(); }}Copy the code