模板引擎的实现原理

发布于 2022-09-20 12:36:51 字数 3001 浏览 153 评论 0

模板引擎是通过字符串拼接得到的

let template = 'hello <% name %>!'
let template = 'hello ' + name + '!'

字符串是通过 new Function 执行的

let name = 'world'
let template = `
  let str = 'hello ' +  name  + '!'
  return str
`
let fn = new Function('name', template)
console.log(fn(name)) // hello world!

将模板转换为字符串并通过函数执行返回

let template = 'hello <% name %>!'
let name = 'world'
function compile (template) {
  let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
    return `' + ${code} + '`
  })
  html = `let str = '${html}'; return str`
  return new Function('name', html)
}
let str = compile(template)
console.log(str(name)) // hello world!

函数只能接收一个 name 变量作为参数,功能太单一了,一般会通过对象来传参,with 来减少变量访问。

with 功能

let params = {
  name: '张三',
  age: 18
}
let str = ''
with (params) {
  str = `用户${name}的年龄是${age}岁`
}
console.log(str) // 用户张三的年龄是18岁

实现简单的模板引擎

let template = 'hello <% name %>!'
let name = 'world'
function compile (template) {
  let html = template.replace(/<%([\s\S]+?)%>/g, (match, code) => {
    return `' + ${code.trim()} + '`
  })
  html = `'${html}'`
  html = `let str = ''; with (params) { str = ${html}; } return str`
  return new Function('params', html)
}
let str = compile(template)
console.log(str({ name })) // hello world!

完善部分功能

let str = `<ul>
<% users.forEach(function(user){ %>
<li><%= user %></li>
<% }) %>
</ul>`;

let render = function (complied, data) {
  // 转译HTML
  let escape = (html) => {
    return String(html)
    .replace(/&(?!\w+;)/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
  };
  return complied(data, escape);
}

// 子模板include导入
var VIEW_FOLDER = './views';
var files = {};
var preComplie = function (str) {
  var  replaced  =  str.replace(/<%\s+(include.*)\s+%>/g, function (match, code) {
    var partial = code.split(/\s/)[1];
    if (!files[partial]) {
      files[partial] = fs.readFileSync(path.join(VIEW_FOLDER, partial), 'utf8');
    }
    return files[partial];
  });
  // 多层嵌套,继续替换
  if (str.match(/<%\s+(include.*)\s+%>/)) {
    return preComplie(replaced);
  } else {
    return replaced;
  }
};

function complie (str) {
  str = preComplie(str);
  let tpl = str.replace(/<%=([\s\S]+?)%>/g, (match, code) => {
    // 需要转义
    return `' + escape(${code.trim()}) + '`
  }).replace(/<%-([\s\S]+?)%>/g, (match, code) => {
    // 正常输出
    return `' + obj.${code.trim()} + '`
  }).replace(/<%([\s\S]+?)%>/g, (match, code) => {
    // 可执行的
    return `'; ${code.trim()}; tpl += '`
  }).replace(/\r\n/g, '').replace(/\n/g, '');
  tpl = `tpl='${tpl}';`
  tpl = `let tpl = ''; with (obj) { ${tpl} } return tpl;`
  return new Function('obj', 'escape', tpl);
}

let res = render(complie(str), {users: [1,2,3]})

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

天荒地未老

暂无简介

文章
评论
26 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文