用JS实现一个模板引擎

2023-04-22 16:25:20 浏览数 (2)


现成的模板引擎

开始手写之前,我们先看看模板引擎应该是什么样的,在用koa开发后台服务的时候,我们用过ejs模板引擎,其作用是把模板渲染成html代码。下面是一个具体的使用例子。

安装

代码语言:javascript复制
npm install ejs

使用示例

在koa中使用ejs模板引擎。

index.js

代码语言:javascript复制
const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const app = new Koa()

// 加载模板引擎
app.use(views(path.join(__dirname, './view'), {
  extension: 'ejs'
}))

app.use( async ( ctx ) => {
  let title = 'hello koa2'
  await ctx.render('index', {
    title,
  })
})

app.listen(3000)

view/index.ejs

代码语言:javascript复制
<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
</head>
<body>
    <h1><%= title %></h1>
    <p>EJS Welcome to <%= title %></p>
</body>
</html>

除了ejs,还有很多其他的模板引擎。

手写简单的模板引擎

那这些模板引擎具体是怎么实现的呢?

下面我们来手写一个简单的类ejs模板引擎。

需求分析

实现模板引擎先要定义模板的语法,这里我们就重新不定义了,直接使用ejs的语法。

我们只实现最简单的几个语法:

  • <% '脚本' 标签,用于流程控制,无输出。
  • <%= 输出数据到模板(输出是转义 HTML 标签)
  • %> 一般结束标签

设计思路

先贴一下待编译的模板。

代码语言:javascript复制
let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i  ) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`;

思路是把模板转换为JavaScript代码并执行,输出转码后的html代码。

观察一下上面的代码,我们可以先推出来,转换后的JavaScript代码。

代码语言:javascript复制
echo(`<ul>`); //echo用于打印出html代码
  for(var i=0; i<data.supplies.length; i  ) { 
    echo(`<li>`); 
    echo(  data.supplies[i]  ); 
    echo(`</li>`); 
  }  
echo(`</ul>`);

下面是echo的实现。

代码语言:javascript复制
var output = "";

function echo(html){
    output  = html;
}

我们最后生成的,用于输出html的JavaScript代码是下面这样的。

代码语言:javascript复制
(function parse(data){
  
  // stores the parsed output
  var output = "";
  
  // appends HTML to the parsed template
  function echo(html){
    output  = html;
  }
  
  // contains echos, etc
  echo(`<ul>`); 
  for(var i=0; i<data.supplies.length; i  ) { 
    echo(`<li>`); 
    echo(  data.supplies[i]  ); 
    echo(`</li>`); 
  }  
  echo(`</ul>`);
  
  return output;
})

具体实现

下面列一下对不同字符串的处理方式。

  • 不在插值标签里面的字符,直接输出为html代码。
  • <% %>里面的字符,保留为js逻辑
  • <%= %>里面的字符,保留js逻辑,且其值输出为html代码。

对这些处理方式,着手实现。

正则/<%=(. ?)%>/g匹配第三种类型的字符串,替换为n echo( $1 ); n

正则/<%([sS] ?)%>/g匹配第二种类型的字符串,替换为n $1 n

其他不是插值的字符,直接n echo( $1 ); n,由于正则取反比较复杂,这里巧妙转换一下写法,改为在开头和每个类型二三的结尾加一个 "echo(`",结尾加一个结束符号,也能达到统一的结果。

最终完整的代码如下:

代码语言:javascript复制
function compile(template){
  const evalExpr = /<%=(. ?)%>/g;
  const expr = /<%([sS] ?)%>/g;
  template = template
    .replace(evalExpr, '`); n  echo( $1 ); n  echo(`')
    .replace(expr, '`); n $1 n  echo(`');
  template = 'echo(`'   template   '`);';
  let script =
  `(function parse(data){
    let output = "";
    function echo(html){
      output  = html;
    }
    ${ template }
    return output;
  })`;
  return script;
}

let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i  ) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`;

let div = document.getElementById("app");
let parse = eval(compile(template));
console.log(parse)
div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] });

运行一下

参考文章

how-to-write-a-template-compiler-in-javascript

实例:模板编译

0 人点赞