从模板编译到虚拟dom

将html字符串转换为AST(parseHtmlToAst.js)

const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
const dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp}]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`);
const startTagClose = /^\s*(\/?)>/;
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);

export function parseHtmlToAst(html) {
  let root,
    currentParent,
    stack = [];
  while (html) {
    let textEnd = html.indexOf('<');
    if (textEnd === 0) {
      const startTagMatch = parseStartTag();
      if (startTagMatch) {
        start(startTagMatch);
        continue;
      }
      const endTagMatch = html.match(endTag);
      if (endTagMatch) {
        advance(endTagMatch[0].length);
        end();
      }
    }
    if (textEnd > 0) {
      chars(html.substring(0, textEnd));
      advance(textEnd);
    }
  }

  function parseStartTag() {
    let attr, end;
    const start = html.match(startTagOpen);
    if (start) {
      const startTagMatch = {
        tagName: start[1],
        attrs: [],
      };
      advance(start[0].length);
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
        startTagMatch.attrs.push(attr[0].trim());
        advance(attr[0].length);
      }
      if (end) {
        advance(end[0].length);
        return startTagMatch;
      }
    }
  }

  function advance(n) {
    html = html.substring(n);
  }

  function start(startTagMatch) {
    const ele = createASTElement(startTagMatch.tagName, startTagMatch.attrs);
    if (!root) {
      root = ele;
    }
    currentParent = ele;
    stack.push(ele);
  }

  function end() {
    const ele = stack.pop();
    currentParent = stack[stack.length - 1];
    if (currentParent) {
      ele.parent = currentParent;
      currentParent.children.push(ele);
    }
  }

  function chars(containerText) {
    const text = containerText.trim();
    if (text) {
      currentParent.children.push({
        type: 3,
        text,
      });
    }
  }

  function createASTElement(tagName, attrs) {
    return {
      type: 1,
      tag: tagName,
      attrs,
      parent,
      children: [],
    };
  }
  return root;
}

返回一个固定的数据结构(h函数)(h.js)

export function h(sel, data = {}, children) {
  let text, selList;
  if (children) {
    if (Array.isArray(children)) {
      selList = changeArr(children);
    } else if (primitive(children)) {
      text = children;
    } else if (children.sel) {
      selList = [children];
    }
  }
  return vnode(sel, data, selList, text, undefined);
}

function primitive(s) {
  return typeof s === 'string' || typeof s === 'number';
}

function changeArr(children) {
  const newArr = [];
  for (let i = 0; i < children.length; ++i) {
    if (primitive(children[i]))
      newArr[i] = vnode(undefined, undefined, undefined, children[i], undefined);
    else newArr[i] = children[i];
  }
  return newArr;
}

function vnode(sel, data, children, text, elm) {
  const key = data === undefined ? undefined : data.key;
  return { sel, data, children, text, elm, key };
}

将虚拟dom渲染成真实dom(patch.js)

export function patch(realDom, vnode) {
  // 根据虚拟dom,生成真实dom
  const el = createRealDom(vnode);
  console.log(el);
  // 将真实dom挂载与dom树上
  realDom.appendChild(el);
}

function createRealDom(vnode) {
  const { sel, data, children, text } = vnode;
  let ele;
  if (sel) {
    ele = createElement(sel, data);
  } else {
    ele = createElement('span', data);
  }
  if (Array.isArray(children)) {
    for (let index = 0; index < children.length; index++) {
      appendChild(ele, createRealDom(children[index]));
    }
  } else {
    appendChild(ele, createTextNode(text));
  }
  return ele;
}

function createElement(tagName, options) {
  return document.createElement(tagName, options);
}
function createElementNS(namespaceURI, qualifiedName, options) {
  return document.createElementNS(namespaceURI, qualifiedName, options);
}
function createTextNode(text) {
  return document.createTextNode(text);
}
function appendChild(node, child) {
  node.appendChild(child);
}

调用(main.js)

import { parseHtmlToAst } from './parse/parseHtmlToAst';
import { h } from './h';
import { patch } from './patch';
const html = `  <div id='div1' class='my ct'> <div id='brother' class='my ct' style='width: 100%; color: red;' >bbb</div> <span id='span' class='my ct'><div id='div2' class='my ct'>aaa</div></span></div>`;

const ast = parseHtmlToAst(html.trim());
const vNode = getVNode(ast);
function getVNode(ele) {
  const vNode = {};
  const props = initProps(ele.attrs);
  const hData = h(ele.tag, props, ele.text);
  Object.assign(vNode, hData);
  if (Array.isArray(ele.children)) {
    vNode.children = [];
    for (let index = 0; index < ele.children.length; index++) {
      vNode.children[index] = getVNode(ele.children[index]);
    }
  }
  return vNode;
}
function initProps(data) {
  const prop = {};
  if (Array.isArray(data)) {
    for (let index = 0; index < data.length; index++) {
      const propArr = data[index].split('=');
      prop[propArr[0]] = delQuotationMark(propArr[1]);
      if (propArr[0] === 'style') {
        prop[propArr[0]] = initStyle(prop[propArr[0]]);
      }
    }
  }
  return prop;
}

function initStyle(style) {
  const styleObj = {};
  Array.from(style.split(';')).forEach((item) => {
    const styleArr = item.trim().split(':');
    if (styleArr[0]) {
      styleObj[styleArr[0]] = String(styleArr[1].trim());
    }
  });

  return styleObj;
}

function delQuotationMark(value) {
  return value.replace(/\'/g, '').replace(/\"/g, '');
}

const container = document.getElementById('container');
patch(container, vNode);

容器(index.html)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="container" style="width: 100%; color: red"></div>
    <script src="js/bundle.js"></script>
  </body>
</html>

打包配置(webpack.config.js)

const path = require('path');

module.exports = {
  // 入口
  entry: './src/index.js',
  // 出口
  output: {
    // 虚拟打包路径
    publicPath: 'js',
    // 打包出来的文件名
    filename: 'bundle.js',
  },
  devServer: {
    // 端口号
    port: 8080,
    // 静态资源文件夹
    contentBase: 'www',
  },
};

(package.json)

{
  "name": "snabbdom-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.44.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.2"
  },
  "dependencies": {
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值