給Markdown新增視訊支援

Kaciras發表於2020-07-19

本文轉載自我的部落格:https://blog.kaciras.com/article/18/add-video-support-to-markdown

都2020年了,現在最流行的就是什麼直播彈幕短視訊,你的部落格要是還不支援插視訊那可就OUT啦!

我的部落格目前使用Markdown寫文,可惜它原生的語法並不支援視訊,於是只能自己來實現這功能了。

視訊好處都有啥?

視訊是個好東西啊,它要不好現在的直播彈幕短視訊怎麼火的……咳咳,扯遠了,就說寫部落格,比如寫教程啊總會遇到需要動態演示的東西吧,比如我自己的純CSS解決圖片載入的佈局移動問題裡一開頭的動圖(其實是視訊啦),要用圖片或者文字來說明文字下移的現象肯定沒有動態的演示好。

另外玩過Twitter的都知道它裡面插入的GIF圖會轉換為視訊,GIF是1987年發明的東西,在我看來早是就該進入垃圾堆的技術,因為當年瀏覽器不支援視訊才得以流行。動圖這一技術完全能被視訊所替代,視訊本身就不是一連串影像的序列嗎(當然還包括聲音)。

在效能上,H.246編碼的視訊體積僅為GIF的13分之一,雖然GIF也有gifsicle能壓縮一下,但效果仍不如視訊。

從我的實際經驗來看,技術類文章裡大部分動態演示都來源於錄屏,錄屏軟體生成的本來就是視訊格式,把它們轉GIF多此一舉。綜上所述,視訊的支援是一個現代化部落格必需的功能

語法的選擇

Markdown版本演進已經有其它的文章寫過了https://juejin.im/post/5baa5b346fb9a05d2d0225cc#heading-6

主要的幾個Markdown版本原生都不支援視訊,我不知道它的作者是怎麼想的,如此重要的功能竟然能沒有。既然官方沒有,那就自己做唄,於是種各樣的實現方案就跑了出來,按照本人強迫症的做法當然要對比一番

直接插HTML


這是我看到的最多的做法,其優勢就是簡單,現有的轉換庫都支援寫HTML,但我認為這種方式並不好。

  • XSS風險:若是自己用還好說,一旦給評論之類的第三方輸入用上,你都猜不到他們會搞些什麼出來。
  • 擴充套件性差:一旦寫死,以後想改動下輸出的HTML可就麻煩了,需要把所有文章都掃一遍,本部落格就遇到過需要改動渲染結果的情況。
  • 可讀性差:Markdown作為輕量級標記語言,掃一眼即可輕鬆Parse是其一大優勢,一旦混入重量級的HTML則可讀性大打折扣。

這缺點太多,所以我決定還是得用Markdown的方式來做。

GitLab Flavored Markdown

GitLab Flavored Markdown(下稱GFM)是Markdown的一種修改版,它複用了圖片的語法,以副檔名來區分媒體的型別,比如![label](foobar.mp4)因為連結是.mp4結尾所以渲染為視訊。

GFM的支援也很廣泛,實現又簡單,還有GitLab背書,自然也是個不錯的選擇。

但它的缺點也很明顯,強制了連結的檔名必須是視訊常用的副檔名,然而並不是所有連結都是如此,Twitter的視訊連結就沒有副檔名。另外既然都修改了原始的Markdown語義,何不直接另起一個新語法呢?

自己編個語法

關於自定義的語法有很多討論,我認為比較好的一種是使用通用指令語法,它的格式是@<指令型別>[...](..){...}這樣的,前面的@可以換成別的,指令型別用於區分視訊、音訊、GIF視訊等,後面三個括號裡的內容可以自由發揮。

最終我決定使用這種語法,它跟原生的圖片語法一樣簡潔,又給足了自由發揮的餘地。

通常來說,新的語法最好還是跟現有的保持相似,這裡就以語法比較像的圖片為基準。圓括號仍然跟圖片一樣包含視訊的連結,方括號裡填標籤(GIF)或者poster(視訊)。

通用指令語法裡,花括號用來放置key = "val"這樣的鍵值對,但它們同樣可以放在連結URL的引數上,而且目前圖片就是這麼做的,為了保持一致,我選擇不要這個花括號部分。

指令型別包含GIF視訊和普通視訊,另外Markdown同樣不支援插入音訊這裡也給補上,所以最終的語法為:

  • @audio[](音訊連結) 插入一個音訊。
  • @gif[標籤](視訊連結) 插入視訊,並儘可能模仿GIF圖。
  • @video[視訊封面](視訊連結) 插入普通視訊。

解析器的實現

我的部落格使用markdown-it來轉換Markdown為HTML,markdown-it 的流程分為解析和渲染兩部分,所以要給這兩個地方編寫自己的函式實現。

首先是怎麼識別@<指令型別>[...](..)這種文字呢,如果不考慮轉義的話倒是一個正則就能搞定,但問題是如果標籤裡出現了方括號,或者連結裡有圓括號咋辦?當然是要轉義了,Markdown對括號的轉義方式有兩種:配對計數和斜槓轉義,其中配對計數需要一個變數來儲存左括號數量挺麻煩,而且斜槓轉義完全能用於所有場景,但反過來配對計數卻無法用於右括號單獨出現的情況(雖然不常見)。

綜上所述,我決定不支援計數了,斜槓轉義用一個前向環視(?<!\\)就能解決,再給指令部分加點限制,最後的正則如下:

  • 指令部分:([a-z][a-z0-9\-_]*)
  • 標籤部分:\[(.*?)(?<!\\)]
  • 連結部分:\((.*?)(?<!\\)\)

把它們三個連起來就可以匹配通用指令語法啦。

Markdown有塊block和行內inline兩種結構,原始的圖片語法是屬於行內的,這可以實現圖文混排,但在使用中發現我並沒有圖文混排的需求,我部落格裡的圖片都是單獨一行。所以我決定新的語法作為塊結構,這樣可以降低解析函式被呼叫的頻率,提升點效能。

最後要注意一下的是反轉義和XSS檢查,這些函式在markdown-it庫裡已有提供。

解析器程式碼見kaciras-blog/web-server/packages/server/lib/markdown-media.ts

不同的渲染目標

Markdown渲染出來的HTML是跟場景相關的,比如在RSS裡渲染的結果應儘量簡單,畢竟閱讀器的樣式是沒法由我來控制的;而在我的部落格網站裡,會有一些額外的樣式和元素來展現更好的效果,比如提前固定寬高比防止佈局移動、居中等。

在我的部落格,GIF視訊通過隱藏控制皮膚、靜音、給下面加標籤、以及IntersectionObserver實現的自動播放/暫停,實現了跟GIF圖片一樣的效果。另外由於RSS閱讀器不會載入我的部落格的樣式表,也不會執行JS,所以RSS閱讀器裡的GIF視訊只能跟普通視訊一樣。

除了這倆之外,我還準備讓評論系統也使用Markdown(暫未實現),這又是一個新的渲染目標,使用者評論屬於第三方輸入,對其的渲染必須加入一些限制以防濫用。

對無法控制的前端,渲染實現跟解析器寫在一起,見上面的連結。

我部落格的渲染實現見kaciras-blog/website/blob/master/src/markdown/media.js

最終效果

SegmentFault不支援插入視訊,所以這裡展示不了,可以去我的部落格裡看效果:

https://blog.kaciras.com/article/18/add-video-support-to-markdown#最終效果

相關文章