Hugo?Config模塊構(gòu)建實(shí)現(xiàn)源碼剖析
了然于胸 - collectModules時(shí)序圖
經(jīng)過(guò)loadConfig
和applyConfigDefaults
,我們已經(jīng)將用戶(hù)自定義信息和默認(rèn)信息都?xì)w置妥當(dāng),并且放在了Config Provider
中,方便查用。
Hugo在拿到這些信息后,立馬著手的事情就是collectModules
,也就是收集模塊信息了。
正如上圖中loadModulesConfig
所示,拿到配置信息后,就進(jìn)行解碼decodeConfig
操作。 在我們的示例中,我們的項(xiàng)目用到了名為mytheme
的主題,所以在項(xiàng)目配置信息中,我們需要把主題添加到導(dǎo)入項(xiàng)Imports
中。
準(zhǔn)備好了模塊的配置信息后,接下來(lái)就是要根據(jù)這些配置信息,對(duì)模塊進(jìn)行處理了。
需要先準(zhǔn)備好回調(diào)函數(shù)beforeFinalizeHook
,為什么要準(zhǔn)備這和個(gè)回調(diào)函數(shù)呢? 我們先把這個(gè)疑問(wèn)放一放,一會(huì)我們就能發(fā)現(xiàn)實(shí)際的觸發(fā)場(chǎng)景。
回調(diào)函數(shù)設(shè)置好后,接著就開(kāi)始收集模塊了。 如上圖左上角所示,首先需要?jiǎng)?chuàng)建Module Client
用來(lái)具體處理模塊的收集工作。 為什么要叫Client
呢? 這是因?yàn)楝F(xiàn)在Hugo支持Golang的mod模式,意味著可以用go.mod
來(lái)導(dǎo)入主題,那我們就需要下載依賴(lài)包 - 主題工程來(lái)管理依賴(lài)了。 這樣來(lái)看,叫客戶(hù)端是不是就不難理解了。 在我們的示例中,主題目錄是用來(lái)做流程講解示范的,只有一個(gè)文本文件,所以這里的場(chǎng)景并不涉線上go模塊加載。
客戶(hù)端設(shè)置好后,開(kāi)始收集,如上圖中間所示,收集過(guò)程總共分四步:
- 按配置遞歸收集所有模塊 - Collect
- 設(shè)置處于活躍狀態(tài)的模塊 - setActiveMods
- 觸發(fā)提前設(shè)置的回調(diào)函數(shù) - HookBeforeFinalize
- 移除重復(fù)的掛載信息 - Finalize
Collect
先為項(xiàng)目創(chuàng)建工程模塊Project Module
,然后開(kāi)始遞歸收集模塊:
func (c *collector) collect() { ... // c.gomods is [], GetMain() returns ni projectMod := createProjectModule(c.gomods.GetMain(), c.ccfg.WorkingDir, c.moduleConfig) // module structure, [project, others...] if err := c.addAndRecurse(projectMod, false); err != nil { c.err = err return } ... }
這里為什么會(huì)用到遞歸呢? 因?yàn)樵贖ugo中,模塊之間是有相互依賴(lài)的。 通過(guò)最開(kāi)始的模塊配置信息也可以看出,我們把依賴(lài)的模塊放在了Imports中,Project Module就需要導(dǎo)入"mytheme"模塊。 在實(shí)際情況中,"mytheme"有可能也是依賴(lài)于其它的主題,所以也需要導(dǎo)入其它模塊。
從上面時(shí)序圖右下方可以看到,addAndRecurse
做了四件事:
- 為導(dǎo)入的模塊創(chuàng)建模塊文件夾,用來(lái)放置模塊所有文件
- 應(yīng)用主題配置,就像最開(kāi)始解析項(xiàng)目模塊的配置信息一樣,看是否還需要導(dǎo)入其它模塊
- 將模塊添加到模塊列表中
- 為新模塊重復(fù)上述步驟
這樣,我們就能順著項(xiàng)目模塊的配置信息,逐個(gè)將所有的模塊信息收集齊全了。
setActiveMods
遞歸收集完所有模塊信息后,需要根據(jù)用戶(hù)配置,進(jìn)一步將禁用的模塊給過(guò)濾到,留下這一次構(gòu)建所需要的模塊。
HookBeforeFinalize
過(guò)濾完模塊后,在Finalize
敲定前,是時(shí)候回調(diào)我們之前設(shè)置好地回調(diào)函數(shù)了。
除了加載多語(yǔ)言設(shè)置處,回調(diào)函數(shù)所做的操作主要集中在上面時(shí)序圖的右下腳。 就是為項(xiàng)目模塊準(zhǔn)備好所有的掛載Mount
,包括Content, Static, Layouts, Archetypes, Data, Assets, i18n,共七個(gè)組件。 其中Content和其它的組件有點(diǎn)不一樣。 因?yàn)镃ontent掛載點(diǎn)和多語(yǔ)言一一對(duì)應(yīng),也就是說(shuō)有幾種語(yǔ)言,就會(huì)有幾個(gè)內(nèi)容目錄。
Finalize
等有了所有的模塊的信息,掛載點(diǎn)也收集完畢后,我們還要做一件事情。 那就是要保證這些掛載點(diǎn)在全局視野下,沒(méi)有重復(fù)。
結(jié)合時(shí)序圖,我們進(jìn)一步將其中的關(guān)鍵對(duì)象結(jié)構(gòu)體,根據(jù)這些結(jié)構(gòu)體的屬性和行為,按流程處理后所得到的最終結(jié)果放在一起,可視化出來(lái)。 方便大家理解:
抽象總結(jié) - 輸入不同類(lèi)型的值,輸出標(biāo)準(zhǔn)的configProvider
在上圖中,通過(guò)下方輸出部分可以看出,一個(gè)模塊配置項(xiàng),對(duì)應(yīng)一個(gè)模塊。
在左邊的模塊配置信息中,包含了模塊之間的依賴(lài)信息。 在上面的示例中項(xiàng)目模塊飽含了主題模塊。
在右邊的模塊實(shí)例中,首先要區(qū)分哪一個(gè)是項(xiàng)目模塊,因?yàn)轫?xiàng)目模塊是站點(diǎn)構(gòu)建的起點(diǎn)。 所以在模塊中需要能標(biāo)識(shí)身份信息的字段projectMod
。
如果從掛載Mounts
的角度來(lái)看模塊,那每個(gè)模塊實(shí)際上就是一個(gè)合并后的根文件系統(tǒng)。 Hugo將這個(gè)文件系統(tǒng)用七個(gè)組件進(jìn)行了劃分。
項(xiàng)目模塊必需得包含這些信息,但因?yàn)橐蕾?lài)于其它模塊,所以需要將項(xiàng)目模塊放在最后處理。 Hugo將項(xiàng)目模塊放在了模塊隊(duì)列的第一個(gè),并用一個(gè)回調(diào)函數(shù)幫助在合適的時(shí)間點(diǎn),對(duì)項(xiàng)目模的掛載進(jìn)行了統(tǒng)一的處理。
再用Input -> [?] -> Output
模型來(lái)進(jìn)行分析,可以抽象為以下模型:
主題信息來(lái)源于用戶(hù)自定義信息,作為輸入傳入收集模塊功能單元。 在處理過(guò)程中,Hugo按Name, Module Config, Module, Mounts的對(duì)應(yīng)關(guān)系,將模塊相關(guān)信息進(jìn)行處理。 最終生成所有模塊的信息,并通過(guò)將這些信息設(shè)置在Config Provider中,為后續(xù)的操作做好準(zhǔn)備。
動(dòng)手實(shí)踐 - Show Me the Code of collectModules
在知道collectModules
的實(shí)現(xiàn)原理后。 按照我們的傳統(tǒng),讓我們動(dòng)動(dòng)小手,用代碼來(lái)總結(jié)代碼,鞏固一下知識(shí)。
可以這里線上嘗試,Show Me the Code, try it yourself
代碼里有注解說(shuō)明,代碼樣例:
package main import "fmt" type Mount struct { // relative path in source repo, e.g. "scss" Source string // relative target path, e.g. "assets/bootstrap/scss" Target string // any language code associated with this mount. Lang string } type Import struct { // Module path Path string } // Config holds a module config. type Config struct { Mounts []Mount Imports []Import } type Module interface { // Config The decoded module config and mounts. Config() Config // Owner In the dependency tree, this is the first module that defines this module // as a dependency. Owner() Module // Mounts Any directory remappings. Mounts() []Mount } type Modules []Module var modules Modules // moduleAdapter implemented Module interface type moduleAdapter struct { projectMod bool owner Module mounts []Mount config Config } func (m *moduleAdapter) Config() Config { return m.config } func (m *moduleAdapter) Mounts() []Mount { return m.mounts } func (m *moduleAdapter) Owner() Module { return m.owner } // happy path to easily understand func main() { // project module config moduleConfig := Config{} imports := []string{"mytheme"} for _, imp := range imports { moduleConfig.Imports = append(moduleConfig.Imports, Import{ Path: imp, }) } // Need to run these after the modules are loaded, but before // they are finalized. collectHook := func(mods Modules) { // Apply default project mounts. // Default folder structure for hugo project ApplyProjectConfigDefaults(mods[0]) } collectModules(moduleConfig, collectHook) for _, m := range modules { fmt.Printf("%#v\n", m) } } // Module folder structure const ( ComponentFolderArchetypes = "archetypes" ComponentFolderStatic = "static" ComponentFolderLayouts = "layouts" ComponentFolderContent = "content" ComponentFolderData = "data" ComponentFolderAssets = "assets" ComponentFolderI18n = "i18n" ) // ApplyProjectConfigDefaults applies default/missing module configuration for // the main project. func ApplyProjectConfigDefaults(mod Module) { projectMod := mod.(*moduleAdapter) type dirKeyComponent struct { key string component string multilingual bool } dirKeys := []dirKeyComponent{ {"contentDir", ComponentFolderContent, true}, {"dataDir", ComponentFolderData, false}, {"layoutDir", ComponentFolderLayouts, false}, {"i18nDir", ComponentFolderI18n, false}, {"archetypeDir", ComponentFolderArchetypes, false}, {"assetDir", ComponentFolderAssets, false}, {"", ComponentFolderStatic, false}, } var mounts []Mount for _, d := range dirKeys { if d.multilingual { // based on language content configuration // multiple language has multiple source folders if d.component == ComponentFolderContent { mounts = append(mounts, Mount{Lang: "en", Source: "mycontent", Target: d.component}) } } else { mounts = append(mounts, Mount{Source: d.component, Target: d.component}) } } projectMod.mounts = mounts } func collectModules(modConfig Config, hookBeforeFinalize func(m Modules)) { projectMod := &moduleAdapter{ projectMod: true, config: modConfig, } // module structure, [project, others...] addAndRecurse(projectMod) // Add the project mod on top. modules = append(Modules{projectMod}, modules...) if hookBeforeFinalize != nil { hookBeforeFinalize(modules) } } // addAndRecurse Project Imports -> Import imports func addAndRecurse(owner *moduleAdapter) { moduleConfig := owner.Config() // theme may depend on other theme for _, moduleImport := range moduleConfig.Imports { tc := add(owner, moduleImport) if tc == nil { continue } // tc is mytheme with no config file addAndRecurse(tc) } } func add(owner *moduleAdapter, moduleImport Import) *moduleAdapter { fmt.Printf("start to create `%s` module\n", moduleImport.Path) ma := &moduleAdapter{ owner: owner, // in the example, mytheme has no other import config: Config{}, } modules = append(modules, ma) return ma }
輸出結(jié)果:
# collect theme as module start to create `mytheme` module # project module has no owner with default mounts &main.moduleAdapter{projectMod:true, owner:main.Module(nil), mounts:[]main.Mount{main.Mount{Source:"mycontent", Target:"content", Lang:"en"}, main.Mount{Source:"data", Target:"data", Lang:""}, main.Mount{Source:"layouts", Target:"layouts", Lang:""}, main.Mount{Source:"i18n", Target:"i18n", Lang:""}, main.Mount{Source:"archetypes", Target:"archetypes", Lang:""}, main.Mount{Source:"assets", Target:"assets", Lang:""}, main.Mount{Source:"static", Target:"static", Lang:""}}, config:main.Config{Mounts:[]main.Mount(nil), Imports:[]main.Import{main.Import{Path:"mytheme"}}}} # theme module owned by project module with no import in the example &main.moduleAdapter{projectMod:false, owner:(*main.moduleAdapter)(0xc000102120), mounts:[]main.Mount(nil), config:main.Config{Mounts:[]main.Mount(nil), Imports:[]main.Import(nil)}} Program exited.
以上就是Hugo Config模塊構(gòu)建實(shí)現(xiàn)源碼剖析的詳細(xì)內(nèi)容,更多關(guān)于Hugo Config模塊構(gòu)建的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go實(shí)現(xiàn)自動(dòng)解壓縮包以及讀取docx/doc文件內(nèi)容詳解
在開(kāi)發(fā)過(guò)程中,我們常常需要處理壓縮包和文檔文件。本文將介紹如何使用Go語(yǔ)言自動(dòng)解壓縮包和讀取docx/doc文件,需要的可以參考一下2023-03-03Golang控制通道實(shí)現(xiàn)協(xié)程等待詳解
這篇文章主要介紹了Golang控制通道實(shí)現(xiàn)協(xié)程等待,通道是Go語(yǔ)言程序的并發(fā)體goroutine是它們之間的通信機(jī)制。一個(gè)通道是一個(gè)通信機(jī)制,它可以讓一個(gè)goroutine通過(guò)它給另一個(gè)goroutine發(fā)送值信息。每個(gè)通道都有一個(gè)特殊的類(lèi)型,也就是channels可發(fā)送數(shù)據(jù)的類(lèi)型2022-11-11Go語(yǔ)言實(shí)現(xiàn)的一個(gè)簡(jiǎn)單Web服務(wù)器
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)的一個(gè)簡(jiǎn)單Web服務(wù)器,本文先是給出一個(gè)使用http包建立的Web服務(wù)器源碼,并對(duì)比了其它編程語(yǔ)言,需要的朋友可以參考下2014-10-10Go語(yǔ)言atomic.Value如何不加鎖保證數(shù)據(jù)線程安全?
這篇文章主要介紹了Go語(yǔ)言atomic.Value如何不加鎖保證數(shù)據(jù)線程安全詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05詳解Go語(yǔ)言如何解決map并發(fā)安全問(wèn)題
常說(shuō)go語(yǔ)言是一門(mén)并發(fā)友好的語(yǔ)言,對(duì)于并發(fā)操作總會(huì)在編譯期完成安全檢查,所以這篇文章我們就來(lái)聊聊go語(yǔ)言是如何解決map這個(gè)數(shù)據(jù)結(jié)構(gòu)的線程安全問(wèn)題吧2024-04-04Go中基本數(shù)據(jù)類(lèi)型和字符串表示之間轉(zhuǎn)換詳解
這篇文章主要為大家詳細(xì)介紹了Go中基本數(shù)據(jù)類(lèi)型和字符串表示之間轉(zhuǎn)換的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01Go文件操作(新建打開(kāi)寫(xiě)入讀取刪除關(guān)閉)學(xué)習(xí)筆記
這篇文章主要為大家介紹了Go文件操作(新建打開(kāi)寫(xiě)入讀取刪除關(guān)閉)學(xué)習(xí)筆記,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01