· 文档 · 8 分钟阅读
AstroWind 模板内部是怎么工作的
AstroWind 模板内部怎么运转。配置集成、永久链接和 base 路径、博客流水线、图片处理、元数据,还有构建过程。
你可以连续用 AstroWind 好几个月,都不用打开 src/pages、src/data/post 和 config.yaml 以外的文件。这篇是写给某一天的:你想弄明白某件事为什么是这样。也许在查一个 URL,也许在加一个内容来源,也许在决定动手改之前,这套模板能信任到哪一步。它顺着一次请求,从配置讲到最终生成的 HTML。如果站点还没搭起来,先看入门指南。
集成和 astrowind:config
vendor/integration/index.ts 是一个很小的 Astro 集成,在 astro.config.ts 里注册。配置就绪时,它读取 src/config.yaml,整理成统一的结构,再通过虚拟模块交出来:
import { SITE, METADATA, APP_BLOG, I18N, UI } from 'astrowind:config';任何组件都可以直接 import 这些常量。没有运行时查找,也不用一层层把 props 传下去。同一个集成还会把 YAML 里的 site、base 和 trailingSlash 写进 Astro 自己的配置,这样站点地图、RSS 和规范 URL 都认同一份来源。构建结束后,它会把 robots.txt 指到生成好的站点地图。
永久链接和 base 路径
模板里每条内部链接都经过 src/utils/permalinks.ts。getPermalink(slug, type) 会为页面、文章、分类、标签或资源拼出路径,加上配置里的 base,再套用 trailingSlash 的规则。所以部署到子目录时,多半只是改配置:把 base 设成 '/handbook',链接、图片和订阅源都会跟着走。deploy-with-base-path 这份 skill 写了少数不能对 getPermalink() 套两次的值。
文章 URL 来自 config.yaml 里的 apps.blog.post.permalink 模式。默认的 /%slug% 把文章放在站点根路径;/blog/%year%/%month%/%slug% 或 /%category%/%slug% 也可以,因为加载内容集合时会按每篇文章展开这个模式。列表、分类和标签的路径在同一段配置里,每条路由都可以单独关掉,或单独标成 noindex。
博客流水线:从 Markdown 到页面
文章是 Astro 的内容集合,定义在 src/content.config.ts:用 glob 加载器读取 src/data/post/*.md 和 *.mdx,并用 schema 校验 front matter。
src/utils/blog.ts 每次构建加载一次集合,把每条记录整理好(分类和标签做成 slug,按上面的模式生成永久链接,在处理 Markdown 时算好阅读时间),按日期排序,并丢掉草稿。src/pages/[...blog]/ 里的路由文件调用同一模块里的辅助函数,生成带分页的列表、每一篇文章,以及分类页和标签页。相关文章按共同的标签和分类来挑。博客始终是预渲染的。
Markdown 会经过 astro.config.ts 里声明的两个小插件:一个把阅读时间写进 front matter,另一个把表格包起来,窄屏时横向滚动,而不是把版面撑破。代码块用 Shiki 高亮,两套主题,一套浅色,一套深色。
图片:Astro 怎样处理本地和远程文件
src/components/common/Image.astro 是全站共用的图片组件,按来源走两条路:
- 本地图片(
~/assets/images/...或 ESM import)交给astro:assets的<Image />。构建时 Sharp 按你要的widths缩放,转成 WebP(模板默认如此;format也可以要 AVIF),写到dist/_astro/,文件名带哈希。 - 远程图片如果在已知的 CDN 上(Unsplash、Cloudinary、Imgix,以及
unpic认识的其他服务),不会下载到本地。组件用 CDN 自己的缩放参数改写 URL,并据此生成srcset,浏览器直接从 CDN 取合适的尺寸。
src/utils/images.ts 把字符串路径解析成已经导入的资源,并准备 Open Graph 图片。这些图片需要绝对 URL 和固定尺寸。
元数据:标题、规范链接和 Open Graph 是怎么拼出来的
src/components/common/Metadata.astro 合并三层:config.yaml 里的默认值、页面传给布局的 metadata 对象,以及文章的 front matter。合并结果交给 astro-seo,输出标题(套用 %s — Site 这个模板)、描述、robots 指令、规范链接、Open Graph 和 Twitter 标签。文章还会加上 article:published_time、article:modified_time、栏目和标签。getCanonical() 根据页面路径推出规范 URL,所以页面一般不用手写,除非同一份内容还住在别处。
结构化数据有三种。每篇文章输出 BlogPosting 和 BreadcrumbList 的 JSON-LD。FAQs 小组件在传入 schema 时输出 FAQPage。其他类型(Organization、Product、HowTo)是页面里的一小段脚本,通过布局的 head 插槽放进去,写法见 .agents/skills/add-structured-data.md。
布局和客户端
src/layouts/Layout.astro 是 HTML 外壳。顺序是:字体(通过 Astro 的 Fonts API 自行托管)、CustomStyles.astro 里的配色、一段内联脚本(首次绘制前,根据 localStorage 或 ui.theme 加上 dark class)、元数据、站点验证和分析(Google Analytics 在你填上 id 之前是关的;Partytown 集成已经接好,但默认关闭),以及用来做视图过渡的 <ClientRouter />。
模板自带的 JavaScript 很少,而且写成自定义元素,这样客户端导航之后不用手动重新初始化。页眉会在页面之间保留自己的状态;如果某一页自带菜单,就换成那一页的。intersect-* 工具类背后的滚动显现动画,是一个很小的观察器。每个交互小组件(标签页、价格切换、画廊灯箱、倒计时、视频、粘住不走的行动号召)都是自己的自定义元素,几十行而已,脚本没跑起来时有静态的退路。FAQ 手风琴完全不需要脚本:它就是一组原生的 <details>。
样式:Tailwind v4 的 token 和 CustomStyles.astro
Tailwind CSS 4 的配置写在 CSS 里,不在 JavaScript 里。src/assets/styles/tailwind.css 把 CustomStyles.astro 的 --aw-* 色板重新导出成 Tailwind 的 token,声明 dark 和 intersect 变体、按钮用的工具类,以及文章正文用的排版插件。从它引入的 shadcn.css 把同一组变量映射成 shadcn/ui 组件期望的 token 名字。怎么改色板、字体和 logo,写在定制指南里。
构建:Astro 会输出什么
npm run build 跑的是 Astro 的静态构建。站点地图集成写出 sitemap-index.xml,里面是每一个页面(搜索引擎再根据各页的 robots meta 决定索引什么)。rss.xml.ts 用博客同一份文章列表写出订阅源。HTML、CSS 和 JavaScript 会被压缩。集成再更新 robots.txt。dist/ 里是普通文件,没有服务端组件,也不需要环境变量。
如果想改其中任何一环,可以看这几处:配置在 vendor/integration/,永久链接、文章和图片在 src/utils/,每个页面都会加载的东西在 src/layouts/Layout.astro。其余都是小组件。读过 AGENTS.md 的AI 编程助手也认得这张地图。