解析Markdown文件生成React組件文檔

前言

最近做的項目使用了微前端框架single-spa。

對於這類微前端框架而言,通常有個utility應用,也就是公共應用,裡面是各個子應用之間可以共用的一些公共組件或者方法。

對於一個團隊而言,項目中公共組件和方法的使用難點不在於封裝不在於技術,很多時候在於團隊內部成員是否都能了解這些組件,以避免重複開發,從而提升團隊效率。

如果是團隊比較小,人員比較穩定的項目組可能還好點,對於團隊比較大,人員流動較快的團隊,這些通用組件和方法往往就被人遺忘在角落,很難再得到有效利用。

因為我所在的項目還在開發初期,並且是新入職也想去熟悉一下當前項目中的一些通用組件和方法,所以我自己特意開發了一個文檔應用去解決這個問題。

技術方案選型

對於一個面向Ant Design編程的鹹魚而言,這個文檔應用肯定是往這個方向做。

目標是能做成Ant Design的組件文檔那樣好用,既能很快看清組件的使用效果,也能快速複製示例程式碼。

有了目標之後,很快選定了兩個技術方案

  • StoryBook方案
  • 自己開發解析markdown文件的文檔應用

StoryBook是市面上一款比較流行的構建UI組件和文檔的庫,功能很強大。

但是這個庫如果要應用到我們項目的團隊存在以下問題:

  • 英文文檔,有學習成本
  • 引入single-spa的utility應用很麻煩
  • 對於構建在utility應用中的組件,需要在StoryBook中再寫一遍,容易不同步
  • 對於通用方法可能不支援

尤其是第三點特別存在問題,團隊成員大都是業務開發,有交付壓力,不是在github上為愛發電的開源貢獻者。

他們是否有意願在utility應用中寫了一遍組件程式碼後,又專門跑到這個StoryBook中再寫一道?

如果組件程式碼修改後,StoryBook這邊沒修改,這種不同步很容易導致開發人員明明按照文檔使用組件,但是組件就是報錯,挫敗感很強。

聯繫到現實,我已經預想到如果走這個方案,一個月內這個玩意就會名存實亡,成為形式主義的存在,在三個月後成為大家都不願提起的垃圾了。

所以有了第二個方案的誕生:自己開發解析markdown文件的文檔應用

這個方案並不是我憑空想像出來的,而是在前公司中就有這麼一個內部應用,組件開發人員自己編寫markdown文件,最終生成組件文檔,並且應用本身可以解析markdown文件中的程式碼部分,從而在組件文檔中生成對應的組件示例。

彼時我只是一個組件開發人員,並不是這個文檔應用的實現人員,所以也不知道其技術原理。

但是前公司這個應用讓我知道了這麼玩行得通,當時作為組件開發人員,接受和使用這個應用異常輕鬆,只要會寫markdown就行了,沒有學習成本。而且使用這種方式,控制權掌握在自己手中,更容易和自己的團隊項目有效結合起來。

基本原理

雖然這個文檔應用是我一個人花了兩天的時間獨立完成,但是花的是工作時間,完成後也是公司內部項目,所以這個文檔應用我沒法開源出來。

不過我可以告訴大家一個主要思路和步驟,想必復現出來也並不困難。

第一步讓我們搞定這個項目的框架。

因為需要引入single-spa的utility應用,所以框架我直接用的是single-spa的基座,並且項目內含一個子應用用於展示文檔。而utility應用直接引入線上開發環境的utility應用,避免團隊成員重複書寫組件程式碼,也解決了文檔和實際應用不同步的問題。

通過這一步,我們解決了StoryBook方案中的痛點2和痛點3。

第二步我們需要載入markdown文件。

這一步肯定是通過webpack的載入器來做處理,這些載入器有的比較強大,可以直接將markdown文件轉換為html。但是我並沒有選擇這種,而是直接用的raw-loader,將我們的markdown文件轉換為字元串載入。

程式碼大致如下,這個比較簡單,就不說了。

module.exports = {
    module: {
        rules: [
            {
                test: /\.md$/,
                use: 'raw-loader'
            }
        ]
    }
}

第三步我們需要解析markdown文件生成文檔,並解析其中的React組件,生成組件示例。

解析markdown文件轉換為html文檔,實際上有個比較強大的庫,叫Showdown

而我所用到的庫react-showdown則是對Showdown的進一步封裝,可以藉助一個react組件將markdown和包含在markdown文件中的react組件渲染成html。

下面是它的一個官方示例:

import React from 'react';
import MarkdownView from 'react-showdown';

export default function App() {
    const markdown = `
    # Welcome to React Showdown :+1:

    To get started, edit the markdown in \`example/src/App.tsx\`.

    | Column 1 | Column 2 |
    |----------|----------|
    | A1       | B1       |
    | A2       | B2       |
    `;

    return (
        <MarkdownView
        markdown={markdown}
        options={{ tables: true, emoji: true }}
        />
    );
};

通過MarkdownView這個組件,可以將一串markdown格式的文本轉化為html。

另外我們注意到它的選項,tables為true,如果不設置這個的話,markdown中的table格式將不會被轉化成表格。第二個emoji為true是支援emoji轉換。

這個時候你可能要問,這只是轉換了一下markdown文件而已,轉換react組件呢?

我們可以看一下下面這個官方示例:

import MarkdownView from 'react-showdown';

function CustomComponent({ name }: { name: string }) {
    return <span>Hello {name}!</span>;
}

const markdown = `
# 我是個標題:

<CustomComponent name="world" />`;

<MarkdownView markdown={markdown} components={{ CustomComponent }} />

在markdown文本中可以直接寫上CustomComponent這個自定義的react組件程式碼,然後在MarkdownView的components中傳入CustomComponent即可。

生成的最終html中不僅會有個標題,標題下面還會展示一個叫hello world!的文本,而不是展示<CustomComponent name=”world” />這個字元串。

排疑解難

看完了上面的原理,想必您已經可以實現這樣的一個文檔應用了。

不過在這個過程中您可能還是會遇到一些小麻煩,這裡提前給您支個招。

麻煩1:markdown轉換成html後的程式碼高亮處理。

因為我們做的是一個組件文檔,那麼肯定會涉及到程式碼展示。

markdown文件中的程式碼塊,使用react-showndown轉換後的並沒有做高亮處理。

不過react-showdown是支援Showdown的各種擴展的,其中有個擴展叫showdown-highlight,通過這個擴展可以對程式碼塊做高亮處理。

麻煩2react-showndown只支援簡單的組件。

雖然react-showndown可以解析react組件程式碼,但是它也只能簡單解析這個組件。如果我們演示的示例比較複雜,涉及到一些函數,還有一些庫的引用,很顯然不能再markdown文件中直接寫。

這裡我建議直接將每個組件的示例寫到一個獨立的js中,這個js導出一個Demo組件,然後我們在markdown文件中直接引用這個demo組件即可。

大致程式碼如下:

import MarkdownView from 'react-showdown';
import ButtonDemo from './ButtonDemo';

const markdown = `
# 按鈕組件

組件描述

## 程式碼示例

<ButtonDemo />

```tsx
這裡貼出以ButtonDemo組件中的程式碼
```

## API

| 屬性     | 說明      |XXX|
|----------|----------|-----|
| title    | 按鈕文本  | XXX |
| type     | 按鈕類型  | XXX |

`;

<MarkdownView markdown={markdown} components={{ ButtonDemo }} />

通過上面這種方式,不論我們ButtonDemo中的邏輯和功能多麼複雜,展示出來都是沒問題的。

麻煩3:如何將這個文檔應用做到簡單好用。

看了上面的程式碼,可能有人會覺得應該沒問題了。

但是我們得明白,我們這個東西是做給業務開發的人員用的,而不是做給我們自己用的。

我業務開發人員為什麼要知道你這些什麼 react-showdown 的程式碼?

我業務開發人員還要學習你的這些鬼東西?

不是每個人都想著學這些亂七八糟的技術好嗎?

我每天就只想在6點下班,就算你5分鐘內給我講明白了,你這個文檔應用我用不用還兩說。

你要是5分鐘之內還講不明白怎麼用,那你休想我在這上面給一個公共組件寫文檔。

我們面對的基本就是這麼一個場景,我們做這個應用是為了解決項目中實際面臨的問題,是面向業務開發人員編程,而不是面向領導和KPI編程。

所以我們需要做到簡單好用,將所有涉及到react-showdown這玩意的部分全部不被業務開發人員感知。

想像一下,寫一個組件的文檔,縮減到最少,就是一個markdown文件,和一個demo.js。

那麼我們就只讓業務開發人員去寫這兩個東西就行,把他們的工作量減少到最小。

就給他們兩個文件夾,一個文件夾叫doc,裡面放markdown文件,一個文件夾叫demo,裡面放各個demo。

再用一個字典配置dict.js,去做個基本的配置。

如果現在有個Easy組件要寫文檔,那麼我們的dict.js內容可能就是下面這樣:

const dict=['Easy','Hard','XXX']

export defalt dict

只需要加個字元串Easy即可。

然後你可以在那麼doc文件夾下加個markdown文件叫EasyMD.md,demo文件夾下加個文件叫EasyDemo.tsx。

之後的所有步驟全部由我們的文檔應用解析dict.js後自動完成,無需用戶操心。

通過這樣的一種約定,我們可以將業務開發人員的工作量減到最小,把他們寫組件文檔的門檻降到最低。

具體程式碼實現就略過了,實現的關鍵詞叫:import()函數,其他的不用多說了。

總結

雖然說這個文檔應用是受前公司啟發,而且因為開發時間就兩天,所以比較簡陋,但是至少我做到了比前公司的內部應用更簡單方便,完全沒有學習成本。

好了,自吹一波就得了,本篇部落格到此結束。

如有疏漏之處,還請不吝賜教。