Open HelloChunWei opened 3 years ago
會有這一篇文章的原因是前陣子去面試的時候,面試官就問了我這個問題:「假設我們用Rich editor 編寫了一篇 blog 文章,他輸出的時候會一大串String,內容都是HTML tag,那你要如何去渲染這串String?」
我當下最直覺的回答是 V-html 直接做輸出。面試官接著問:「我們都知道v-html的實作是利用innerHTML,那可能會暴露出 xss 的危險,有辦法不利用V-html去做渲染嗎?」
其實在當下我是並沒有回答出這題的答案,想當然爾並沒有拿到offer,思考了幾個禮拜後,終於寫出這題的答案了。
不知道有多少人有用到vue的render function? 大多時候運用 templates 就可以完成將近90%的需求,但是 vue 還有提供一個更為彈性的 render function 。
從文件上可以知道render function 可以完全不用寫template,以下面範例來說
<h1>{{ blogTitle }}</h1>
可以直接轉換成:
render() { return h('h1', {}, this.blogTitle) }
以 Vuetify 來說,他們套件的主要寫法就是利用 render function 完成,看看他們的Button寫法。 VBtn
不過 element UI 的寫法就是我們比較熟悉的 single file component。 button
現在我們知道render function後,我們接下來就是要把 String 餵給render function 去渲染。
起初我是在這裡被卡住,我有一段String <div><h1>title</h1></div> 要怎麼樣拆解? 我最一開始的想法是想直接用正規表達去拆,但後來發現超級困難,因為在這 html tag 中間有可能是有 class, id, style 這些屬性的, 如果單純用正規表達會相當的困難。
<div><h1>title</h1></div>
後來我找到了 DOMParser 他會把String 轉換成 HTML object,結果如下:
轉換後要用 web node API 下去操作dom。但實際上實作起來非常不順,例如我要拿到 div 的tag name 必須要透過 element.tagName 取得,接著我還要再一個一個去針對各個html tag,以及屬性轉換成我要的資訊。所以如果我最後要變成以下形式的話,我要處理非常多東西:
return return h('div', {}, [h('h1', {}, 'title')])
不過我在看到上面這段code的時候靈機一動,「這怎麼看起來好像一個東西?」 「我如果能夠把string HTML 轉換成 virtual dom 的格式,然後再用遞迴去跑render function 不就好了?」
我們都知道 virtual dom 的基本格式長這樣:
var node = { tagName: 'div', attributes: [], childNodes: [ { tagName: 'h1', attributes: [], childNodes: [ { nodeName: "#text" value: "title" } ] } ], };
那我們把寫成遞迴function,就可以利用vue render function 渲染出來了:
import { h } from "vue"; function renderNode (nodes) { if (!nodes.tagName) return let childNodes = nodes.childNodes.map((node) => { if (node.tagName) { return this.createEle(node); } // 如果他是純文字的話 return node.value; }); return h(nodes.tagName, {}, childNodes); }
這樣就完成我們要的功能了~
現在知道我們的目標結構後,就是要來想怎麼做轉換了,不過這又回到上面所提到的,難道要用 DOMParser 然後再用 web API 一個一個自己手動轉換嗎?這好像是個方法,但應該有更方便的解法吧?
在這時候我想到一張圖:
對耶~其實 vue 本身就有做過這樣的事情,那他們怎麼轉的?從上面那張圖可以知道:vue 先把template的 html 轉換成 AST tree,然後再去做render。
ok~ 竟然知道關鍵字了那我們就來研究看看,提到 AST 就要提到 AST explorer,這網站可以線上將code轉換成AST tree,這次我是要轉換 HTML,就先切換成HTML。
切換到HTML後往右變看會發現,轉換後的結果不就是我們要的結構嗎?
那們接下來的工作就是把String HTML 轉換成 AST tree,再從中把我們要的東西拿出來,並放入遞迴涵式做渲染。 那 AST explorer 是利用 parse5 這套件去做轉換。
現在我們都知道步驟了,接著就是把它組起來。我就不再一一說明了,直接呈上結果。
成果在這
這一次算是把之前被問倒的問題給解決,後來想想,其實要把這問題回答好會需要很多的知識點:
當然如果沒有想到 AST tree的話,也可以自己土炮一個轉換功能。只是會比較複雜跟麻煩而已。(parse5 的實作code)
前言
會有這一篇文章的原因是前陣子去面試的時候,面試官就問了我這個問題:「假設我們用Rich editor 編寫了一篇 blog 文章,他輸出的時候會一大串String,內容都是HTML tag,那你要如何去渲染這串String?」
我當下最直覺的回答是 V-html 直接做輸出。面試官接著問:「我們都知道v-html的實作是利用innerHTML,那可能會暴露出 xss 的危險,有辦法不利用V-html去做渲染嗎?」
其實在當下我是並沒有回答出這題的答案,想當然爾並沒有拿到offer,思考了幾個禮拜後,終於寫出這題的答案了。
vue的 render function
不知道有多少人有用到vue的render function? 大多時候運用 templates 就可以完成將近90%的需求,但是 vue 還有提供一個更為彈性的 render function 。
從文件上可以知道render function 可以完全不用寫template,以下面範例來說
可以直接轉換成:
以 Vuetify 來說,他們套件的主要寫法就是利用 render function 完成,看看他們的Button寫法。 VBtn
不過 element UI 的寫法就是我們比較熟悉的 single file component。 button
現在我們知道render function後,我們接下來就是要把 String 餵給render function 去渲染。
String HTML的轉換
起初我是在這裡被卡住,我有一段String
<div><h1>title</h1></div>
要怎麼樣拆解? 我最一開始的想法是想直接用正規表達去拆,但後來發現超級困難,因為在這 html tag 中間有可能是有 class, id, style 這些屬性的, 如果單純用正規表達會相當的困難。後來我找到了 DOMParser 他會把String 轉換成 HTML object,結果如下:
轉換後要用 web node API 下去操作dom。但實際上實作起來非常不順,例如我要拿到 div 的tag name 必須要透過 element.tagName 取得,接著我還要再一個一個去針對各個html tag,以及屬性轉換成我要的資訊。所以如果我最後要變成以下形式的話,我要處理非常多東西:
不過我在看到上面這段code的時候靈機一動,「這怎麼看起來好像一個東西?」 「我如果能夠把string HTML 轉換成 virtual dom 的格式,然後再用遞迴去跑render function 不就好了?」
我們都知道 virtual dom 的基本格式長這樣:
那我們把寫成遞迴function,就可以利用vue render function 渲染出來了:
這樣就完成我們要的功能了~
把String HTML轉換成類似 virtual dom 的結構
現在知道我們的目標結構後,就是要來想怎麼做轉換了,不過這又回到上面所提到的,難道要用 DOMParser 然後再用 web API 一個一個自己手動轉換嗎?這好像是個方法,但應該有更方便的解法吧?
在這時候我想到一張圖:
對耶~其實 vue 本身就有做過這樣的事情,那他們怎麼轉的?從上面那張圖可以知道:vue 先把template的 html 轉換成 AST tree,然後再去做render。
ok~ 竟然知道關鍵字了那我們就來研究看看,提到 AST 就要提到 AST explorer,這網站可以線上將code轉換成AST tree,這次我是要轉換 HTML,就先切換成HTML。
切換到HTML後往右變看會發現,轉換後的結果不就是我們要的結構嗎?
那們接下來的工作就是把String HTML 轉換成 AST tree,再從中把我們要的東西拿出來,並放入遞迴涵式做渲染。 那 AST explorer 是利用 parse5 這套件去做轉換。
現在我們都知道步驟了,接著就是把它組起來。我就不再一一說明了,直接呈上結果。
成果在這
結論
這一次算是把之前被問倒的問題給解決,後來想想,其實要把這問題回答好會需要很多的知識點:
當然如果沒有想到 AST tree的話,也可以自己土炮一個轉換功能。只是會比較複雜跟麻煩而已。(parse5 的實作code)