chore: 升级 Astro 5.16.5 → 7.3.3
依赖
- astro 5 → 7.3.3,@astrojs/{mdx,react,rss,sitemap,check}、astro-expressive-code
与 @swup/astro 同步升到最新
- 新增 @astrojs/markdown-remark(Astro 7 默认改用 Sätteri 处理器,
本项目依赖大量 remark/rehype 插件,故显式固定回 unified 管线)
内容集合迁移到 Content Layer API(Astro 6 已移除旧 API)
- src/content/config.ts → src/content.config.ts,三个集合改用 glob() loader
- z 改从 astro/zod 导入
- 所有 entry.slug → entry.id;entry.render() → render(entry)
Astro 7 兼容
- 修复 bookshelf-content.astro 里多行 {/* */} 注释导致的 Rust 编译器
"Unterminated regular expression"
- 删除该文件里一处多余的 </div>(旧 Go 编译器会静默纠正,新编译器不会)
- global.css: @import "tailwindcss" → "tailwindcss/index.css"
(Vite 8 不再解析裸包名的 style 条件)
- astro.config.mjs 显式设置 compressHTML: true,保持 v6 的空白处理行为
验证:构建 41 页通过;与线上(Astro 5 构建)对比 39 个 URL 无一丢失,
仅新增新文章 DIY-computer 的 2 个 URL;逐页渲染文本与线上一致;
pnpm build 与 dev server 均正常。
This commit is contained in:
+50
-46
@@ -21,6 +21,7 @@ import { remarkExcerpt } from "./src/plugins/remark-excerpt.js"
|
||||
import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs"
|
||||
import { pluginLanguageBadge } from "./src/plugins/expressive-code/language-badge"
|
||||
import { pluginCustomCopyButton } from "./src/plugins/expressive-code/custom-copy-button"
|
||||
import { unified } from "@astrojs/markdown-remark"
|
||||
import { site } from "./src/data/site"
|
||||
|
||||
const createAdmonitionComponent = (type) => (properties = {}, children = []) => {
|
||||
@@ -85,57 +86,60 @@ export default defineConfig({
|
||||
sitemap(),
|
||||
mdx(),
|
||||
],
|
||||
compressHTML: true,
|
||||
markdown: {
|
||||
remarkPlugins: [
|
||||
remarkMath,
|
||||
remarkReadingTime,
|
||||
remarkExcerpt,
|
||||
remarkShiftHeadings,
|
||||
remarkGithubAdmonitionsToDirectives,
|
||||
remarkDirective,
|
||||
remarkSectionize,
|
||||
parseDirectiveNode,
|
||||
],
|
||||
rehypePlugins: [
|
||||
rehypeKatex,
|
||||
rehypeSlug,
|
||||
[
|
||||
rehypeComponents,
|
||||
{
|
||||
components: {
|
||||
github: GithubCardComponent,
|
||||
note: createAdmonitionComponent("note"),
|
||||
tip: createAdmonitionComponent("tip"),
|
||||
important: createAdmonitionComponent("important"),
|
||||
caution: createAdmonitionComponent("caution"),
|
||||
warning: createAdmonitionComponent("warning"),
|
||||
},
|
||||
},
|
||||
processor: unified({
|
||||
remarkPlugins: [
|
||||
remarkMath,
|
||||
remarkReadingTime,
|
||||
remarkExcerpt,
|
||||
remarkShiftHeadings,
|
||||
remarkGithubAdmonitionsToDirectives,
|
||||
remarkDirective,
|
||||
remarkSectionize,
|
||||
parseDirectiveNode,
|
||||
],
|
||||
[
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
behavior: "append",
|
||||
properties: {
|
||||
className: ["anchor"],
|
||||
},
|
||||
content: {
|
||||
type: "element",
|
||||
tagName: "span",
|
||||
properties: {
|
||||
className: ["anchor-icon"],
|
||||
"data-pagefind-ignore": true,
|
||||
rehypePlugins: [
|
||||
rehypeKatex,
|
||||
rehypeSlug,
|
||||
[
|
||||
rehypeComponents,
|
||||
{
|
||||
components: {
|
||||
github: GithubCardComponent,
|
||||
note: createAdmonitionComponent("note"),
|
||||
tip: createAdmonitionComponent("tip"),
|
||||
important: createAdmonitionComponent("important"),
|
||||
caution: createAdmonitionComponent("caution"),
|
||||
warning: createAdmonitionComponent("warning"),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
rehypeAutolinkHeadings,
|
||||
{
|
||||
behavior: "append",
|
||||
properties: {
|
||||
className: ["anchor"],
|
||||
},
|
||||
content: {
|
||||
type: "element",
|
||||
tagName: "span",
|
||||
properties: {
|
||||
className: ["anchor-icon"],
|
||||
"data-pagefind-ignore": true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
}),
|
||||
},
|
||||
vite: {
|
||||
server: {
|
||||
|
||||
+11
-10
@@ -14,14 +14,15 @@
|
||||
"reading": "node scripts/fetch-reading.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^5.0.6",
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@astrojs/rss": "^4.0.12",
|
||||
"@astrojs/sitemap": "^3.5.1",
|
||||
"@astrojs/markdown-remark": "^7.3.1",
|
||||
"@astrojs/mdx": "^8.0.1",
|
||||
"@astrojs/react": "^6.0.6",
|
||||
"@astrojs/rss": "^4.0.19",
|
||||
"@astrojs/sitemap": "^3.7.4",
|
||||
"@aws-sdk/client-s3": "^3.1037.0",
|
||||
"@expressive-code/core": "^0.41.5",
|
||||
"@expressive-code/plugin-collapsible-sections": "^0.41.5",
|
||||
"@expressive-code/plugin-line-numbers": "^0.41.5",
|
||||
"@expressive-code/core": "^0.44.2",
|
||||
"@expressive-code/plugin-collapsible-sections": "^0.44.2",
|
||||
"@expressive-code/plugin-line-numbers": "^0.44.2",
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@radix-ui/react-accordion": "1.2.2",
|
||||
"@radix-ui/react-alert-dialog": "1.1.4",
|
||||
@@ -50,9 +51,9 @@
|
||||
"@radix-ui/react-toggle": "1.1.1",
|
||||
"@radix-ui/react-toggle-group": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "1.1.6",
|
||||
"@swup/astro": "^1.7.0",
|
||||
"@swup/astro": "^1.8.0",
|
||||
"@tabler/icons-react": "^3.40.0",
|
||||
"astro-expressive-code": "^0.41.5",
|
||||
"astro-expressive-code": "^0.44.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -102,7 +103,7 @@
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"astro": "^5.16.5",
|
||||
"astro": "^7.3.3",
|
||||
"postcss": "^8.5",
|
||||
"tailwindcss": "^4.1.9",
|
||||
"tw-animate-css": "1.3.3",
|
||||
|
||||
Generated
+3733
-3404
File diff suppressed because it is too large
Load Diff
@@ -5,3 +5,7 @@ allowBuilds:
|
||||
esbuild: true
|
||||
sharp: true
|
||||
swup: true
|
||||
|
||||
minimumReleaseAgeExclude:
|
||||
- '@astrojs/react@6.0.6'
|
||||
- astro@7.3.3
|
||||
|
||||
@@ -163,9 +163,7 @@ const recommendBadge = (status?: string) => {
|
||||
{/* ========================= Main ========================= */}
|
||||
<section class="flex-1 min-w-0" data-bookshelf-main>
|
||||
|
||||
{/* ========================= 最近在读(微信读书) =========================
|
||||
数据在构建期由 scripts/fetch-reading.mjs 抓到 src/data/reading.json,
|
||||
这里只负责渲染,页面本身不发任何请求。 */}
|
||||
{/* ========================= 最近在读(微信读书) ========================= 数据在构建期由 scripts/fetch-reading.mjs 抓到 src/data/reading.json, 这里只负责渲染,页面本身不发任何请求。 */}
|
||||
{isReadingView && (
|
||||
<div id="reading" class="mb-16 scroll-mt-24">
|
||||
<div class="flex flex-wrap items-end justify-between gap-x-6 gap-y-2 mb-6">
|
||||
@@ -205,9 +203,7 @@ const recommendBadge = (status?: string) => {
|
||||
<div class="text-sm italic font-serif text-muted-foreground">博学而笃志,切问而近思</div>
|
||||
</div>
|
||||
|
||||
{/* ---------- Stats + Search + filters ----------
|
||||
只有存在手工书单时才显示。books.json 为空时这三行都是「0 本已读 /
|
||||
0 本推荐 / 0 个领域」加一排没东西可筛的按钮,纯占地方。 */}
|
||||
{/* ---------- Stats + Search + filters ---------- 只有存在手工书单时才显示。books.json 为空时这三行都是「0 本已读 / 0 本推荐 / 0 个领域」加一排没东西可筛的按钮,纯占地方。 */}
|
||||
{hasManualBooks && (
|
||||
<>
|
||||
<div class="flex flex-wrap gap-x-7 gap-y-2 text-sm text-muted-foreground mb-7">
|
||||
@@ -340,9 +336,7 @@ const recommendBadge = (status?: string) => {
|
||||
</section>
|
||||
))}
|
||||
|
||||
{/* 微信读书里已读完的书,并入「书籍」区。
|
||||
卡片用和「最近在读」同一个 ReadingCard 组件,只是底部换成「开始/读完」两行、没有进度条。
|
||||
data-weread-card 让搜索框和领域/评价筛选同样作用于这一组。 */}
|
||||
{/* 微信读书里已读完的书,并入「书籍」区。 卡片用和「最近在读」同一个 ReadingCard 组件,只是底部换成「开始/读完」两行、没有进度条。 data-weread-card 让搜索框和领域/评价筛选同样作用于这一组。 */}
|
||||
{finishedReading.length > 0 && (
|
||||
<section data-books-section={WEREAD_CATEGORY}>
|
||||
<div class="flex items-center gap-3 mb-5">
|
||||
@@ -363,7 +357,6 @@ const recommendBadge = (status?: string) => {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { defineCollection, z } from "astro:content"
|
||||
import { defineCollection } from "astro:content"
|
||||
import { glob } from "astro/loaders"
|
||||
import { z } from "astro/zod"
|
||||
|
||||
const posts = defineCollection({
|
||||
type: "content",
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
published: z.date(),
|
||||
@@ -26,7 +28,7 @@ const posts = defineCollection({
|
||||
})
|
||||
|
||||
const thoughts = defineCollection({
|
||||
type: "content",
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/thoughts" }),
|
||||
schema: z.object({
|
||||
title: z.string().optional(),
|
||||
published: z.date(),
|
||||
@@ -43,9 +45,9 @@ const thoughts = defineCollection({
|
||||
})
|
||||
|
||||
const studies = defineCollection({
|
||||
type: "content",
|
||||
// 注意:frontmatter 里的 `slug` 字段被 Astro 当 reserved key 处理(用作 URL slug),
|
||||
// 不会出现在 schema 校验的 data 里。所以这里不声明 slug,直接读 entry.slug。
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/studies" }),
|
||||
// 注意:`id` 由 glob loader 从文件路径生成(Content Layer API),
|
||||
// 所以这里不声明 slug,直接读 entry.id。
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
status: z.enum(["在读", "沉淀中", "暂搁", "已结"]),
|
||||
+3
-3
@@ -41,11 +41,11 @@ export async function getSortedPosts(ignorePinned = false): Promise<PostEntry[]>
|
||||
export async function getPostsWithNav(): Promise<PostEntry[]> {
|
||||
const sorted = await getSortedPosts()
|
||||
for (let i = 1; i < sorted.length; i += 1) {
|
||||
sorted[i].data.nextSlug = sorted[i - 1].slug
|
||||
sorted[i].data.nextSlug = sorted[i - 1].id
|
||||
sorted[i].data.nextTitle = sorted[i - 1].data.title
|
||||
}
|
||||
for (let i = 0; i < sorted.length - 1; i += 1) {
|
||||
sorted[i].data.prevSlug = sorted[i + 1].slug
|
||||
sorted[i].data.prevSlug = sorted[i + 1].id
|
||||
sorted[i].data.prevTitle = sorted[i + 1].data.title
|
||||
}
|
||||
return sorted
|
||||
@@ -63,7 +63,7 @@ export const toPostMeta = (post: PostEntry) => {
|
||||
const category = (post.data.category ?? "").toString().trim() || "未分类"
|
||||
|
||||
return {
|
||||
slug: post.slug,
|
||||
slug: post.id,
|
||||
title: post.data.title,
|
||||
excerpt,
|
||||
category,
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function getSiteStats(latestCount = 4): Promise<SiteStats> {
|
||||
.slice(0, latestCount)
|
||||
.map((post) => ({
|
||||
title: post.data.title,
|
||||
href: `/posts/${post.slug}/`,
|
||||
href: `/posts/${post.id}/`,
|
||||
date: new Date(post.data.published).toISOString().slice(0, 10),
|
||||
}))
|
||||
|
||||
|
||||
+2
-2
@@ -307,8 +307,8 @@ function shape(entry: StudyEntry, idx: number): StudyData {
|
||||
const no = String(idx + 1).padStart(2, "0")
|
||||
|
||||
return {
|
||||
id: entry.slug,
|
||||
slug: entry.slug,
|
||||
id: entry.id,
|
||||
slug: entry.id,
|
||||
no,
|
||||
title: entry.data.title,
|
||||
subtitle: entry.data.subtitle,
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ export const toThoughtMeta = (thought: ThoughtEntry) => {
|
||||
const content = md.render(body)
|
||||
|
||||
return {
|
||||
slug: thought.slug,
|
||||
slug: thought.id,
|
||||
title: thought.data.title,
|
||||
content,
|
||||
date: formatDate(thought.data.published),
|
||||
@@ -75,7 +75,7 @@ export const toThoughtPreview = (thought: ThoughtEntry, maxLength = 96) => {
|
||||
const text = stripHtml(content)
|
||||
|
||||
return {
|
||||
slug: thought.slug,
|
||||
slug: thought.id,
|
||||
title: thought.data.title,
|
||||
date: formatDate(thought.data.published),
|
||||
excerpt: truncateText(text, maxLength),
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getSortedPosts } from "@/lib/posts"
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getSortedPosts(true)
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.slug },
|
||||
params: { slug: post.id },
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import ArticleContent from "@/components/ArticleContent.astro"
|
||||
import SiteHeader from "@/components/site-header.astro"
|
||||
import Footer from "@/components/footer.astro"
|
||||
import { getPostsWithNav, type PostEntry } from "@/lib/posts"
|
||||
import { render } from "astro:content"
|
||||
import Disclaimer from "@/components/Disclaimer.astro"
|
||||
import PasswordProtection from "@/components/PasswordProtection.astro"
|
||||
import Comments from "@/components/Comments.astro"
|
||||
@@ -15,7 +16,7 @@ import { site, siteTitleSuffix } from "@/data/site"
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getPostsWithNav()
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.slug },
|
||||
params: { slug: post.id },
|
||||
props: { post },
|
||||
}))
|
||||
}
|
||||
@@ -26,7 +27,7 @@ if (!post) {
|
||||
return Astro.redirect("/")
|
||||
}
|
||||
|
||||
const { Content, remarkPluginFrontmatter } = await post.render()
|
||||
const { Content, remarkPluginFrontmatter } = await render(post)
|
||||
const words = remarkPluginFrontmatter?.words
|
||||
const minutes = remarkPluginFrontmatter?.minutes
|
||||
|
||||
@@ -113,8 +114,8 @@ if (post.data.encrypted && post.data.password) {
|
||||
</div>
|
||||
{encryptedData ? (
|
||||
<div class="mb-6">
|
||||
<PasswordProtection encryptedData={encryptedData} slug={post.slug} class="mb-6 rounded-xl" />
|
||||
<div id={`article-content-${post.slug}`} class="mb-6 hidden encrypted-content">
|
||||
<PasswordProtection encryptedData={encryptedData} slug={post.id} class="mb-6 rounded-xl" />
|
||||
<div id={`article-content-${post.id}`} class="mb-6 hidden encrypted-content">
|
||||
<!-- 内容将在验证密码后动态插入 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function GET(context: APIContext) {
|
||||
title: post.data.title,
|
||||
pubDate: post.data.published,
|
||||
description: post.data.description || "",
|
||||
link: `/posts/${post.slug}/`,
|
||||
link: `/posts/${post.id}/`,
|
||||
}
|
||||
|
||||
if (index >= FULL_CONTENT_LIMIT) return item
|
||||
|
||||
@@ -12,7 +12,7 @@ const posts = await getSortedPosts(true)
|
||||
|
||||
// 转换为时间线所需的简化数据
|
||||
const timelineItems = posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
slug: post.id,
|
||||
title: post.data.title,
|
||||
date: post.data.published.toISOString().slice(0, 10),
|
||||
}))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@import "./transition.css";
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss/index.css";
|
||||
@import "tw-animate-css";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user