天天看點

82.精讀《Htm - Hyperscript 源碼》1 引言2 概要3 精讀4 總結

1 引言

htm 是 preact 作者的新嘗試,利用原生 HTML 規範支援了類 JSX 的寫法。

2 概要

htm 沒有特别的文檔,假如你用過 JSX,那隻需要記住下面三個不同點:

  • className

    ->

    class

  • 标簽引号可選(回歸 html 規範):

    <div class=foo>

  • 支援 HTML 模式的注釋:

    <div><!-- don't delete this! --></div>

另外支援了可選結束标簽、快捷元件 End 标簽,不過這些自己發明的文法不建議記憶。

用法也沒什麼特别的地方,你可以利用 HTML 原生規範,用直覺去寫 JSX:

html`
  <div class="app">
    <${Header} name="ToDo's (${page})" />
    <ul>
      ${todos.map(
        todo => html`
          <li>${todo}</li>
        `
      )}
    </ul>
    <button onClick=${() => this.addTodo()}>Add Todo</button>
    <${Footer}>footer content here<//>
  </div>
`;           

複制

很顯然,由于跳過了 JSX 編譯,換成了原生的 Template Strings ,是以所有元件、屬性部分都需要改成

${}

文法,比如:

<${Header}>

這種寫法略顯别扭,但整體上還是蠻直覺的。

你不一定非要用在項目環境中,但當你看到這種文法時,内心一定情不自禁的 WoW,竟然還有這種寫法!

下面将帶你一起分析 htm 的源碼,看看作者是如何做到的。

3 精讀

你可以先自己嘗試閱讀,源碼加上注釋一共 90 行:源碼。

好了,歡迎繼續閱讀。

首先你要認識到,

htm

+

vhtml

才等于你上面看到的 DEMO。

Htm

Htm

是一個 dom template 解析器,它可以将任何 dom template 解析成一顆文法樹,而這個文法樹的結構是:

interface VDom {
  tag: string;
  props: {
    [attrKey: string]: string;
  };
  chindren: VDom[];
}           

複制

我們看一個 demo:

function h(tag, props, ...children) {
  return { tag, props, children };
}

const html = htm.bind(h);

html`
  <div>123</div>
`; // { tag: "div", props: {}, children: ["123"] }           

複制

那具體是怎麼做文法解析的呢?

其實實作方式有點像腦經急轉彎,畢竟解析 dom template 是浏覽器引擎做的事,規範也早已定了下來,有了規範和實作,當然沒必要重複造輪子,辦法就是利用 HTML 的 AST 生成我們需要的 AST。

首先建立一個

template

元素:

const TEMPLATE = document.createElement("template");           

複制

再裝輸入的 dom template 字元串塞入(作者通過正則,機智的将自己支援的額外文法先轉化為标準文法,再交給 HTML 引擎):

TEMPLATE.innerHTML = str;           

複制

最後我們會發現進入了

walk

函數,通過

localName

拿到标簽名;

attributes

拿到屬性值,通過

firstChild

nextSibling

周遊子元素繼續走

walk

,最後

tag

props

children

三劍客就生成了。

可能你還沒看完,就已經結束了。筆者分析這個庫,除了告訴你作者的機智思路,還想告訴你的是,站在巨人的肩膀造輪子,真的事半功倍。

VDom

VDom 是個抽象概念,它負責将實體文法樹解析為 DOM。這個工具可以是 preact、vhtml,或者由你自己來實作。

當然,你也可以利用這個 AST 生成 JSON,比如:

import htm from "htm";
import jsxobj from "jsxobj";

const html = htm.bind(jsxobj);

console.log(html`
  <webpack watch mode=production>
    <entry path="src/index.js" />
  </webpack>
`);
// {
//   watch: true,
//   mode: 'production',
//   entry: {
//     path: 'src/index.js'
//   }
// }           

複制

讀到這,你覺得還有哪些 “VDom” 可以寫呢?其實任何可以根據

tag

props

children

推導出的結構都可以寫成解析插件。

4 總結

htm 是一個教科書般借力造論子案例:

  • 利用

    innerHTML

    會自動生成的标準 AST,解析出符合自己規範的 AST,這其實是進一步抽象 AST。
  • 利用原有庫進行 DOM 解析,比如 preact 或 vhtml。
  • 基于第二點,是以可以生成任何目标代碼,比如 json,pdf,excel 等等。

不過這也帶來了一個問題:依賴原生 DOM API 會導緻無法運作在 NodeJS 環境。

想一想你現在開發的工具庫,有沒有可以借力的地方呢?有哪些點可以通過借力做得更好進而實作雙赢呢?歡迎留下你的思考。