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

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

导航

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

一、概览

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

首先我们看看这个系列的目标是什么,如下图:

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


二、场景分析

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

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

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

	import { parser } from '@jax/html-parser'
	// api参照 https://www.npmjs.com/package/htmlparser2
	parser('Jax', {
		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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/941422.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-17
下一篇 2022-05-17

发表评论

登录后才能评论

评论列表(0条)

保存