持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情 ,祝福祖国,生日快乐 :)
提到模块化,大家可能会想到的是Nginx模块,IDEA插件等等。 通常是我可以通过上传一些模块,来满足我的差异化需求。 之所以大家都喜欢这种模块,主要是因为足够灵活,不用费太大的劲就可以满足自身的需求。 因为很多时候,虽然大体上差不多,但总有一些细节上的差异。 这也正说明软件的复杂度,除了技术上的复杂度,还有业务上的复杂度。 大多数情况下,我们面对的主要是业务复杂度。 这也正是在软件领域,对"隔行如隔山"这句俗语最好的阐述。 如今,不仅互联网行业,金融行业,就算传统的制造业,都已经使用上了信息化系统,来帮助企业的生产和管理。 同样是请假系统,哪怕在同样的行业,不同的公司,都会有所差异。
而Hugo的模块和大家印象中的模块有点不一样,并不是以功能为单位,来满足差异化需求。 而是以目录结构为主,来识别相同的结构。
先来看看在我们架构中,模块的位置:
在架构图中,Modules需要统一组织起来,依赖于Modules Config描述信息,而这个信息的加载是由configLoader负责的。
我们再从Hugo游乐场源码来看一看实际的调用时序:
可以看出,在我们的游乐场中,由主函数,调用LoadConfig方法,并在该方法中为Modules做了两件事。
一件是loadModulesConfig,把和Modules相关的配置信息整理到Module Config中。
另一件则是collectModules,根据收集到的配置信息,按模块的标准,将模块信息标准化。
先来看看Module Config的源码定义:
// Config holds a module config.
type Config struct {
Mounts []Mount
Imports []Import
// Meta info about this module (license information etc.).
Params map[string]any
}
可以看出,重要字段有两个,一个是Mounts,一个是Imports。
loadModulesConfig中主要是对Imports字段进行处理,在我们实例中:
-- config.toml --
theme = "mytheme"
...
主题的配置信息是theme = "mytheme",解析成模块配置信息就成了c.Imports = [mytheme]。
接下来就是收集模块collectModules了:
func (l configLoader) collectModules(modConfig modules.Config, ...) (modules.Modules, ...) {
...
}
传入刚收集到的模块配置信息,输出标准的模块信息。 而我们刚收集到的配置信息只有Imports有值,并且只有一个值"mytheme",输出实例如下图所示:
fmt.Printf("%#v\n", modulesConfig)
modules.Config{
Mounts:[]modules.Mount(nil),
Imports:[]modules.Import{
modules.Import{
Path:"mytheme",
...
}},
Params:map[string]interface {}(nil)
}
为什么Hugo管Theme叫模块呢?
输入以下命令,创建一个站点:
➜ tmp hugo new site xyz
查看目录结构:
➜ xyz tree
.
├── archetypes
│ └── default.md
├── config.toml
├── content
├── data
├── layouts
├── public
├── static
└── themes
7 directories, 2 files
再输入以下命令,创建一个主题:
➜ tmp hugo new theme mytheme
同样查看目录结构:
➜ mytheme tree
.
├── LICENSE
├── archetypes
│ └── default.md
├── layouts
│ ├── 404.html
│ ├── _default
│ │ ├── baseof.html
│ │ ├── list.html
│ │ └── single.html
│ ├── index.html
│ └── partials
│ ├── footer.html
│ ├── head.html
│ └── header.html
├── static
│ ├── css
│ └── js
└── theme.toml
7 directories, 11 files
我们把Site和Theme的目录结构放在一起比对一下:
通过对比,我们不难发现,目录结构基本上是一到的,都包含了archetypes, layouts, static等等。
在Hugo官网中,有对目录结构有明确说明。 从源码中也可以看到:
// hugo-playground/hugofs/files/classifier.go
const (
ComponentFolderArchetypes = "archetypes"
ComponentFolderStatic = "static"
ComponentFolderLayouts = "layouts"
ComponentFolderContent = "content"
ComponentFolderData = "data"
ComponentFolderAssets = "assets"
ComponentFolderI18n = "i18n"
)
var (
ComponentFolders = []string{
ComponentFolderArchetypes,
ComponentFolderStatic,
ComponentFolderLayouts,
ComponentFolderContent,
ComponentFolderData,
ComponentFolderAssets,
ComponentFolderI18n,
}
)
可以看出,Hugo通过标准化目录结构的方式,让每一个模块都遵循这一统一原则,这样无论在解析主题还是用户项目的时候,都有章可循。
再看看Hugo对模块的说明:
Hugo Modules are the core building blocks in Hugo. A module can be your main project or a smaller module providing one or more of the 7 component types defined in Hugo: static, content, layouts, data, assets, i18n, and archetypes.
也就是说
static, content, layouts, data, assets, i18n, and archetypes
这7个组件的任意组合,我们都认为是符合模块的要求。
Hugo的模块是基于Go Modules构建的,使用起来也很方便:
[module]
[[module.imports]]
path = 'github.com/sunwei/zero'
也就是说,我们现在加载主题可以通过加载模块的形式,不用再把主题以git submodule的导入,更方便,更合理了。
搞清楚为什么主题也是模块后,我们再一起看下最终,我们得到了什么样的模块详细配置信息:
// hugo-playground/hugolib/config.go
// line 140
...
log.Process("collectModules", "set active modules to config with key 'allModules'")
for i, m := range moduleConfig.ActiveModules {
fmt.Println(i)
fmt.Printf("%#v\n", m)
}
...
Output:
==> Process collectModules: set active modules to config with key 'allModules'
0
&modules.moduleAdapter{path:"project", ... projectMod:true, owner:modules.Module(nil),
mounts:[]modules.Mount{
modules.Mount{Source:"mycontent", Target:"content", Lang:"en"},
modules.Mount{Source:"data", Target:"data", Lang:""},
modules.Mount{Source:"layouts", Target:"layouts", Lang:""},
modules.Mount{Source:"i18n", Target:"i18n", Lang:""},
modules.Mount{Source:"archetypes", Target:"archetypes", Lang:""},
modules.Mount{Source:"assets", Target:"assets", Lang:""},
modules.Mount{Source:"static", Target:"static", Lang:""}}, ...}
1
&modules.moduleAdapter{path:"mytheme", ... projectMod:false, owner:(*modules.moduleAdapter)(0xc00019e410),
mounts:[]modules.Mount(nil), ...}
从输出结果看,一共有两个模块,一个是project,另一个是mytheme。
因为在我们的实例中,mytheme只有一个txt文件,七个组件中的任何一个都没有,所以mounts为空,而project模块则有每个组件的Mount。
Hugo通过巧妙的标准化目录结构设计,实现了Hugo Module。 强大的拓展性和便捷性,让用户可以专注于内容创作,个性化也得到了大大的满足。