使用Go語言編寫一個毫秒級生成組件庫文檔工具
在開發(fā)組件庫的過程中,文檔無疑是不可或缺的一環(huán)。優(yōu)質的文檔可以極大地提高組件庫的可用性和可維護性。然而,傳統(tǒng)的文檔編寫和維護方式通常是一項耗時且繁瑣的任務。這些傳統(tǒng)文檔生成工具通?;贘avaScript開發(fā),速度上并沒有明顯的優(yōu)勢。
在本文中,我將嘗試將Go語言與前端技術巧妙融合,以創(chuàng)建一款能在毫秒級別完成文檔生成的工具。
鑒于代碼量龐大,本文中的代碼部分將主要以函數(shù)簽名和注釋的形式呈現(xiàn),而不涉及具體實現(xiàn)細節(jié)。我們將深入研究以下六個主要部分:
- 命令行工具: 文檔生成的啟動入口。
- 任務流管理(并行串行任務): 高效地管理文檔生成任務,包括并行和串行執(zhí)行。
- Markdown文檔解析: 從Markdown格式的文檔中提取信息并轉化為可用的HTML格式。
- 文檔元數(shù)據(jù)收集: 從文檔中提取關鍵元數(shù)據(jù),如位置信息,排序信息,分類信息。
- 文檔錨點: 用于導航和快速跳轉的錨點,提升用戶體驗。
- 文檔生成: 最終生成組件庫文檔的核心部分。
命令行工具
為了確保我們的生成工具能夠輕松地進行打包和使用,我將其設計成了一個命令行工具。這使得將來可以方便地將工具包裝成npm包。我選擇了使用github.com/spf13/cobra
庫來構建命令行工具,以便更好地管理命令和參數(shù)。
相關代碼如下:
var rootCommand = &cobra.Command{ Use: "nk", Short: "nk: 一個神奇的文檔生成工具", Long: "nk是一個強大的文檔生成工具,旨在提供快速而高效的文檔生成體驗。", } var generateCommand = &cobra.Command{ Use: "generate", Aliases: []string{"g"}, Short: "generate: 啟動文檔生成", Long: "generate命令用于啟動文檔生成。", } var docCommand = &cobra.Command{ Use: "doc", Short: "generate doc project: 生成文檔項目", Long: "doc命令用于生成文檔項目。", Run: runDocCommand, } func initDocCommand() { wd, err := os.Getwd() if err != nil { return } // 聲明命令參數(shù) docCommand.Flags().String("components-dir", path.Join(wd, "projects", "components"), "指定組件目錄的絕對路徑") docCommand.Flags().String("doc-dir", path.Join(wd, "projects", "design-doc"), "指定設計文檔目錄的絕對路徑") docCommand.Flags().String("docs-dir", path.Join(wd, "docs"), "指定文檔目錄的絕對路徑") docCommand.Flags().Bool("watch", true, "監(jiān)聽文件變化") } func init() { initDocCommand() generateCommand.AddCommand(docCommand) generateCommand.AddCommand(serviceCommand) generateCommand.AddCommand(staticIconsCommand) rootCommand.AddCommand(generateCommand) }
在上述代碼中,我們對命令和參數(shù)進行了更具描述性的命名,以便開發(fā)者更容易理解其功能。同時,我們還添加了關于工具的簡短和長描述,以提供更多上下文信息。這將有助于用戶更好地理解如何使用這個命令行工具以及它的目標。
這樣我們就可以運行類似如下命令了:
nx g doc --components-dir xxxx
任務流管理
在這個工具中我們可以拆分很多子任務,有的任務需要依賴其他任務的完成,有的任務沒有執(zhí)行順序可以并行運行,所以需要一個很好的任務流管理工具來幫助我們來管理這些子任務
主要代碼如下:
type Task struct { Name string // 任務名稱 Work func() error // 任務執(zhí)行的函數(shù) Dependencies []*Task // 依賴的任務 mu sync.Mutex // 互斥鎖,用于同步 cond *sync.Cond // 條件變量,用于等待任務完成 done bool // 任務完成標志 StartTime time.Time // 任務開始時間 EndTime time.Time // 任務結束時間 noOutputLog bool // 是否輸出日志 } // printExecutionTime 輸出任務執(zhí)行時間 func printExecutionTime(task *Task) { executionTime := task.EndTime.Sub(task.StartTime).Milliseconds() color.Green.Println(fmt.Sprintf("[%s] 任務完成 執(zhí)行時間:%d 毫秒", task.Name, executionTime)) } // NewTask 創(chuàng)建一個新任務 func NewTask(name string, work func() error) *Task { task := &Task{ Name: name, Work: work, done: false, } task.cond = sync.NewCond(&task.mu) return task } // SetDependency 設置任務的依賴關系 func (t *Task) SetDependency(dependencies ...*Task) { t.Dependencies = append(t.Dependencies, dependencies...) } // WaitForDependencies 等待任務的依賴任務完成 func (t *Task) WaitForDependencies() { for _, dep := range t.Dependencies { dep.WaitForCompletion() } } // Run 運行任務 func (t *Task) Run() error { t.WaitForDependencies() t.mu.Lock() defer t.mu.Unlock() t.StartTime = time.Now() if !t.noOutputLog { color.Green.Println(fmt.Sprintf("[%s] 任務開始", t.Name)) } err := t.Work() if err != nil { return err } t.EndTime = time.Now() t.done = true if !t.noOutputLog { printExecutionTime(t) } t.cond.Broadcast() return nil } // WaitForCompletion 等待任務完成 func (t *Task) WaitForCompletion() { t.mu.Lock() defer t.mu.Unlock() for !t.done { t.cond.Wait() } } // SerialTask 串行執(zhí)行任務 func SerialTask(tasks []*Task) { for _, task := range tasks { task.Run() } } // ParallelTask 并行執(zhí)行任務 func ParallelTask(tasks []*Task) { var wg sync.WaitGroup for _, task := range tasks { wg.Add(1) go func(t *Task) { defer wg.Done() t.Run() }(task) } wg.Wait() }
通過上述簡單封裝,我們可以這樣使用輕松管理子任務的執(zhí)行
var clearCacheTask = flows.NewTask("清除項目緩存", func() error { return nil }) var copyProjectTask = flows.NewTask("復制項目模板", func() error { return nil }) // 設置依賴 copyProjectTask 依賴 clearCacheTask任務 copyProjectTask.SetDependency(clearCacheTask) var collectTemplateMetaTask = flows.NewTask("收集模板信息", func() error { return nil }) flows.ParallelTask([]*flows.Task{ clearCacheTask, copyProjectTask, collectTemplateMetaTask, })
上述示例中clearCacheTask
, copyProjectTask
, collectTemplateMetaTask
這三個任務會并行運行,不過copyProjectTask
依賴了clearCacheTask
,需要等clearCacheTask
執(zhí)行完成才會真正執(zhí)行。
Markdown文檔解析
在組件庫文檔中,Markdown(md)文檔通常占據(jù)著相當大的比例。這些文檔不僅包括了示例代碼,還包括了組件庫的介紹、使用指南、組件API等內容。為了處理這些Markdown文檔,我們使用了 github.com/yuin/goldmark
這個工具庫。本節(jié)將詳細介紹如何使用它來將Markdown文檔轉換為HTML格式。
核心代碼如下;
func convertToHTML(markdownContent string) string { var htmlOutput bytes.Buffer md := goldmark.New( goldmark.WithExtensions(extension.GFM), // 支持GitHub風格的Markdown // 如果需要語法高亮,可以啟用以下代碼(需要配置chroma等選項) //goldmark.WithExtensions( // highlighting.NewHighlighting( // highlighting.WithStyle("github"), // highlighting.WithFormatOptions( // chromahtml.WithClasses(true), // ), // ), //), goldmark.WithRenderer(HtmlRenderer()), // 使用自定義的HTML渲染器 goldmark.WithRendererOptions(html.WithUnsafe()), // 允許HTML渲染器輸出不安全的HTML ) if err := md.Convert([]byte(markdownContent), &htmlOutput); err != nil { panic(err) } return htmlOutput.String() }
需要注意的是, 我們啟用了 html.WithUnsafe()
選項, 如果不開啟的話,默認情況下,goldmark 不會呈現(xiàn)原始 HTML 或潛在危險的鏈接
這段核心代碼演示了如何使用github.com/yuin/goldmark
庫將Markdown文檔內容轉換為HTML格式。它首先創(chuàng)建了一個goldmark
實例,配置了一些擴展(例如支持GitHub風格的Markdown和語法高亮),然后將Markdown內容傳遞給md.Convert
函數(shù),最終將轉換后的HTML輸出到htmlOutput
緩沖區(qū)中。
如果需要語法高亮,可以根據(jù)需要取消注釋相關部分,并進行相應的配置。
Markdown文檔解析是生成組件庫文檔的重要一步,它使我們能夠將Markdown格式的文檔轉換為易于閱讀和導航的HTML格式,為用戶提供了更好的文檔瀏覽體驗。
這段代碼中使用了自定義HTML渲染器,自定義渲染器主要對md標簽進行了特殊定制,轉換HTML時,h標簽上需要攜帶ID,方便后續(xù)的文檔錨點進行錨點導航。
核心代碼如下:
// CustomHTMLRenderer 自定義渲染器 type CustomHTMLRenderer struct { html.Renderer } func HtmlRenderer() renderer.Renderer { return renderer.NewRenderer(renderer.WithNodeRenderers(util.Prioritized(NewRenderer(), 1000))) } func NewRenderer(opts ...html.Option) renderer.NodeRenderer { r := &CustomHTMLRenderer{ Renderer: html.Renderer{ Config: html.NewConfig(), }, } for _, opt := range opts { opt.SetHTMLOption(&r.Config) } return r } // renderHeading 自定義標簽渲染 func (r *CustomHTMLRenderer) renderHeading( w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Heading) if entering { lines := n.BaseBlock.Lines() if lines.Len() > 0 { at := n.BaseBlock.Lines().At(0) buf := at.Value(source) // 附加id n.SetAttribute([]byte("id"), buf) } _, _ = w.WriteString("<h") _ = w.WriteByte("0123456"[n.Level]) if n.Attributes() != nil { html.RenderAttributes(w, node, html.HeadingAttributeFilter) } _ = w.WriteByte('>') } else { _, _ = w.WriteString("</h") _ = w.WriteByte("0123456"[n.Level]) _, _ = w.WriteString(">\n") } return ast.WalkContinue, nil } func (r *CustomHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { r.Renderer.RegisterFuncs(reg) // 注冊 覆蓋內置的標題渲染邏輯 reg.Register(ast.KindHeading, r.renderHeading) }
文檔元數(shù)據(jù)收集
在將Markdown文檔渲染為HTML并呈現(xiàn)在頁面上時,我們需要了解渲染的位置、順序、語言、標題等重要信息。為了實現(xiàn)這一目標,通常在Markdown文檔的開頭聲明這些信息。例如:
因此,我們需要收集并匯總這些元數(shù)據(jù),以供后續(xù)文檔生成使用。
以下是核心代碼示例,配有詳細的注釋:
func ParseMarkdown(filePath string) (*Document, error) { // 讀取Markdown文件內容 mdContent, err := ioutil.ReadFile(filePath) if err != nil { return nil, err } // 使用正則表達式提取YAML元數(shù)據(jù)部分 re := regexp.MustCompile(`---\r*\n([\s\S]*?)\r*\n---`) match := re.FindStringSubmatch(string(mdContent)) if len(match) < 2 { return nil, fmt.Errorf("YAML section not found") } // 獲取YAML元數(shù)據(jù)內容 yamlContent := match[1] // 獲取除去YAML元數(shù)據(jù)的Markdown內容 markdownContent := string(mdContent)[len(yamlContent)+8:] // 解析YAML元數(shù)據(jù) var metadata Metadata if err := yaml.Unmarshal([]byte(yamlContent), &metadata); err != nil { return nil, fmt.Errorf("error parsing YAML: %w", err) } // 獲取文件名(包括擴展名) fileNameWithExtension := filepath.Base(filePath) // 去除擴展名,獲得文件名 fileName := strings.TrimSuffix(fileNameWithExtension, filepath.Ext(fileNameWithExtension)) // 創(chuàng)建文檔對象,包括元數(shù)據(jù)和多語言HTML內容 document := Document{ Metadata: metadata, ZhCN: convertToHTML(getSectionContent(markdownContent, "zh-CN")), EnUS: convertToHTML(getSectionContent(markdownContent, "en-US")), FileKey: fileName, } return &document, nil }
這段核心代碼演示了如何從Markdown文檔中提取YAML格式的元數(shù)據(jù)部分,并將其解析為元數(shù)據(jù)對象。隨后,代碼將Markdown文檔內容分成不同語言的部分,并將其轉換為HTML格式。
文檔元數(shù)據(jù)收集是文檔生成過程中的關鍵步驟,它允許我們獲取文檔的關鍵信息,如標題、語言等,以便更好地呈現(xiàn)文檔內容。這些信息將對后續(xù)文檔生成和導航起到至關重要的作用。
文檔錨點
文檔錨點是一種重要的導航工具,它們可以幫助用戶快速跳轉到文檔中的特定部分。在組件庫文檔中,文檔錨點特別有用,因為它們使用戶能夠快速找到他們需要的信息,提高了文檔的可讀性和可用性。
核心代碼如下:
func wrapperAnchor(content string, anchor string) string { return fmt.Sprintf( "<div class="doc-content">\n\t\t%s\n</div><nx-anchor container="#component-demo">%s</nx-anchor>", content, anchor) } // ... templateString += wrapperAnchor(angularNonBindAble(templateContent), strings.Join(anchorLink, "\n"))
anchorLink
是從前面收集的元數(shù)據(jù)解析而來
文檔生成
文檔生成是組件庫文檔工具的核心部分,它負責將收集到的元數(shù)據(jù)、Markdown文檔解析結果和文檔錨點整合在一起,最終生成用戶友好的組件庫文檔。在這一章節(jié)中,我們將討論如何使用Go語言來實現(xiàn)文檔生成的關鍵功能。首先,讓我們來了解文檔生成的一般流程。
文檔生成通常包括以下主要步驟:
- 收集元數(shù)據(jù): 獲取組件庫中的各個文檔的元數(shù)據(jù),包括標題、語言、作者等信息。
- Markdown文檔解析: 將Markdown文檔解析為HTML格式,以便用戶可以在頁面上瀏覽和交互。
- 創(chuàng)建文檔錨點: 在文檔中創(chuàng)建錨點,以便用戶可以輕松導航和快速跳轉到感興趣的部分。
- 整合內容: 將元數(shù)據(jù)、Markdown解析結果和錨點整合在一起,構建完整的組件庫文檔。
- 生成HTML頁面: 最終,將整合后的文檔轉換為HTML頁面,并提供用戶友好的界面。
核心代碼如下:
// registrationTask 注冊相關子任務&處理任務之間的依賴關系 func (receiver *CompileDocTask) registrationTask() { // 創(chuàng)建清除 design-doc 任務 receiver.ClearDesignDocTask = flows.NewTask("清除 design-doc", receiver.clearTaskHandler) // 創(chuàng)建復制 design-doc 任務 receiver.CopyDesignDocTask = flows.NewTask("復制 design-doc", receiver.copyDocProjectTaskHandler) // 設置復制任務依賴于清除任務 receiver.CopyDesignDocTask.SetDependency(receiver.ClearDesignDocTask) // 創(chuàng)建收集全局文檔信息任務 receiver.CollectGlobalDocsTask = flows.NewTask("收集全局文檔信息", receiver.collectGlobalDocsTaskHandler) // 創(chuàng)建收集組件文檔信息任務 receiver.CollectComponentDocsTask = flows.NewTask("收集組件文檔信息", receiver.collectComponentDocsTaskHandler) // 創(chuàng)建生成全局文檔任務 receiver.GenerateGlobalDocsTask = flows.NewTask("生成全局文檔", receiver.generateGlobalDocsTaskHandler) // 設置生成全局文檔任務依賴于復制和收集任務 receiver.GenerateGlobalDocsTask.SetDependency( receiver.CopyDesignDocTask, receiver.CollectGlobalDocsTask, ) // 創(chuàng)建生成demo文檔任務 receiver.GenerateDemoDocsTask = flows.NewTask("生成demo文檔", receiver.generateDemoDocsTaskHandler) // 設置生成demo文檔任務依賴于收集組件文檔信息任務 receiver.GenerateDemoDocsTask.SetDependency(receiver.CollectComponentDocsTask) } // CompileTask 編譯文檔 處理元數(shù)據(jù)收集,md文檔解析等 func (receiver *CompileDocTask) CompileTask() { // 并行執(zhí)行清除、復制、收集任務 flows.ParallelTask([]*flows.Task{ receiver.ClearDesignDocTask, receiver.CopyDesignDocTask, receiver.CollectComponentDocsTask, receiver.CollectGlobalDocsTask, }) } // GenerateTask 將收集的信息整合到一起生成完整的組件庫文檔 func (receiver *CompileDocTask) GenerateTask() { // 并行執(zhí)行生成全局文檔和生成demo文檔任務 flows.ParallelTask([]*flows.Task{ receiver.GenerateGlobalDocsTask, receiver.GenerateDemoDocsTask, }) } func runDocCommand(cmd *cobra.Command, _ []string) { componentsDir, _ := cmd.Flags().GetString("components-dir") docDir, _ := cmd.Flags().GetString("doc-dir") docsDir, _ := cmd.Flags().GetString("docs-dir") watch, _ := cmd.Flags().GetBool("watch") sourceDir := path.Join("design-doc") // 源目錄 // 創(chuàng)建文檔生成任務 compileDocTask := NewCompileDocTask(componentsDir, docDir, docsDir, sourceDir) // 執(zhí)行編譯任務 compileDocTask.CompileTask() // 執(zhí)行生成任務 compileDocTask.GenerateTask() if watch { // 監(jiān)聽單個文件的變化 實現(xiàn)局部更新 watchDoc(cmd, compileDocTask) } }
在這個示例中,我們首先創(chuàng)建了文檔生成任務并注冊了相關的子任務以及它們之間的依賴關系。然后,我們展示了如何執(zhí)行編譯任務和生成任務
生成結果如下:
使用效果
生成速度
整體下來300ms不到就生成了完整的組件庫文檔了
局部更新
可以看到修改group.ts文件觸發(fā)更新所需時間也是非??斓?/p>
總結
本文介紹了一款強大的組件庫文檔生成工具,使用Go語言作為開發(fā)語言。該工具旨在提高組件庫文檔的可用性和可維護性,同時也力求提供毫秒級的文檔生成體驗。通過本文,我們深入探討了工具的各個方面,包括命令行工具、任務流管理、Markdown文檔解析、文檔元數(shù)據(jù)收集、文檔錨點以及文檔生成等關鍵特性。
源碼鏈接
你可以在以下鏈接找到完整的源代碼和工具的實現(xiàn)細節(jié):
關鍵功能和步驟
- 命令行工具: 我們使用Go語言的
github.com/spf13/cobra
庫創(chuàng)建了一個命令行工具,使工具能夠以包裝的方式輕松使用。 - 任務流管理: 我們實現(xiàn)了任務流管理,支持并行和串行任務,以有效管理異步任務的執(zhí)行順序。
- Markdown文檔解析: 使用
github.com/yuin/goldmark
庫,我們能夠將Markdown文檔轉換為HTML,以便用戶可以在頁面上瀏覽和交互。 - 文檔元數(shù)據(jù)收集: 工具可以從Markdown文檔中收集元數(shù)據(jù),包括標題、語言、作者等信息,以供后續(xù)文檔生成使用。
- 文檔錨點: 我們討論了如何在Markdown文檔中創(chuàng)建文檔錨點,以幫助用戶快速導航到感興趣的部分。
- 文檔生成: 最后,我們介紹了文檔生成的流程,包括任務的注冊、依賴管理和并行執(zhí)行,以將元數(shù)據(jù)、Markdown解析結果和錨點整合為用戶友好的組件庫文檔。
這個組件庫文檔生成工具可以大大簡化文檔編寫和維護的工作,并提供出色的文檔瀏覽體驗。通過深入了解本文所述的關鍵特性和源代碼,您可以定制并集成這個工具到您的項目中,提升組件庫文檔的質量和效率。
以上就是使用Go語言編寫一個毫秒級生成組件庫文檔工具的詳細內容,更多關于Go組件庫文檔工具的資料請關注腳本之家其它相關文章!
相關文章
Go通過SJSON實現(xiàn)動態(tài)修改JSON
在Go語言 json 處理領域,在 json 數(shù)據(jù)處理中,讀取與修改是兩個核心需求,本文我們就來看看如何使用SJSON進行動態(tài)修改JSON吧,有需要的小伙伴可以了解下2025-03-03golang使用sync.singleflight解決熱點緩存穿透問題
在go的sync包中,有一個singleflight包,里面有一個?singleflight.go文件,代碼加注釋,一共200行出頭,通過?singleflight可以很容易實現(xiàn)緩存和去重的效果,避免重復計算,接下來我們就給大家詳細介紹一下sync.singleflight如何解決熱點緩存穿透問題2023-07-07