从零开始,编写一个HTML模版引擎(一)

导航

从零开始,编写一个HTML模版引擎(二)

一、概览

从今天开始,让我们从头来写一个带diff的前端模版引擎,该功能会分成几期做完。

首先我们看看这个系列的目标是什么,如下图:
在这里插入图片描述

今天我们主要实现基础的模版解析。


二、场景分析

首先,假设一下使用场景:

  • 我们需要一个parser() 方法。
  • 将模版字符串传入。
  • parser()方法在解析html标签的各个阶段调用我们的回调函数,方便我们生成预期的结构。

下面用代码演示一下使用方法:

	import { parser } from '@jax/html-parser'
	// api参照 https://www.npmjs.com/package/htmlparser2
	parser('<div>Jax</div>', {
		onStart(tag: string, attr: string) {},
		onText(text) {},
		onEnd(tag) {}
	})

ok,知道怎么用了之后,我们就来实现parser()方法吧。

三、实现parser解析HTML

3.1 实现逻辑

解析html模版的方式有很多,此处我们采用正则匹配的方法,下面让我们图来简单说明下编码逻辑:
在这里插入图片描述
确定了代码逻辑,接下来我们准备需要的几个正则表达式

3.2 四个正则表达式

// 匹配起始标签和属性
const startReg = /^<(\w+)\s?(.*?)\/?>/
// 匹配结束标签属性
const endReg = /^<\/(\w+)>/
// 匹配标签内容
const textReg = /^>?(.*?)</
// 空字符
const blockReg = /^>?\s+\S/

正则表达式准备好后,接下来实现主方法

3.3 实现parser()

export const parser = (html: string, options: optionsType) => {
  // 获取传入的几个回调函数
  const { onStartTag, onText, onEndTag } = options
  html = html.trim()
  let matched = null
  // 扫描文本,默认我们认为模版字符串的最后一个字符一定是>,所以此处>1即可
  while (html.length > 1) {
    // 判断是否为结束标签
    if (html.startsWith('</')) {
      matched = matchByReg(endReg)
      onEndTag && onEndTag(matched[1]) // 执行回调
    } else if (html.startsWith('<')) {
      // 开始标签
      matched = matchByReg(startReg)
      onStartTag && onStartTag(matched[1], attrParser(matched[2])) // 执行回调
      
      // 特殊处理下自闭合标签
      if (matched[0].endsWith('/>')) {
        onEndTag && onEndTag(matched[1]) // 执行回调
      }
    } else if (html.startsWith('>')) {
      // 去掉可能存在的空格
      matchByReg(blockReg, false)
      if (!html.startsWith('<')) {
        // 获取标签中的内容
        matched = matchByReg(textReg, false)
        if (matched[1]) {
          onText && onText(matched[1]) // 执行回调
        }
      }
    }
  }
}

代码中用于匹配和截取的操作我们给提成matchByReg()方法。注意,此方法需要操作html模版字符串,所以该方法是声明在parser()方法中

const matchByReg = (
   reg: RegExp,
   isThrow: boolean = true
 ): RegExpMatchArray => {
   let matched = html.match(reg)
   if (matched === null) {
     if (!isThrow) {
       return []
     }
     return throwError()
   }
   // 匹配到了后处理字符串
   let len = matched.index! + matched[0].length
   html = html.substring(len - 1)
   return matched
 }

此时像我们在场景分析中那样使用,parser() 方法已经能够正常工作了,下一期我们来处理目标结构生成

循序渐进,不忘初心,我们明天见

有问题请留言
或发邮件: liujax@126.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值