· Documentation · 8 min read

AstroWind 这个 Astro 模板内部是怎么工作的

AstroWind 模板的内部机制。配置集成、永久链接和 base path、博客流水线、图片处理、元数据,以及构建。

机械表机芯的内部

你可以连续几个月使用 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';

任何组件都可以导入这些常量;没有运行时查找,也不用一层层传 props。同一个集成把 YAML 里的 site、base 和 trailingSlash 写进 Astro 自己的配置,所以站点地图、RSS 和规范 URL 都认同一个来源;构建之后,它还会让 robots.txt 指向生成的站点地图。

永久链接和 base path

模板里的每个站内链接都经过 src/utils/permalinks.ts。getPermalink(slug, type) 为页面、文章、分类、标签或资源拼出路径,加上配置里的 base,并应用 trailingSlash 策略。这就是为什么部署到子目录大多只是改配置:设 base: '/handbook',每个链接、图片和 feed 都会跟着走。deploy-with-base-path 技能说明了少数不能对 getPermalink() 用两次的值。

文章 URL 来自 config.yaml 里的 apps.blog.post.permalink 模式。默认的 /%slug% 把文章放在站点根上;/blog/%year%/%month%/%slug% 或 /%category%/%slug% 也可以,因为加载内容集合时会按每篇文章展开这个模式。列表、分类和标签的路径在同一块里配置,每条路由都可以单独关掉,或单独标成 noindex。

博客流水线:从 Markdown 到页面

文章是 src/content.config.ts 里定义的 Astro 内容集合:一个 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 导入)交给 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)是页面里的一小段脚本,按 .agents/skills/add-structured-data.md 所说,通过布局的 head 插槽放进去。

布局和客户端

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 令牌和 CustomStyles.astro

Tailwind CSS 4 在 CSS 里配置,不在 JavaScript 里:src/assets/styles/tailwind.css 把 CustomStyles.astro 的 --aw-* 调色板重新导出为 Tailwind 令牌,声明 dark 和 intersect 变体、按钮工具类,以及给文章内容用的排版插件。从它导入的 shadcn.css 把同一组变量映射成 shadcn/ui 组件期望的令牌名。怎么改调色板、字体和 Logo,是定制指南的内容。

构建:Astro 输出什么

npm run build 运行 Astro 的静态构建。站点地图集成写出 sitemap-index.xml,包含每一页(搜索引擎根据每页的 robots meta 决定索引什么);rss.xml.ts 用博客同一份文章列表写出 feed;HTML、CSS 和 JavaScript 会被压缩;集成会更新 robots.txt。dist/ 里的结果是普通文件,没有服务端组件,也不需要环境变量。

如果想改这里的任何机制,该看的地方是:配置看 vendor/integration/,永久链接、文章和图片看 src/utils/,每一页加载什么看 src/layouts/Layout.astro。其余都是组件。读过 AGENTS.md 的AI 编程助手也知道这张地图。

Share:
Back to Blog

Related Posts

View All Posts »

Markdown 元素演示

博客能渲染的每一种 Markdown 和 MDX 元素都放在这一页,用来看标题、列表、表格、代码、图片和嵌入在默认样式下的样子。