PageSourceSearch

https://lantian.pub/article/modify-website/migrate-blog-to-astro-js.lantian/

html lantian.pub collected 2026-10-02 04:26:56 UTC 170,502 bytes, 584 lines download raw bytes

1<!DOCTYPE html><html lang="zh-CN" itemscope="" itemtype="https://schema.org/Article" data-astro-cid-pgt5l3fs=""><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>使用 Astro.js 重构我的博客 - Lan Tian @ Blog</title>
1<script>
2  ;(() => {
3    try {
4      const mode = localStorage.getItem('user-color-scheme')
5      if (mode == 'dark' || mode == 'light') {
6        document.documentElement.setAttribute('data-user-color-scheme', mode)
7      }
8    } catch {
9      // Ignore errors
10    }
11  })()
12  ;(() => {
13    window.lantianImageFallback = (img, src) => {
14      img.onerror = null
15      img.srcset = src
16      img.src = src
17      if (img.parentNode.nodeName.toLowerCase() === 'picture') {
18        const picture = img.parentNode
19        picture.innerHTML = ''
20        picture.appendChild(img)
21      }
22    }
23  })()
24</script>
24<link rel="stylesheet" href="/assets/PostText.DhvrU1vO.css"><style>.post-image-wrap[data-astro-cid-axmlwxhn]{margin:0 -5px}.post-image-wrap[data-astro-cid-axmlwxhn] img[data-astro-cid-axmlwxhn]{-o-object-fit:cover;object-fit:cover;-o-object-position:center;object-position:center;height:150px}@media (width>=768px){.post-image-wrap[data-astro-cid-axmlwxhn]{float:right;padding:5px}.post-image-wrap[data-astro-cid-axmlwxhn] img[data-astro-cid-axmlwxhn]{max-width:200px}}@media (width<=767.98px){.post-image-wrap[data-astro-cid-axmlwxhn]{text-align:center}.post-image-wrap[data-astro-cid-axmlwxhn] img[data-astro-cid-axmlwxhn]{max-width:100%}}#post-navi[data-astro-cid-wlifdhof]{border-top:1px solid var(--lt-border-weak);border-bottom:1px solid var(--lt-border-weak);padding:5px}
25</style>
25<script type="module" src="/assets/PageLayout.astro_astro_type_script_index_0_lang.I87Dj7a_.js"></script>
25<meta http-equiv="X-UA-Compatible" content="IE=edge"><meta http-equiv="Referrer-Policy" content="no-referrer, strict-origin-when-cross-origin"><meta name="renderer" content="webkit|ie-comp|ie-stand"><meta name="imagemode" content="force"><meta name="nightmode" content="disable"><meta name="wap-font-scale" content="no"><meta name="title" content="使用 Astro.js 重构我的博客 - Lan Tian @ Blog"><meta name="keywords" content=""><meta name="author" content="Lan Tian"><link rel="canonical" href="https://lantian.pub/article/modify-website/migrate-blog-to-astro-js.lantian/"><link rel="alternate" type="application/rss+xml" title="Lan Tian @ Blog" href="https://lantian.pub/rss2.xml"><link rel="alternate" type="application/atom+xml" title="Lan Tian @ Blog" href="https://lantian.pub/feed.xml"><link rel="alternate" type="application/rss+xml" title="Lan Tian @ Blog (Chinese Simplified / 简体中文)" href="https://lantian.pub/rss2.zh.xml" hreflang="zh"><link rel="alternate" type="application/atom+xml" title="Lan Tian @ Blog (Chinese Simplified / 简体中文)" href="https://lantian.pub/feed.zh.xml" hreflang="zh"><link rel="alternate" type="application/json" title="Lan Tian @ Blog (Chinese Simplified / 简体中文)" href="https://lantian.pub/feed.zh.json" hreflang="zh"><link rel="alternate" type="application/rss+xml" title="Lan Tian @ Blog (English)" href="https://lantian.pub/rss2.en.xml" hreflang="en"><link rel="alternate" type="application/atom+xml" title="Lan Tian @ Blog (English)" href="https://lantian.pub/feed.en.xml" hreflang="en"><link rel="alternate" type="application/json" title="Lan Tian @ Blog (English)" href="https://lantian.pub/feed.en.json" hreflang="en"><link rel="alternate" href="https://lantian.pub/article/modify-website/migrate-blog-to-astro-js.lantian/" hreflang="zh"><link rel="alternate" href="https://lantian.pub/en/article/modify-website/migrate-blog-to-astro-js.lantian/" hreflang="en"><meta name="copyright" content="Copyright 2012-2026 Lan Tian @ Blog"><link rel="icon" type="image/x-icon" href="/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="manifest" href="/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" media="(prefers-color-scheme: light)" content="#03a9f4"><meta name="theme-color" media="(prefers-color-scheme: dark)" content="#212121"><meta name="application-name" content="Lan Tian @ Blog"><link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/apple-touch-icon-1024x1024.png"><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-precomposed.png"><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#03a9f4"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="Lan Tian @ Blog"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-1620x2
25160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#bbdefb"><meta name="msapplication-TileImage" content="/mstile-144x144.png"><meta name="msapplication-config" content="/browserconfig.xml"><link rel="yandex-tableau-widget" href="/yandex-browser-manifest.json"><meta property="og:url" content="https://lantian.pub/article/modify-website/migrate-blog-to-astro-js.lantian/"><meta property="og:type" content="website"><meta property="og:title" content="使用 Astro.js 重构我的博客 - Lan Tian @ Blog"><meta property="og:image" content="https://lantian.pub/apple-touch-icon.png"><meta content="前言 差不多四年半前, 我把博客系统从 Typecho 迁移到了 Hexo 。Hexo 是一款静态网页生成器(Static Site Generator),本身无需连接 MySQL、PostgreSQL 等数据库动态生成网页,而是根据给定的 Markdown 文章文件一次性生成所有的 HTML 网页。相比于 WordPress、Typecho 等动态方案,预先生成静态网页不需要服务器支持 PHP 等动态语言,对服务器的性能压力更低。 但是 Hexo 本身并不新, 它的第一个版本发布于 2013 年 7 月 。当时以 React 为首的单页应用框架刚刚发布(React:2013 年 5 月),Webpack 等现代前端流程必须的框架也还不存在(Webpack:2014 年 2 月),因此 Hexo 理所当然的没有用上这些工具,而是停留在拼接 HTML 字符串的时代。Hexo 常用的几个主题模板系统 EJS,Pug 等都是基于字符串拼接的模板系统。 没有了现代前端流程,就意味着: 我需要手动处理拼接 HTML 的所有细节。每次修改模板都让我想起写 PHP 的美好时光。..."><meta property="og:site_name" content="Lan Tian @ Blog"><meta property="og:locale" content="zh"><meta name="twitter:card" content="summary"><meta name="twitter:url" content="https://lantian.pub/article/modify-website/migrate-blog-to-astro-js.lantian/"><meta name="twitter:title" content="使用 Astro.js 重构我的博客 - Lan Tian @ Blog"><meta property="twitter:image" content="https://lantian.pub/apple-touch-icon.png"><meta content="前言 差不多四年半前, 我把博客系统从 Typecho 迁移到了 Hexo 。Hexo 是一款静态网页生成器(Static Site Generator),本身无需连接 MySQL、PostgreSQL 等数据库动态生成网页,而是根据给定的 Markdown 文章文件一次性生成所有的 HTML 网页。相比于 WordPress、Typecho 等动态方案,预先生成静态网页不需要服务器支持 PHP 等动态语言,对服务器的性能压力更低。 但是 Hexo 本身并不新, 它的第一个版本发布于 2013 年 7 月 。
25当时以 React 为首的单页应用框架刚刚发布(React:2013 年 5 月),Webpack 等现代前端流程必须的框架也还不存在(Webpack:2014 年 2 月),因此 Hexo 理所当然的没有用上这些工具,而是停留在拼接 HTML 字符串的时代。Hexo 常用的几个主题模板系统 EJS,Pug 等都是基于字符串拼接的模板系统。 没有了现代前端流程,就意味着: 我需要手动处理拼接 HTML 的所有细节。每次修改模板都让我想起写 PHP 的美好时光。..."><meta name="twitter:dnt" content="on"><meta itemprop="name" content="使用 Astro.js 重构我的博客 - Lan Tian @ Blog"><meta property="image" content="https://lantian.pub/apple-touch-icon.png"><meta content="前言 差不多四年半前, 我把博客系统从 Typecho 迁移到了 Hexo 。Hexo 是一款静态网页生成器(Static Site Generator),本身无需连接 MySQL、PostgreSQL 等数据库动态生成网页,而是根据给定的 Markdown 文章文件一次性生成所有的 HTML 网页。相比于 WordPress、Typecho 等动态方案,预先生成静态网页不需要服务器支持 PHP 等动态语言,对服务器的性能压力更低。 但是 Hexo 本身并不新, 它的第一个版本发布于 2013 年 7 月 。当时以 React 为首的单页应用框架刚刚发布(React:2013 年 5 月),Webpack 等现代前端流程必须的框架也还不存在(Webpack:2014 年 2 月),因此 Hexo 理所当然的没有用上这些工具,而是停留在拼接 HTML 字符串的时代。Hexo 常用的几个主题模板系统 EJS,Pug 等都是基于字符串拼接的模板系统。 没有了现代前端流程,就意味着: 我需要手动处理拼接 HTML 的所有细节。每次修改模板都让我想起写 PHP 的美好时光。..."><meta content="前言 差不多四年半前, 我把博客系统从 Typecho 迁移到了 Hexo 。Hexo 是一款静态网页生成器(Static Site Generator),本身无需连接 MySQL、PostgreSQL 等数据库动态生成网页,而是根据给定的 Markdown 文章文件一次性生成所有的 HTML 网页。相比于 WordPress、Typecho 等动态方案,预先生成静态网页不需要服务器支持 PHP 等动态语言,对服务器的性能压力更低。 但是 Hexo 本身并不新, 它的第一个版本发布于 2013 年 7 月 。当时以 React 为首的单页应用框架刚刚发布(React:2013 年 5 月),Webpack 等现代前端流程必须的框架也还不存在(Webpack:2014 年 2 月),因此 Hexo 理所当然的没有用上这些工具,而是停留在拼接 HTML 字符串的时代。Hexo 常用的几个主题模板系统 EJS,Pug 等都是基于字符串拼接的模板系统。 没有了现代前端流程,就意味着: 我需要手动处理拼接 HTML 的所有细节。每次修改模板都让我想起写 PHP 的美好时光。..."><link rel="me" href="https://mastodon.social/@lantian"></head><body data-astro-cid-pgt5l3fs=""><div id="bg" data-astro-cid-fpl3fizy=""></div><div id="container" class="container" data-astro-cid-pgt5l3fs=""><nav class="lantian navbar navbar-expand-lg" data-astro-cid-b2i3gsw2=""><div class="container-fluid" data-astro-cid-b2i3gsw2=""><a class="navbar-brand" href="/" data-astro-cid-b2i3gsw2="">Lan Tian @ Blog</a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#lantian-navbar" aria-controls="lantian-navbar" aria-expanded="false" aria-label="Toggle navigation" data-astro-cid-b2i3gsw2=""><span class="navbar-toggler-icon" data-astro-cid-b2i3gsw2=""></span></button><div class="collapse navbar-collapse" id="lantian-navbar" data-astro-cid-b2i3gsw2=""><ul class="navbar-nav" data-astro-cid-b2i3gsw2=""><li class="nav-item d-none d-lg-block" data-astro-cid-b2i3gsw2=""><a class="nav-link" href="/page/archive/index.html" data-astro-cid-b2i3gsw2="">文章们</a></li><li class="nav-item d-none d-lg-block" data-astro-cid-b2i3gsw2=""><a class="nav-link" href="/page/himawari/index.html" data-astro-cid-b2i3gsw2="">俯瞰地球</a></li><li class="nav-item d-none d-lg-block" data-astro-cid-b2i3gsw2=""><a class="nav-link" href="/page/dn42/index.html" data-astro-cid-b2i3gsw2="">DN42</a></li><li class="nav-item dropdown d-block d-lg-none" data-astro-cid-b2i3gsw2=""><a href="#" class="nav-link dropdown-toggle" id="pages-dropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-astro-cid-b2i3gsw2=""><i class="fas fa-fw fa-file" data-astro-cid-b2i3gsw2=""></i> 页面 <span class="caret" data-astro-cid-b2i3gsw2=""></span></a><ul class="dropdown-menu" aria-labelledby="pages-dropdown" data-astro-cid-b2i3gsw2=""><li data-astro-cid-b2i3gsw2=""><a class="dropdown-item" href="/page/archive/index.html" data-astro-cid-b2i3gsw2="">文章们</a></li><li data-astro-cid-b2i3gsw2=""><a class="dropdown-item" href="/page/himawari/index.html" data-astro-cid-b2i3gsw2="">俯瞰地球</a></li><li data-astro-cid-b2i3gsw2=""><a class="dropdown-item" href="/page/dn42/index.html" data-astro-cid-b2i3gsw2="">DN42</a></li></ul></li></ul><ul class="navbar-nav ms-auto" data-astro-cid-b2i3gsw2=""><li class="nav-item dropdown" data-astro-cid-b2i3gsw2=""><a href="#" class="nav-link dropdown-toggle" id="languages-dropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-astro-cid-b2i3gsw2=""><i class="fas fa-fw fa-language" data-astro-cid-b2i3gsw2=""></i>Language<span class="caret" data-astro-cid-b2i3gsw2=""></span></a><ul class="dropdown-menu" aria-labelledby="languages-dropdown" data-astro-cid-b2i3gsw2=""><li data-astro-cid-b2i3gsw2=""><a class="dropdown-item active" href="/article/modify-website/migrate-blog-to-astro-js.lantian/" data-astro-cid-b2i3gsw2=""><span class="flag-icon flag-icon-cn" data-astro-cid-b2i3gsw2=""></span>Chinese Simplified / 简体中文</a></li><li data-astro-cid-b2i3gsw2=""><a class="dropdown-item" href="/en/article/modify-website/migrate-blog-to-astro-js.lantian/" data-astro-cid-b2i3gsw2=""><span class="flag-icon flag-icon-us" data-astro-cid-b2i3gsw2=""></span>English</a></li><li data-astro-cid-b2i3gsw2=""><hr class="dropdown-divider" data-astro-cid-b2i3gsw2=""></li><li data-astro-cid-b2i3gsw2=""><a class="dropdown-item disabled" href="#" data-astro-cid-b2i3gsw2="">Not all pages have translations.</a></li><li data-astro-cid-b2i3gsw2=""><a class="dropdown-item disabled" href="#" data-astro-cid-b2i3gsw2="">You may see 404 - this is normal.</a></li></ul></li><li class="nav-item dropdown" data-astro-cid-b2i3gsw2=""><a href="#" class="nav-link dropdown-toggle" id="color-scheme-dropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-astro-cid-b2i3gsw2=""><i class="fas fa-fw fa-adjust" data-astro-cid-b2i3gsw2=""></i>夜间模式</a><ul class="dropdown-menu" aria-labelledby="color-scheme-dropdown" data-astro-cid-b2i3gsw2=""><li data-astro-cid-b2i3gsw2=""><a id="color-scheme-auto" class="dropdown-item" href="#" data-astro-cid-b2i3gsw2=""><i class="fas fa-fw fa-adjust" data-astro-cid-b2i3gsw2=""></i>自动</a></li><li data-astro-cid-b2i3gsw2=""><hr class="dropdown-divider" data-astro-cid-b2i3gsw2=""></li><li data-astro-cid-b2i3gsw2=""><a id="color-scheme-light" class="dropdown-item" href="#" data-astro-cid-b2i3gsw2=""><i class="fas fa-fw fa-sun" data-astro-cid-b2i3gsw2=""></i>浅色</a></li><li data-astro-cid-b2i3gsw2="">
25<a id="color-scheme-dark" class="dropdown-item" href="#" data-astro-cid-b2i3gsw2=""><i class="fas fa-fw fa-moon" data-astro-cid-b2i3gsw2=""></i>深色</a></li></ul></li></ul></div></div></nav>
25<script type="module">var e=`user-color-scheme`,t=`data-user-color-scheme`,n=(e,t)=>{try{localStorage.setItem(e,t)}catch{}},r=e=>{try{return localStorage.getItem(e)}catch{return null}},i=r=>{let i={dark:!0,light:!0,auto:!0},a=r===null||!Object.keys(i).includes(r)?`auto`:r;n(e,a);for(let e in i){let t=document.getElementById(`color-scheme-`+e);t&&(e==a?t.classList.add(`active`):t.classList.remove(`active`))}a==`auto`?document.documentElement.removeAttribute(t):document.documentElement.setAttribute(t,a)};i(r(e));var a=document.getElementById(`color-scheme-auto`);a&&a.addEventListener(`click`,()=>i(`auto`));var o=document.getElementById(`color-scheme-light`);o&&o.addEventListener(`click`,()=>i(`light`));var s=document.getElementById(`color-scheme-dark`);s&&s.addEventListener(`click`,()=>i(`dark`));</script>
25<div class="d-flex" data-astro-cid-pgt5l3fs=""><div id="content" data-astro-cid-pgt5l3fs=""><article><div class="d-flex"><div class="post-info-wrap" data-astro-cid-65ryylzs=""><div class="post-info" data-astro-cid-65ryylzs=""><div class="post-meta-wrap" data-astro-cid-h7k7n6gs=""><div class="post-meta" data-astro-cid-h7k7n6gs=""><p data-astro-cid-h7k7n6gs=""><span class="fas fa-clock" data-astro-cid-h7k7n6gs=""></span><br data-astro-cid-h7k7n6gs=""><small data-astro-cid-h7k7n6gs="">04-20</small></p><div class="post-meta-value" data-astro-cid-h7k7n6gs="">发布于<span class="badge badge-tag" data-astro-cid-65ryylzs="">2024-04-20 16:36</span></div></div></div><div class="post-meta-wrap" data-astro-cid-h7k7n6gs=""><div class="post-meta" data-astro-cid-h7k7n6gs=""><p data-astro-cid-h7k7n6gs=""><span class="fas fa-file-alt" data-astro-cid-h7k7n6gs=""></span><br data-astro-cid-h7k7n6gs=""><small data-astro-cid-h7k7n6gs="">网站与服务端</small></p><div class="post-meta-value" data-astro-cid-h7k7n6gs="">分类 <a class="badge badge-tag" href="/category/modify-website" data-astro-cid-65ryylzs="">网站与服务端</a></div></div></div><div class="post-meta-wrap" data-astro-cid-h7k7n6gs=""><div class="post-meta" data-astro-cid-h7k7n6gs=""><p data-astro-cid-h7k7n6gs=""><span class="fas fa-tag" data-astro-cid-h7k7n6gs=""></span><br data-astro-cid-h7k7n6gs=""><small data-astro-cid-h7k7n6gs="">2 标签</small></p><div class="post-meta-value" data-astro-cid-h7k7n6gs="">标签 <a class="badge badge-tag" href="/tag/Astro-js" data-astro-cid-65ryylzs="">Astro.js</a> <a class="badge badge-tag" href="/tag/博客" data-astro-cid-65ryylzs="">博客</a> </div></div></div><div class="post-meta-wrap" data-astro-cid-h7k7n6gs=""><div class="post-meta" data-astro-cid-h7k7n6gs=""><p data-astro-cid-h7k7n6gs=""><span class="fas fa-list" data-astro-cid-h7k7n6gs=""></span><br data-astro-cid-h7k7n6gs=""><small data-astro-cid-h7k7n6gs="">目录</small></p><div class="post-meta-value" data-astro-cid-h7k7n6gs=""><ol data-astro-cid-jkcjrl7z=""><li class="toc-item toc-level-1" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#前言" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">前言</span></a></li><li class="toc-item toc-level-1" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#方案选择" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">方案选择</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#reactvuesvelte-等-spa-单页应用方案" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">React、Vue、Svelte 等 SPA 单页应用方案</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#sukka-的-hexo--nextjs-方案" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">Sukka 的 Hexo + Next.js 方案</span></a></li><li class="toc-item toc-level-1" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#最终方案astrojs" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">最终方案:Astro.js</span></a></li><li class="toc-item toc-level-1" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#博客系统架构" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">博客系统架构</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#生成基础模板" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">生成基础模板</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#文章显示" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">文章显示</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#多语言" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">多语言</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#文章摘要" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">文章摘要</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#分页" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">分页</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#匹配-hexo-的-url" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">匹配 Hexo 的 URL</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#island岛屿功能" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">Island(岛屿)功能</span></a></li><li class="toc-item toc-level-2" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#后处理插件例css-内联" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">后处理插件(例:CSS 内联)</span></a></li><li class="toc-item toc-level-1" data-astro-cid-jkcjrl7z=""><a class="toc-link" href="#总结" data-astro-cid-jkcjrl7z=""><span class="toc-text" data-astro-cid-jkcjrl7z="">总结</span></a></li></ol></div></div></div></div></div><div class="post-wrap"><h1 class="post-title"><a href="/article/modify-website/migrate-blog-to-astro-js.lantian/" rel="bookmark" title="使用 Astro.js 重构我的博客">使用 Astro.js 重构我的博客</a></h1><div class="post-text"><h1 id="前言">前言</h1>
26<p>差不多四年半前,<a href="https://lantian.pub/article/modify-website/switching-to-hexo-static-site-generator.lantian/" rel="noopener noreferrer" target="_blank">我把博客系统从 Typecho 迁移到了 Hexo</a>。Hexo
27是一款静态网页生成器(Static Site Generator),本身无需连接 MySQL、PostgreSQL 等数据库动态生成网页,而是根据给定的 Markdown 文章文件一次性生成所有的 HTML 网页。相比于 WordPress、Typecho 等动态方案,预先生成静态网页不需要服务器支持 PHP 等动态语言,对服务器的性能压力更低。</p>
28<p>但是 Hexo 本身并不新,<a href="https://github.com/hexojs/hexo/releases/tag/1.0.0" rel="noopener noreferrer" target="_blank">它的第一个版本发布于 2013 å¹´ 7 月</a>。当时以 React 为首的单页应用框架刚刚发布(React:2013 å¹´ 5 月),Webpack 等现代前端流ç¨
28‹å¿…须的框架也还不存在(Webpack:2014 å¹´ 2 月),因此 Hexo 理所当然的没有用上这些工具,而是停留在拼接 HTML 字符串的时代。Hexo 常用的几个主题模板系统
29EJS,Pug 等都是基于字符串拼接的模板系统。</p>
30<p>没有了现代前端流程,就意味着:</p>
31<ul>
32<li>我需要手动处理拼接 HTML 的所有细节。每次修改模板都让我想起写 PHP 的美好时光。</li>
33<li>我无法方便地使用 Sass,PostCSS 等 CSS 工具,Unified.js(Remark/Rehype)等可扩展 Markdown 解析器。虽然有一些 Hexo 插件可以调用这些功能,但它们大都停留在「能跑就行」的阶段,只支持原作者用到的几个功能,而且依赖版本还停留在原作者编写插件的时代。
34<ul>
35<li>例如:<a href="https://github.com/nejj/hexo-renderer-webpack" rel="noopener noreferrer" target="_blank">hexo-renderer-webpack</a>
36的依赖版本停留在 5 年前。<a href="https://github.com/knksmith57/hexo-renderer-sass" rel="noopener noreferrer" target="_blank">hexo-renderer-sass</a>
37最后一次更新是 10 个月前。<a href="https://github.com/LikaKavkasidze/hexo-renderer-unified" rel="noopener noreferrer" target="_blank">hexo-renderer-unified</a>
38也停留在 5 年前。</li>
39<li>不过我确实有办法使用 Webpack:我单独写了一套 Webpack 配置文件,在 CI 的构建脚本里加一步调用 Webpack,把生成的 JS、CSS 放进 Hexo 的主题目录,然后在 Hexo
40的模板里手写 HTML 调用。</li>
41</ul>
42</li>
43</ul>
44<p>除此之外,还有另外几个原因促使我寻找新的静态网页生成器框架:</p>
45<ul>
46<li>即使是最新的 7.x 版本,Hexo 也还无法在主题、插件中使用 ESM Module,也就意味着难以使用 Typescript。而众所周知,手写弱类型的 Javascript 很容易写出 Bug。</li>
47</ul>
48<p><picture><source srcset="/usr/uploads/202404/the-javascript-trinity.jpg.webp" type="image/webp"><source srcset="/usr/uploads/202404/the-javascript-trinity.jpg.avif" type="image/avif"><source srcset="/usr/uploads/202404/the-javascript-trinity.jpg.jxl" type="image/jxl"><img src="/usr/uploads/202404/the-javascript-trinity.jpg" alt="Javascript Trinity"></picture></p>
49<p>(图源 <a href="https://javascriptwtf.com/" rel="noopener noreferrer" target="_blank">https://javascriptwtf.com/</a>)</p>
50<ul>
51<li>Hexo 无法很好支持一些我的个人需求,例如多语言支持。这些都需要我写插件对逻辑进行定制或者替换。随着定制进行,我发现整套系统成了忒修斯之船:如果我换掉了 Hexo
52的一堆功能,那我还用 Hexo 干什么?</li>
53</ul>
54<p>因此为了满足我的需求,我开始寻找新的静态网站生成方案。</p>
55<h1 id="方案选择">方案选择</h1>
56<p>我对新方案有以下要求:</p>
57<ul>
58<li>能够集成现代的前端工具链。</li>
59<li>做好将模板转换成 HTML/CSS/JS 这一步,而且模板格式最好接近 React JSX,而非手动拼接字符串。
60<ul>
61<li>但是我不想要将整个网页放在浏览器端用 Javascript 渲染的笨重方案,我的个人博客不是复杂的单页应用。我希望 Javascript 是博客网页的可选项,而不是必须部分。</li>
62</ul>
63</li>
64<li>有一个可定制的网页路由系统。我需要保持迁移前后每篇文章的 URL 不变。</li>
65<li>最好有一套简单的文章管理系统,可以加载我的 Markdown 文章并且读取它们的
66Frontmatter 中的信息。但因为手写一套文章管理系统也很简单,所以这不是必须项。</li>
67</ul>
68<p>于是经过搜索,我考虑了以下几个方案:</p>
69<h2 id="reactvuesvelte-等-spa-单页应用方案">React、Vue、Svelte 等 SPA 单页应用方案</h2>
70<p>React 是现代单页应用框架的祖师爷,可以说完全改变了前端的开发方式。选择 React 自然不会在使用现代前端工具链时遇到问题,因为 React 就是现代前端本身,绝大多数前端工具开发初始就会考虑到对 React 的支持。</p>
71<p>但是 React 有一个很大的缺点:它的整个网页都是在浏览器上用 Javascript 渲染的。这就意味着:</p>
72<ul>
73<li>用户浏览网页时需要加载一个很大的 Javascript 文件,然后消耗相当多的 CPU 资源渲染出 HTML DOM。</li>
74<li>如果用户难以/不想使用 JS,例如开了 NoScript 插件,或者网络连接很慢,那么他们将完全无法查看网页内容,而不是像传统 HTML 一样,即使没有了 CSS/JS 也至少可以看到文字内容。</li>
75</ul>
76<p>简单的说,我希望禁用 JS 的浏览器也可以正常打开网页(可能丢失一些不重要的功能)。因此我排除了 React 框架,以及与其类似的 Vue、Svelte 等面向单页应用的网页框架。</p>
77<h2 id="sukka-的-hexo--nextjs-方案">Sukka 的 Hexo + Next.js 方案</h2>
78<p><a href="https://blog.skk.moe/post/use-nextjs-and-hexo-to-rebuild-my-blog/" rel="noopener noreferrer" target="_blank">苏卡卡在 2022 年将自己的博客从纯 Hexo 迁移到了 Hexo + Next.js 方案</a>。他保留了 Hexo 作为文章管理系统以及使用少量插件,而博客网页本身使用 Next.js 框架生成。</p>
79<p>相比于 React,Next.js 会预先渲染出整个 HTML DOM,因此即使禁用 Javascript 也可以看到网页的基本内容。网页加载完成后,Next.js 框架的 Javascript 库再给浏览器端的静态组件加上单页应用的动态功能。</p>
80<p>这种方案确实能满足我的「Javascript 必须可选」的要求,但是依然有一些问题:</p>
81<ul>
82<li>Next.js 加载的额外 JS 代码我基本用不到。
83<ul>
84<li>单页应用主要用于与用户有大量交互的网站,但我的博客以文章内容为主,并没有那么多交互控件。</li>
85<li>单页应用的另一个主要优势是无缝加载,而我使用
86<a href="https://instant.page/" rel="noopener noreferrer" target="_blank">Instant.page</a>
86 加浏览器缓存也可以做到很快的加载速度。</li>
87</ul>
88</li>
89<li>苏卡卡选择 Hexo 是因为他是 Hexo 的核心开发者,Hexo 刚好可以满足他的需求,而我不是。我不需要 Hexo 的复杂的文章管理逻辑,因为我不使用草稿,不需要自动帮我生成文章模板,几乎不会使用 <code>hexo generate</code> 以外的任何命令。而且我本身就会在 Hexo
90的文章、页面列表上做二次过滤,来满足我的多语言需求。</li>
91</ul>
92<p>因此我依然没有选择这种方案,而是选择了另一款框架:</p>
93<h1 id="最终方案astrojs">最终方案:Astro.js</h1>
94<p><a href="https://astro.build/" rel="noopener noreferrer" target="_blank">Astro.js</a> 宣称它是「为内容网站设计的网页框架」。它和
95React、Next.js 等针对单页应用设计的网页框架有很大的不同:</p>
96<ul>
97<li>Astro.js 不支持单页应用的复杂组件。但这也意味着它本身不需要在最终网页中引入额外的 JS 代码。如果我不主动加入 JS,Astro.js 生成的网页就会是一行 JS 代码都没有的纯静态网页。</li>
98<li>但是这也不意味着网站上一点复杂功能都不能有。Astro.js 的主打功能 Island(岛屿),可以在页面中生成一些互相隔离的区域,并在其中使用 React、Vue 等框架。由于岛屿间互相隔离,你甚至可以混用不同网页框架。</li>
99</ul>
100<p>除此以外,Astro.js 也满足了我的其它要求:</p>
101<ul>
102<li>Astro.js 的模板是拼接类似 JSX 的组件,而不是拼接 HTML 字符串。如果你有 React
103开发经验,你会觉得模板语法很熟悉。</li>
104<li>Astro.js 自带一套路由系统,可以完全自定义每个页面的地址,也可以用同一模板为每篇文章批量生成页面。</li>
105<li>Astro.js 自带一套很简单的文章管理系统(称为 Collection),支持 Markdown 和
106MDX。而且它除了加载 Markdown Frontmatter 和渲染 Markdown 之外不做任何事,给用户最大的自由度。</li>
107<li>Astro.js 基于现代前端工具链(Rollup.js,Vite.js,Unified.js 等)开发,对各种插件有良好的支持。</li>
108</ul>
109<p>但是 Astro.js 不是一个开箱即用的博客系统,它只是一套框架,还需要做不少的开发。我在开发博客系统时,也遇到了 Astro.js 的一些限制:</p>
110<ul>
111<li>Astro.js 缺失一个比较重要的功能:无法将 Astro 组件单独渲染成 HTML,只能渲染整个网页。这导致获取文章摘要比较困难,但在整个网页渲染过程中是可以获取组件 HTML
112的,因此有技巧可以实现。</li>
113<li>Astro.js 依然有少量用户无法操控的细节,例如它会自动将网页用到的 JS 和 CSS 插入
114<code>&#x3C;head></code> 中,我无法在渲染中控制位置,只能在 HTML 生成完毕后做后处理。</li>
115</ul>
116<h1 id="博客系统架构">博客系统架构</h1>
117<p>选定了方案,就可以开始开发博客系统了。我的整个过程的所有 commit 可以在
118<a href="https://github.com/xddxdd/blog/tree/astro-dev" rel="noopener noreferrer" target="_blank">GitHub 上的 astro-dev 分支</a>看到。</p>
119<h2 id="生成基础模板">生成基础模板</h2>
120<p>Astro.js 本身提供了一个非常简单的博客模板,可以用 <code>npm create astro@latest</code> 生成:</p>
121<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-bash"><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">Need</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> to</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> the</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> following</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> packages:</span></span>
122<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">[email protected]</span></span>
123<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">Ok</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> to</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> proceed?</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (y)</span></span>
124<span class="line"></span>
125<span class="line" style="--line-indent:1ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> astro</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   Launch</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sequence</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> initiated.</span></span>
126<span class="line"></span>
127<span class="line" style="--line-indent:3ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   dir</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   Where</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> should</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">
127 we</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> create</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> your</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> new</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> project?</span></span>
128<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         ./blog</span></span>
129<span class="line"></span>
130<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  tmpl</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   How</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> would</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> you</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> like</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> to</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> start</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> your</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> new</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> project?</span></span>
131<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Use</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> blog</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> template</span></span>
132<span class="line"></span>
133<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    ts</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   Do</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> you</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> plan</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> to</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> write</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> TypeScript?</span></span>
134<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Yes</span></span>
135<span class="line"></span>
136<span class="line" style="--line-indent:3ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   use</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   How</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> strict</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> should</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> TypeScript</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> be?</span></span>
137<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Strict</span></span>
138<span class="line"></span>
139<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  deps</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   Install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> dependencies?</span></span>
140<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Yes</span></span>
141<span class="line"></span>
142<span class="line" style="--line-indent:3ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   git</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   Initialize</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> a</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> new</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> git</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> repository?</span></span>
143<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Yes</span></span>
144<span class="line"></span>
145<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      ✔</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">  Project</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> initialized!</span></span>
146<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         ■</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> Template</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> copied</span></span>
147<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         ■</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> TypeScript</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> customized</span></span>
148<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         ■</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> Dependencies</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> installed</span></span>
149<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         ■</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">
149 Git</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> initialized</span></span>
150<span class="line"></span>
151<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  next</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">   Liftoff</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> confirmed.</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> Explore</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> your</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> project!</span></span>
152<span class="line"></span>
153<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Enter</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> your</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> project</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> directory</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> using</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> cd</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ./blog</span></span>
154<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Run</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">
154 npm</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> run</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> dev</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> to</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> start</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> the</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> dev</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> server.</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> CTRL+C</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> to</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> stop.</span></span>
155<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Add</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> frameworks</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> like</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> react</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> or</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> tailwind</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> using</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> astro</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> add.</span></span>
156<span class="line"></span>
157<span class="line" style="--line-indent:9ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">         Stuck?</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> Join</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> us</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> at</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://astro.build/chat</span></span>
158<span class="line"></span>
159<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">╭─────╮</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">  Houston:</span></span>
160<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">│</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ◠</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ◡</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ◠</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">  Good</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> luck</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> out</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> there,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> astronaut!</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 🚀</span></span>
161<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">╰─────╯</span></span></code></pre>
162<h2 id="文章显示">文章显示</h2>
163<p>Astro.js 自带一套很简单的文章管理系统(称为 Collection),可以自动加载 Markdown
164文件,读取它们 Frontmatter 中的信息。在使用博客模板生成完项目后,可以找到
165<code>src/content/config.ts</code> 这个文件:</p>
166<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-typescript"><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">defineCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 'astro:content'</span></span>
167<span class="line"></span>
168<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> blog</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
168 = </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">defineCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">({</span></span>
169<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  type:</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 'content'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
170<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // Type-check frontmatter using a schema</span></span>
171<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  schema:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">object</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">({</span></span>
172<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    title:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
173<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    description:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
174<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // Transform string to Date object</span></span>
175<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    pubDate:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">coerce</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">date</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
176<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    updatedDate:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">coerce</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">date</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">().</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">optional</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
177<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    heroImage:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">().</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">optional</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
178<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }),</span></span>
179<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">})</span></span>
180<span class="line"></span>
181<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">// 注:在 Typescript 中 { blog } 等价于 { blog: blog }</span></span>
182<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> collections</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">blog</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> }</span></span></code></pre>
183<p>这个文件定义了一个名为 <code>blog</code> 的 Collection,并且设置了
184<code>title</code>,<code>description</code>,<code>pubDate</code>,<code>updatedDate</code>,<code>heroImage</code> 五项要从 Markdown
185Frontmatter 中读取的信息。这些参数和 Hexo 使用的不太一样,所以我们把它改成类似
186Hexo 的格式:</p>
187<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-typescript"><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">
187import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">defineCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 'astro:content'</span></span>
188<span class="line"></span>
189<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> article</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">defineCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">({</span></span>
190<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  type:</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 'content'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
191<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // Type-check frontmatter using a schema</span></span>
192<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  schema:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">object</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">({</span></span>
193<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    title:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
194<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    categories:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
195<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    tags:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">array</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()).</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">optional</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
196<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    date:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">coerce</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">date</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
197<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    image:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> z</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">().</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">optional</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(),</span></span>
198<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }),</span></span>
199<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">})</span></span>
200<span class="line"></span>
201<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> collections</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
202<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  article:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> article</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
203<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
204<p>(我实际使用的更复杂的 <code>config.ts</code> 可以在
205<a href="https://github.com/xddxdd/blog/blob/astro-dev/src/content/config.ts" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/astro-dev/src/content/config.ts</a>
206看到)</p>
207<p>下一步是把文章放进 <code>src/content/[Collection 名字]</code> 这个文件夹。因为我把文章的
208Collection 改名成了 <code>article</code>,所以我把所有文章都复制到了 <code>src/content/article</code>
209这个文件夹下。</p>
210<p>有了这组 Collection,接下来就要读取 Collection 的文章列表并生成网页了。Astro 的博客模板在 <code>src/pages/blog/[...slug].astro</code> 提供了一个简单的例子:</p>
211<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
212<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">type</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> CollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">getCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 'astro:content'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
213<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> BlogPost</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../layouts/BlogPost.astro'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
214<span class="line"></span>
215<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> async</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPaths</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">() {</span></span>
216<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">	const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'blog'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
217<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">	return</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">((</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ({</span></span>
218<span class="line" style="--line-indent:16ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">		params:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">slug:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">slug</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> },</span></span>
219<span class="line" style="--line-indent:16ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">		props:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
220<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">	}));</span></span>
221<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
222<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">type</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> Props</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
222 = </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">CollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C;</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'blog'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">>;</span></span>
223<span class="line"></span>
224<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Astro</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">props</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
225<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">Content</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">render</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">();</span></span>
226<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
227<span class="line"></span>
228<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">BlogPost</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {...</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">data</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
229<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#808080;--shiki-light:#800000">	&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Content</span><span style="--shiki-dark:#808080;--shiki-light:#800000"> /></span></span>
230<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">BlogPost</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span></code></pre>
231<p>这个模板在 <code>getStaticPaths()</code> 函数中使用 <code>getCollection('blog')</code> 读取 <code>blog</code>
232Collection 下的所有文章,并为每篇文章生成一个 <code>{params: ..., props: ...}</code> 的对象。<code>params</code> 中的参数会用于生成 URL,例如 <code>slug</code> 参数就会替换掉文件路径中的
233<code>[...slug]</code> 这一部分。而 <code>props</code> 的参数会被传递给 Astro 模板,可以用
234<code>const post = Astro.props</code> 读取整个 <code>props</code> 对象。由于 这里 <code>getStaticPaths()</code>
235返回的 <code>props</code> 参数是文章本身,所以 <code>
235Astro.props</code> 的返回值就是这篇文章。</p>
236<p>因为我改了 Collection 的格式,所以模板也要相应修改,主要是把 <code>blog</code> 改成
237<code>article</code>:</p>
238<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
239<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">type</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> CollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">getCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 'astro:content'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
240<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> BlogPost</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../../layouts/BlogPost.astro'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
241<span class="line"></span>
242<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> async</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPaths</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">() {</span></span>
243<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'article'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
244<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">((</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ({</span></span>
245<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    params:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">slug:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">slug</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">language:</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> undefined</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> },</span></span>
246<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    props:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
247<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }));</span></span>
248<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
249<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">type</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> Props</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
249 = </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">CollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C;</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'article'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">>;</span></span>
250<span class="line"></span>
251<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Astro</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">props</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
252<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">Content</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">render</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">();</span></span>
253<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
254<span class="line"></span>
255<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">BlogPost</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {...</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">data</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
256<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#808080;--shiki-light:#800000">  &#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Content</span><span style="--shiki-dark:#808080;--shiki-light:#800000"> /></span></span>
257<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">BlogPost</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
258<span class="line"></span></code></pre>
259<p>(我实际使用的更复杂的模板可以在
260<a href="https://github.com/xddxdd/blog/blob/astro-dev/src/pages/%5B...language%5D/article/%5B...path%5D.lantian/index.astro" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/astro-dev/src/pages/%5B...language%5D/article/%5B...path%5D.lantian/index.astro</a>
261看到)</p>
262<h2 id="多语言">多语言</h2>
263<p>我下一个要实现的功能是多语言。我预先将中文文章放在了 <code>src/content/article/zh</code>,英文文章放在 <code>src/content/article/en</code>。</p>
264<p>这样我就可以根据文章的文件路径判断语言了:</p>
265<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-typescript"><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> class</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
266<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  public</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> readonly</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> title</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span></span>
267<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  public</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> readonly</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> language</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span></span>
268<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  public</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> readonly</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span></span>
269<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  public</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> readonly</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> body</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span></span>
270<span class="line"></span>
271<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  constructor</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">CollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C;</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'article'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">>) {</span></span>
272<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    this</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">collectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
272 = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span></span>
273<span class="line"></span>
274<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> [</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">language</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, ...</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">paths</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">] = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">slug</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">split</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'/'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
275<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">paths</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">join</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'/'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
276<span class="line"></span>
277<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    this</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">title</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">data</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">title</span></span>
278<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    this</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">language</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">language</span></span>
279<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    this</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">path</span></span>
280<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    this</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">body</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">body</span></span>
281<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
282<span class="line"></span>
283<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  public</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> static</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> fromCollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">CollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C;</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'article'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">>): </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
284<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">    return</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> new</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
285<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
286<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
287<span class="line"></span>
288<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> async</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getPosts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(): </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Promise</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">[]> {</span></span>
289<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getCollection</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'article'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">))</span></span>
290<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    .</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fromCollectionEntry</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
291<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    .</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">
291sort</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">((</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">a</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">b</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> b</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">date</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">valueOf</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">() - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">a</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">date</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">valueOf</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">())</span></span>
292<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
293<p>(我实际使用的完整代码可以在
294<a href="https://github.com/xddxdd/blog/blob/astro-dev/src/lib/posts.ts" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/astro-dev/src/lib/posts.ts</a>
295看到)</p>
296<p>这段代码从文章的 <code>post.slug</code> 参数获取文件在 <code>src/content/article</code> 下的路径,然后切出路径第一段的语言代码。</p>
297<p>有了语言代码,我们就可以在模板中根据语言生成网页路径了。我的模板在
298<code>src/pages/[...language]/article/[...path].lantian/index.astro</code> 路径下:</p>
299<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
300<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> PageLayout</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../../../components/PageLayout.astro'</span></span>
301<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> PagePost</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../../../components/PagePost.astro'</span></span>
302<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">getPosts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../../../lib/posts'</span></span>
303<span class="line"></span>
304<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> async</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPaths</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">() {</span></span>
305<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
305 = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getPosts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()</span></span>
306<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">((</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">index</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ({</span></span>
307<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    params:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
308<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 遵循 src/content/article 下的原始路径,path 是我在前面的 Post 类中定义的</span></span>
309<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      path:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
310<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 路径以 [...language] 形式定义并且设置为 undefined 代表省略路径中这一项</span></span>
311<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 我的英文文章在 /en/article 下,而中文文章在 /article 下,没有前缀</span></span>
312<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      language:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">language</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> == </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"zh"</span></span>
313<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        ? </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">undefined</span></span>
314<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        : </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">language</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
315<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    },</span></span>
316<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    props:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
317<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      post:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
318<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    },</span></span>
319<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }))</span></span>
320<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
321<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">type</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> Props</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
322<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Post</span></span>
323<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
324<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> }
324 = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Astro</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">props</span></span>
325<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
326<span class="line"></span>
327<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PageLayout</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> title</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">title</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> language</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">language</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
328<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#808080;--shiki-light:#800000">  &#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PagePost</span></span>
329<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    language</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">language</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span></span>
330<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span></span>
331<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#808080;--shiki-light:#800000">  /></span></span>
332<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PageLayout</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span></code></pre>
333<p>(我实际使用的更复杂的模板可以在
334<a href="https://github.com/xddxdd/blog/blob/astro-dev/src/pages/%5B...language%5D/article/%5B...path%5D.lantian/index.astro" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/astro-dev/src/pages/%5B...language%5D/article/%5B...path%5D.lantian/index.astro</a>
335看到)</p>
336<p>由于 Hexo 的 URL 也是根据 Markdown 文件的路径生成的,因此所有文章的 URL 都不会变。</p>
337<h2 id="文章摘要">文章摘要</h2>
338<p>有了文章内容,接下来就要在文章列表中显示文章摘要。Astro 的默认博客模板没有提供这个功能,只会显示文章标题和图片。但是,我在实现摘要功能时遇到了一点困难。</p>
339<p>我的第一反应是,Astro 会提供一个将组件渲染成 HTML 的接口,类似于:</p>
340<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-typescript"><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> component</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
340 = </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">PostContent</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
341<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> html</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">component</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">renderToHTML</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()</span></span>
342<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> excerpt</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">createExcerpt</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">html</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span></code></pre>
343<p>但是查找一圈后,发现 Astro.js 还没有实现这个功能:<a href="https://github.com/withastro/roadmap/issues/533" rel="noopener noreferrer" target="_blank">https://github.com/withastro/roadmap/issues/533</a></p>
344<p>不过 Astro.js 只是无法单独渲染某个组件,在渲染整个页面的过程中,还是可以用 Slot
345功能获取组件的 HTML 的。</p>
346<p>Astro.js 的 Slot 功能类似于 React 的 <code>props.children</code>,用于传递子组件。例如,我有一个组件 <code>PostExcerpt.astro</code>:</p>
347<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
348<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">createExcerpt</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../lib/utils'</span></span>
349<span class="line"></span>
350<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> html</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> Astro</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">slots</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">render</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'default'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
351<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> excerpt</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">createExcerpt</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">html</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
352<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
353<span class="line"></span>
354<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">excerpt</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
355<span class="line"></span></code></pre>
356<p>(我使用的完整组件可以在
357<a href="https://github.com/xddxdd/blog/blob/master/src/components/fragments/PostExcerpt.astro" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/master/src/components/fragments/PostExcerpt.astro</a>
358看到)</p>
359<p>如果我们向这个组件传递一个子组件:</p>
360<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
361<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
362<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PostExcerpt</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
363<span class="line" style="--line-indent:1ch"><span style="--shiki-dark:#808080;--shiki-light:#800000"> &#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">A really really long post content...</span><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
364<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PostExcerpt</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span></code></pre>
365<p><code>PostExcerpt.astro</code> 组件就可以通过 <code>await Astro.slots.render('default')</code> 获取
366<code>&#x3C;p>A really really long post content...&#x3C;/p></code> 这个子组件的 HTML 渲染结果了。</p>
367<p>接下来,它就可以进一步调用 <code>createExcerpt</code> 函数,从文章的完整 HTML 中提取摘要。</p>
368<p>(更详细的 Slot 功能介绍请参阅官方文档:<a href="https://docs.astro.build/zh-cn/basics/astro-components/#slots" rel="noopener noreferrer" target="_blank">https://docs.astro.build/zh-cn/basics/astro-components/#slots</a>)</p>
369<h2 id="分页">分页</h2>
370<p>下一步是实现主页文章列表的分页。Astro 的默认博客模板会将所有文章显示在同一页上,如果博客中文章较多,网页会非常长,难以导航。因此我先实现了一个分页函数,将文章列表拆分成 10 篇文章的小段:</p>
371<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-typescript"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">// 每页文章数</span></span>
372<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> POSTS_PER_PAGE</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">10</span></span>
373<span class="line"></span>
374<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">// 我的模板使用的分页参数</span></span>
375<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> type</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> PaginationProps</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
376<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  numPages</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">number</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // 总页数</span></span>
377<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  currentPage</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">number</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"> // 当前页码</span></span>
378<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  basePath</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // 基础路径,最终路径是 ${basePath}/page/${currentPage}</span></span>
379<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
380<span class="line"></span>
381<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> type</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> PaginatedProps</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
382<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  pagination</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PaginationProps</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"> // 当前分页的参数</span></span>
383<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">[]               </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">
383// 当前分页的文章列表</span></span>
384<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
385<span class="line"></span>
386<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPathsForPaginate</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span></span>
387<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">[],</span></span>
388<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  basePath</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
389<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  additionalParams</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">?: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Record</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">>,</span></span>
390<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  additionalProps</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">?: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">Record</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">string</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">any</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">></span></span>
391<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) {</span></span>
392<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 总页数</span></span>
393<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> numPages</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">ceil</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">length</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> / </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">POSTS_PER_PAGE</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
394<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> [...</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">Array</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">numPages</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">).</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">keys</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()].</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> =></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ({</span></span>
395<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    params:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
396<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 多语言支持省略</span></span>
397<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      language:</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> undefined</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
398<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 在路径中添加 page/2,page/3 这样的页码</span></span>
399<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 如果是第一页,就不用加 page/1 了</span></span>
400<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      page_prefix:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> == </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ? </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">undefined</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
400 : </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">`page/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> +</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">`</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
401<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      ...</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">additionalParams</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
402<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    },</span></span>
403<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    props:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PaginatedProps</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">>{</span></span>
404<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      pagination:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C;</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PaginationProps</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">>{</span></span>
405<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        numPages:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> numPages</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
406<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        currentPage:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
407<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        basePath:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> basePath</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
408<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      },</span></span>
409<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      posts:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> postsForLanguage</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">slice</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span></span>
410<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">POSTS_PER_PAGE</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
411<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) * </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">POSTS_PER_PAGE</span></span>
412<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      ),=</span></span>
413<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      ...</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">additionalProps</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
414<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    },</span></span>
415<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }))</span></span>
416<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
417<p>(我实际使用的完整代码可以在
418<a href="https://github.com/xddxdd/blog/blob/astro-dev/src/lib/posts.ts" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/astro-dev/src/lib/posts.ts</a>
419看到)</p>
420<p>然后从页面模板中调用这个分页函数。我的主页文章列表模板位于
421<code>src/pages/[...language]/[...page_prefix]/index.astro</code>:</p>
422<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
423<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">getPosts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">getStaticPathsForPaginate</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../../lib/posts'</span></span>
424<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> type</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">PaginatedProps</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '../../../lib/posts'</span></span>
425<span class="line"></span>
426<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> async</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPaths</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">() {</span></span>
427<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getPosts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()</span></span>
428<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPathsForPaginate</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">''</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
429<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 第一页时,函数返回 params 中的 page_prefix 为 undefined,此时路径中没有页码</span></span>
430<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 第二页开始,page_prefix会插入路径中,形成 /page/2 的路径</span></span>
431<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
432<span class="line"></span>
433<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">type</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> Props</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">PaginatedProps</span></span>
434<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> }
434 = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Astro</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">props</span></span>
435<span class="line"></span>
436<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">// 其余逻辑省略</span></span></code></pre>
437<p>这样我们就生成了 <code>/</code> 这个第一页的路径,以及 <code>/page/2</code>,<code>/page/3</code> 等其余页码的路径。</p>
438<h2 id="匹配-hexo-的-url">匹配 Hexo 的 URL</h2>
439<p>下一步是实现 Hexo 自动生成的其它页面,例如分类页面
440<code>/category/[分类名]</code>,<code>/category/[分类名]/page/[页码]</code>,以及标签页面
441<code>/tag/[标签名]</code>,<code>/tag/[标签名]/page/[页码]</code>。</p>
442<p>这些页面除了要根据分类过滤文章之外,其余实现和主页文章列表并无区别。以分类页面为例,我们创建
443<code>src/pages/[...language]/category/[category]/[...page_prefix]/index.astro</code> 这个模板文件:</p>
444<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
445<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> async</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPaths</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">() {</span></span>
446<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> categories</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
447<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ...</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">new</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> Set</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Object</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">entries</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">CATEGORY_MAP</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">).</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(([</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">v</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]) </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> v</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)),</span></span>
448<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ]</span></span>
449<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">await</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getPosts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()</span></span>
450<span class="line"></span>
451<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> categories</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">flatMap</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">category</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> =></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
452<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    const</span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1"> postsForCategory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
452 = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">filter</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span></span>
453<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      post</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> =></span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> post</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">category</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> == </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">category</span></span>
454<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    )</span></span>
455<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">    return</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> getStaticPathsForPaginate</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span></span>
456<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 当前分类的所有文章</span></span>
457<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      postsForCategory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
458<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // 分类的基础路径,用于提供给分页选择组件</span></span>
459<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      `/category/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">categoryInUrl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">`</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
460<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // additionalParams 参数,将分类加到 URL 中</span></span>
461<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">category:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> category</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> },</span></span>
462<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      // additionalProps 参数,将分类名加到 Astro.props.中</span></span>
463<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      { </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">category:</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> category</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> }</span></span>
464<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    )</span></span>
465<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  })</span></span>
466<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
467<span class="line"></span>
468<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> { </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">posts</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#4FC1FF;--shiki-light:#0070C1">category</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> } = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">Astro</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">props</span></span>
469<span class="line"></span>
470<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">// 其余逻辑省略</span></span></code></pre>
471<p>这样,我们可以将所有该分类的文章显示在 <code>/category/[分类名]</code> 或者
472<code>/category/[分类名]/page/[页码]</code> 这个路径上了。</p>
473<h2 id="island岛屿功能">Island(岛屿)功能</h2>
474<p>Astro.js 的主打功能 Island(岛屿),可以以 <code>.astro</code>
474 文件为单位,在页面中生成一些互相隔离的区域。这些隔离功能包括:</p>
475<ul>
476<li>CSS 隔离。假设我在 <code>src/components/a.astro</code> 中添加以下的 HTML/CSS 代码:</li>
477</ul>
478<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-html"><span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> class</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">"my-class"</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Hello World</span><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
479<span class="line"></span>
480<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">style</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
481<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D7BA7D;--shiki-light:#800000">  .my-class</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF"> {</span></span>
482<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    font-size</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">: </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">100px</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">;</span></span>
483<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">  }</span></span>
484<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">style</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span></code></pre>
485<p>Astro.js 会对上述 HTML 和 CSS 进行处理,并打上一个 <code>data-astro-cid</code> 开头的标签:</p>
486<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-html"><span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> class</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">"my-class"</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> data-astro-cid-123456</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Hello World</span><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">p</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
487<span class="line"></span>
488<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">style</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
489<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D7BA7D;--shiki-light:#800000">  .my-class</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">[</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">data-astro-cid-123456</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">] {</span></span>
490<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    font-size</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">: </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">100px</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">;</span></span>
491<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">  }</span></span>
492<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">style</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span></code></pre>
493<p>这样这个 CSS 就只对上面这个 <code>p</code> 标签生效了。</p>
494<p>这个 <code>data-astro-cid</code> 标签对于所有来自 <code>
494src/components/a.astro</code> 的 HTML/CSS 都是相同的,因此上面的 CSS 会应用到所有的来自 <code>a.astro</code> 的 HTML。但是假设我有另一个组件 <code>src/components/b.astro</code>,它会有一个不同的标签,例如
495<code>data-astro-cid-654321</code>,这样 <code>a.astro</code> 的 CSS 就不会对 <code>b.astro</code> 生效了。</p>
496<p>这在写 CSS 时是一个非常好用的功能,调整网页一个部分的 CSS 时不用担心影响到其余部分的效果了。</p>
497<ul>
498<li>Javascript 组件隔离</li>
499</ul>
500<p>如果我的 Astro 模板中有 Javascript 代码:</p>
501<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
502<span class="line"></span>
503<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
504<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">div</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> id</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"test"</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Hello world</span><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">div</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
505<span class="line"></span>
506<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">script</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
507<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">document</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">getElementById</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"test"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">).</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">innerHTML</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "Hi there"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">;</span></span>
508<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">script</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span></code></pre>
509<p>Astro.js 会自动为这个组件生成一个岛屿,并在网页加载完成后,再执行这个岛屿的
510Javascript 代码。这个岛屿与网页的其余部分完全独立,因此不会拖慢其余部分的加载速度。</p>
511<p>除了提高加载速度,这个特性还简化了 <code>window.onload</code> 的使用。在传统前端上,如果我们想在网页加载时再执行代码,可以这样做:</p>
512<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-javascript"><span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">onload</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = () </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
513<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  console</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'Hello World'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
514<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
515<p>但如果我们有多个函数需要执行呢?如果简单地覆盖 <code>window.onload</code> 会导致前一个函数不被执行:</p>
516<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-javascript"><span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">onload</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = () </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
517<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  console</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'这个函数不会被执行'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
518<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
519<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">onload</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = () </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
520<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  console</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'Hello World'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
521<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
522<p>因此我们需要一些更复杂的处理:</p>
523<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-javascript"><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">function</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> addLoadEvent</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">o</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) {</span></span>
524<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 记录先前的 window.onload</span></span>
525<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  var</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> n</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">
525 = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">onload</span></span>
526<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">  'function'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> != </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">typeof</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">onload</span></span>
527<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ? (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">onload</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">o</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
528<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    : (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">onload</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">function</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> () {</span></span>
529<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        // 如果之前设置了 window.onload,就同时执行先前设置的函数</span></span>
530<span class="line" style="--line-indent:8ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        n</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x26;&#x26; </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">n</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(), </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">o</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()</span></span>
531<span class="line" style="--line-indent:6ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      })</span></span>
532<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
533<span class="line"></span>
534<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">addLoadEvent</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(() </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
535<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  console</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'这个函数现在会被执行'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
536<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">})</span></span>
537<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">addLoadEvent</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(() </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">=></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
538<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  console</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">'Hello World'</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
539<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">})</span></span></code></pre>
540<p>可行,但有点麻烦。不过有了 Astro.js,上述这些流程都会被自动化:</p>
541<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-astro"><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
542<span class="line"></span>
543<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">---</span></span>
544<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">div</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000"> id</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">
544"test"</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Hello world</span><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">div</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
545<span class="line"></span>
546<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">script</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span>
547<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">// 这段代码会在整个网页加载完成后再执行,无需其余设置</span></span>
548<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">document</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">getElementById</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"test"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">).</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">innerHTML</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "Hi there"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000FF">;</span></span>
549<span class="line"><span style="--shiki-dark:#808080;--shiki-light:#800000">&#x3C;/</span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">script</span><span style="--shiki-dark:#808080;--shiki-light:#800000">></span></span></code></pre>
550<p>下面是我使用岛屿加载 Javascript 的两个例子:</p>
551<ul>
552<li><code>WalineComment.astro</code>,用于加载 Waline 评论系统的评论框:<a href="https://github.com/xddxdd/blog/blob/astro-dev/src/components/fragments/WalineComment.astro" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/astro-dev/src/components/fragments/WalineComment.astro</a></li>
553<li><code>WalineRecentComments.astro</code>,用于显示 Waline 评论系统的近期评论:<a href="https://github.com/xddxdd/blog/blob/astro-dev/src/components/fragments/WalineRecentComments.astro" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/blog/blob/astro-dev/src/components/fragments/WalineRecentComments.astro</a></li>
554</ul>
555<h2 id="后处理插件例css-内联">后处理插件(例:CSS 内联)</h2>
556<p>Astro.js 主页上有一个插件(Integrations)列表,提供了很多可以提供额外功能的插件:<a href="https://astro.build/integrations/" rel="noopener noreferrer" target="_blank">https://astro.build/integrations/</a></p>
557<p>这里我以一个插件 <a href="https://github.com/Playform/Inline" rel="noopener noreferrer" target="_blank">Inline</a> 为例,它使用
558Google 的 <a href="https://github.com/GoogleChromeLabs/critters" rel="noopener noreferrer" target="_blank">critters</a> 项目,自动将当前页面用到的 CSS 内联到网页中,以提高网页加载速度。</p>
559<p>首先从 NPM 安装 Inline 插件:</p>
560<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-bash"><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">npm</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --save</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> @playform/inline</span></span></code></pre>
561<p>然后修改 <code>astro.config.ts</code>,将插件添加到 <code>defineConfig</code> 的 <code>integrations</code> 配置项中:</p>
562<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code class="language-typescript"><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">
562import</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> inline</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> from</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> '@playform/inline'</span></span>
563<span class="line"></span>
564<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">export</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> default</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> defineConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">({</span></span>
565<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  integrations:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> [</span></span>
566<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // ...</span></span>
567<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    critters</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">({}),</span></span>
568<span class="line" style="--line-indent:4ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // ...</span></span>
569<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ],</span></span>
570<span class="line" style="--line-indent:2ch"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // ...</span></span>
571<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">})</span></span></code></pre>
572<p>之后,你用 <code>astro build</code> 命令生成的网页都会自动被优化。</p>
573<p>类似的,Astro 还提供了很多常用的插件:</p>
574<ul>
575<li>MDX 支持:<a href="https://docs.astro.build/en/guides/integrations-guide/mdx/" rel="noopener noreferrer" target="_blank">https://docs.astro.build/en/guides/integrations-guide/mdx/</a></li>
576<li>生成
577Sitemap:<a href="https://docs.astro.build/en/guides/integrations-guide/sitemap/" rel="noopener noreferrer" target="_blank">https://docs.astro.build/en/guides/integrations-guide/sitemap/</a></li>
578<li>React 支持:<a href="https://docs.astro.build/en/guides/integrations-guide/react/" rel="noopener noreferrer" target="_blank">https://docs.astro.build/en/guides/integrations-guide/react/</a></li>
579</ul>
580<p>你可以用类似的方法,非常简单地加载这些插件。</p>
581<h1 id="总结">总结</h1>
582<p>我已经在 2024 年 3 月 18 日将整个博客系统迁移到 Astro.js。整个迁移过程中,所有网页 URL 均保持不变,我的网页主题模板也原样迁移成功。对于访客来说,整个博客的内容、样式和功能应该没有任何变化。但是对于我来说,更好的开发体验有利于我方便地实现更复杂的功能,测试最新的前端工具,以及进行进一步的性能优化。</p>
583<p>整个过程的所有 commit 可以在
584<a href="https://github.com/xddxdd/blog/tree/astro-dev" rel="noopener noreferrer" target="_blank">GitHub 上的 astro-dev 分支</a>看到。</p></div><div class="clearfix" id="post-navi" data-astro-cid-wlifdhof=""><div class="float-end text-end" data-astro-cid-wlifdhof=""><a href="/article/modify-computer/nixos-low-ram-vps.lantian/" title="NixOS 系列(五):制作小内存 VPS 的 DD 磁盘镜像" data-astro-cid-wlifdhof="">NixOS 系列(五):制作小内存 VPS 的 DD 磁盘镜像</a><br data-astro-cid-wlifdhof="">下一篇文章 »</div><div class="float-start text-start position-absolute" data-astro-cid-wlifdhof="">« 上一篇文章<br data-astro-cid-wlifdhof=""><a href="/article/modify-computer/build-custom-android-kernel-with-nix.lantian/" title="用 Nix 编译自定义 Android 内核" data-astro-cid-wlifdhof="">用 Nix 编译自定义 Android 内核</a></div></div><div id="waline" data-waline-language="zh-CN" data-waline-path="/article/modify-website/migrate-blog-to-astro-js.lantian"></div>
584<script type="module" src="/assets/WalineComment.astro_astro_type_script_index_0_lang.SJyMLKEa.js"></script>
584</div></div></article></div><aside class="d-none d-xl-block" data-astro-cid-pgt5l3fs=""><section class="widget"><h3>最新文章</h3><ul class="list-unstyled"><li>05-13 <a href="/article/fun/ai-agent-bankrupted-their-operator-scan-dn42lantian.lantian/">AI Agent 试图扫描 DN42 时把主人搞破产了</a></li><li>04-13 <a href="/article/modify-computer/modify-filezilla-workaround-bambu-3d-printer-ftp-issue.lantian/">魔改 FileZilla 解决拓竹 3D 打印机的 FTP 问题</a></li><li>12-07 <a href="/article/modify-website/dn42-flapalerted-reduce-flapping.lantian/">在 DN42 中使用 FlapAlerted 抑制 Flapping</a></li><li>07-20 <a href="/article/modify-computer/legal-lte-network-at-home-with-open5gs.lantian/">用 Open5GS 搭建合法的 LTE 网络</a></li><li>06-27 <a href="/article/modify-computer/sidestore-without-stosvpn-across-lan.lantian/">在内网中免 StosVPN 使用 SideStore</a></li><li>05-19 <a href="/article/modify-computer/nix-logarithmetic-math-library-from-zero.lantian/">从零开始实现 Nix 对数函数库</a></li><li>04-06 <a href="/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/">$100,DIY 搭建合法的 LTE 网络</a></li><li>02-10 <a href="/article/modify-computer/build-custom-android-kernel-with-nix.lantian/">用 Nix 编译自定义 Android 内核</a></li><li>04-20 <a href="/article/modify-website/migrate-blog-to-astro-js.lantian/">使用 Astro.js 重构我的博客</a></li><li>12-16 <a href="/article/modify-computer/nixos-low-ram-vps.lantian/">NixOS 系列(五):制作小内存 VPS 的 DD 磁盘镜像</a></li><li>09-20 <a href="/article/modify-computer/nix-trigonometric-math-library-from-zero.lantian/">从零开始实现 Nix 三角函数库</a></li><li>05-29 <a href="/article/random-notes/pipewire-sigkill-fix.lantian/">解决 Pipewire 被 SIGKILL 的问题</a></li><li>05-12 <a href="/article/modify-website/how-to-kill-the-dn42-network.lantian/">如何引爆 DN42 网络(2023-05-12 更新)</a></li><li>05-08 <a href="/article/modify-computer/laptop-muxed-nvidia-passthrough.lantian/">Optimus MUXed 笔记本上的 NVIDIA 虚拟机显卡直通(2023-05 更新)</a></li><li>04-17 <a href="/article/random-notes/fix-china-telecom-roaming-no-4g-on-aosp-rom.lantian/">修改 APN 解决 AOSP ROM 上中国电信无法 4G 漫游问题</a></li></ul></section><section class="widget"><h3>最新评论</h3><ul class="list-unstyled" id="recent-comments"><li><a href="#">Loading...</a></li></ul></section>
584<script type="module" src="/assets/WalineRecentComments.astro_astro_type_script_index_0_lang.IH-4y8rJ.js"></script>
584<section class="widget"><h3>其它功能</h3><ul class="list-unstyled"><li><i class="fas fa-fw fa-rss"></i> <a href="/rss2.zh.xml" target="_blank">RSS 订阅</a> | <a href="/feed.zh.xml" target="_blank">Atom</a> | <a href="/feed.zh.json" target="_blank">JSON</a></li><li><i class="fas fa-fw fa-rss"></i> <a href="/rss2.xml" target="_blank">RSS 订阅</a> | <a href="/feed.xml" target="_blank">Atom</a> | <a href="/feed.json" target="_blank">JSON</a>(全语言)</li><li><i class="fas fa-fw fa-globe"></i> <a href="https://stats.uptimerobot.com/NLYPbrSal6" target="_blank">服务器状态</a></li><li><i class="fas fa-fw fa-sitemap"></i> <a href="https://lg.lantian.pub" target="_blank">DN42 节点状态</a></li><li><i class="fas fa-fw fa-link"></i> <a href="gopher://lantian.pub" target="_blank">使用 Gopher 协议访问</a></li></ul></section><section class="widget"><h3>友情链接</h3><ul class="list-unstyled"><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://0x7f.cc">0x7f Blog 🐑</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://alanyhq.com">Alanyhq</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://blog.cas7.moe">Blog of Moecast</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://jerryxiao.cc">JerryXiao</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://snli.org">Shucheng Li</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://sangsir.com">SangSir | 艺术界的一朵奇葩</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://yuetau.net">YuetAu&#39;s Spot</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="https://blog.baoshuo.ren/?utm_source=friends">宝硕博客</a></li><li><i class="fas fa-fw fa-link"></i> <a target="_blank" href="http://foreverblog.cn">十年之约</a></li></ul></section></aside></div><footer class="clearfix" data-astro-cid-3jm3l2uw=""><div id="copyright" class="float-end text-end" data-astro-cid-3jm3l2uw=""><i class="fas fa-copyright" data-astro-cid-3jm3l2uw=""></i>2012-2026Lan Tian @ Blog<br data-astro-cid-3jm3l2uw=""><small data-astro-cid-3jm3l2uw="">基于 Astro v7.2.2 构建 @ 2026-09-24 05:42:06</small></div>
584<script type="module" src="/assets/PlausibleAnalytics.astro_astro_type_script_index_0_lang.CCqn5Tkb.js"></script>
584</footer></div></body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.