Web-Astro

一个集多功能于一体的 Web 框架。

资源

看了这个大佬的博客感觉非常的流畅,研究一下这个网站所用到的框架 Astro

正文

为什么是 Astro?

官网说用这玩意构建的网站速度快。

注意

功能

段落标题 功能

Astro 是一个集多功能于一体的 Web 框架。它内置包含了你构建网站所需的一切。还有数百个不同的集成API 钩子可根据你的具体用例和需求定制你的项目。

一些亮点包括:

  • 群岛:一种基于组件的针对内容驱动的网站进行优化的 Web 架构。
  • UI 无关:支持 React、Preact、Svelte、Vue、Solid、HTMX、Web 组件等等。
  • 服务器优先 (EN):将沉重的渲染移出访问者的设备。
  • 默认无 JS:让客户端更少的执行 JS ,以提升网站速度。
  • 内容集合:为你的 Markdown 内容,提供组织、校验并保证 TypeScript 类型安全。
  • 可定制:Tailwind、MDX 和数百个集成可供选择。

设计原则

段落标题 设计原则

以下的五个核心设计原则有助于解释我们为什么做了 Astro,它需要解决的问题以及为什么 Astro 可能是你的项目或团队的最佳选择。

Astro 是…

  1. 内容驱动:Astro 专为展示你的内容而设计。
  2. 服务器优先:网站在服务器上渲染 HTML 时运行速度更快。
  3. 默认快速:在 Astro 中应当不可能做出缓慢的网站。
  4. 易于使用:你不需要是一个专家即可使用 Astro 做点什么。
  5. 以开发者为中心:你应该拥有成功所需的资源。

Welcome, world!

创建项目

shell
npm create astro@latest
> npx
> create-astro


 astro   Launch sequence initiated.

   dir   Where should we create your new project?
         ./minor-meteor

  tmpl   How would you like to start your new project?
         Use blog template

  deps   Install dependencies?
         Yes

   git   Initialize a new git repository?
         Yes

      ✔  Project initialized!
         ■ Template copied
         ■ Dependencies installed
         ■ Git initialized

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./minor-meteor
         Run npm run dev to start the dev server. CTRL+C to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at https://astro.build/chat

进项目启动开发服务:

shell
npm run dev
> minor-meteor@0.0.1 dev
> astro dev

▶ Astro collects anonymous usage data.
  This information helps us improve Astro.
  Run "astro telemetry disable" to opt-out.
  https://astro.build/telemetry

08:27:46 [types] Generated 1ms

 astro  v5.0.5 ready in 2122 ms

┃ Local    http://localhost:4321/
┃ Network  use --host to expose

网站的入口内容由 src/pages/index.astro 控制。

前期准备

创建第一个 Astro 页面

提示

Astro 组件的语法是 HTML 的超集。它的设计使得任何有 HTML 或 JSX 经验的人都感到熟悉。

src/pages/ 下创建一个 test.astro,里面放上内容:

astro
---
import BaseHead from "../components/BaseHead.astro";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
---
 
<!doctype html>
<html lang="en">
    <head>
        <BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
    </head>
    <body>
        <Header />
        <main>
            <h1>我的 Astro 网站</h1>
            <h1>关于我</h1>
            <h2>……和我的新 Astro 网站!</h2>
 
            <p>
                我正在学习 Astro
                的入门教程。这是我网站上的第二个页面,也是我自己建立的第一页面!
            </p>
 
            <p>
                随着我完成更多教程,该站点将更新,所以请继续查看我的旅程将如何进行吧!
            </p>
        </main>
        <Footer />
    </body>
</html>

index.astro 中放入跳转入口:

astro
<a href="/test/">测试</a>

打开 /test/ 则会看到:

webp

页面

第一篇 Markdown 文章

创建 src/pages/posts/post-1.md,里面放上:

markdown
---
title: '我的第一篇博客文章'
pubDate: 2024-12-16
description: '这是我 Astro 博客的第一篇文章。'
author: 'Astro 学习者'
image:
    url: 'https://docs.astro.build/assets/rose.webp'
    alt: 'The Astro logo on a dark background with a pink glow.'
tags: ["astro", "blogging", "learning in public"]
---
 
# 我的第一篇博客文章
 
 欢迎来到我学习关于 Astro 的新博客!在这里,我将分享我建立新网站的学习历程。
 
 ## 我做了什么
 
 1. **安装 Astro**:首先,我创建了一个新的 Astro 项目并设置好了我的在线账号。
 
 2. **制作页面**:然后我学习了如何通过创建新的 `.astro` 文件并将它们保存在 `src/pages/` 文件夹里来制作页面。
 
 3. **发表博客文章**:这是我的第一篇博客文章!我现在有用 Astro 编写的页面和用 Markdown 写的文章了!
 
 ## 下一步计划
 
 我将完成 Astro 教程,然后继续编写更多内容。关注我以获取更多信息。
 
 ```C++
 #include <iostream>
 using namespace std;
 int main() {
   cout << "Hello, world!" << endl;
   return 0;
 }

![webp](02.webp)

​	`.md` 文件将会自动被渲染成 HTML。

### 添加 Astro 的动态内容

- [添加「关于你」的动态内容 | Docs](https://docs.astro.build/zh-cn/tutorial/2-pages/3/)

​	Astro 的顶部代码栅栏是由 Javascript 编写的。可以在上面定义变量:

```astro
---
const msg = "Hello, world!";
---

在正文内容中,用 {} 包含 Javascript 的表达式:

astro
<h1>{msg}</h1>
webp

这个值在网页中就会被正确转换。

复杂一点的案例:

astro
---
const pageTitle = "关于我";
 
const identity = {
  firstName: "莎拉",
  country: "加拿大",
  occupation: "技术撰稿人",
  hobbies: ["摄影", "观鸟", "棒球"],
};
 
const skills = ["HTML", "CSS", "JavaScript", "React", "Astro", "Writing Docs"];
---
 
<p>以下是关于我的几个事实:</p>
<ul>
  <li>我的名字是{identity.firstName}.</li>
  <li>我住在{identity.country}。我的职业是{identity.occupation}。</li>
  {identity.hobbies.length >= 2 &&
    <li>我的两个习惯:{identity.hobbies[0]}和{identity.hobbies[1]}</li>
  }
</ul>
<p>我的技能是:</p>
<ul>
  {skills.map((skill) => <li>{skill}</li>)}
</ul>

提示

Astro 的模板语法类似于 JSX 语法。如果你想知道如何在你的 HTML 中使用你的脚本,那么搜索一下 JSX 中是如何做到的,这可能是一个很好的出发点!

条件渲染:

astro
---
const happy = true;
const finished = false;
const goal = 3;
---
 
<!-- 显示 -->
{happy && <p>我非常乐意学习 Astro!</p>}
 
<!-- 没有东西被显示 -->
{finished && <p>我完成了这节教程!</p>}
 
<!-- 为真,渲染冒号前面的 -->
{goal === 3 ? <p>我的目标是在三天内完成。</p> : <p>我的目标不是 3 天。</p>}

添加样式

引入 CSS:

webp

注意

Astro 的 <style> 标签还可以使用 define:vars={ {...} } 指令引用 frontmatter 中的任何变量。你可以在代码围栏中定义变量,然后在样式标签中将其用作 CSS 变量使用

astro
---
const skillColor = "navy";
---
 
<style define:vars={{skillColor}}>
  h1 {
    color: purple;
    font-size: 4rem;
  }
  .skill {
    color: green;
    color: var(--skillColor);
    font-weight: bold;
  }
</style>
webp

全局样式

注意

Astro 的 <style> 标签默认是有作用域的,意味着它只能影响到当前文件中的元素。

在 Astro 文件的 frontmatter 中加入如下语句即可引入外部 CSS:

astro
---
import '../styles/global.css';
---

组件

创建和设计 Astro 组件

注意

在项目中创建一个新的文件夹 src/components/ 来容纳 .astro 文件,它们会被用来生成 HTML 代码,但不会成为网站上的新页面。

创建一个 src/components/Navigation.astro,放入如下内容:

astro
---
---
<a href="/">首页</a>
<a href="/about/">关于</a>
<a href="/blog/">博客</a>

在 frontmatter 中注册后便可使用这个组件:

astro
---
import Navigation from '../components/Navigation.astro';
---
 
<Navigation />
webp

就是教你怎么在组件中复用另一个组件,同时传递参数。

创建一个 src/components/Navigation.astro,内容如下:

astro
---
const { platform, username } = Astro.props;
---
<a href={`https://www.${platform}.com/${username}`}>{platform}</a>

Astro.props; 这么写应该就是可以提供一个传参的接口了(作为 props 接收)。

src/components/Footer.astro 中引入并使用它:

astro
---
import Social from './Social.astro';
---
 
<Social platform="twitter" username="astrodotbuild" />
webp

第一个浏览器脚本

创建一个 src/components/Hamburger.astro,内容如下:

astro
---
 
---
 
<style>
    .hamburger {
        padding-right: 20px;
        cursor: pointer;
    }
 
    .hamburger .line {
        display: block;
        width: 40px;
        height: 5px;
        margin-bottom: 10px;
        background-color: #ff9776;
    }
</style>
<div class="hamburger">
    <span class="line"></span>
    <span class="line"></span>
    <span class="line"></span>
</div>

创建 src/components/menu.js,内容如下:

javascript
document.querySelector('.hamburger').addEventListener('click', () => {
    console.log('clicked');
  });

引入并渲染 Hamburger.astro,并引入 menu.js

astro
---
import Hamburger from "../components/Hamburger.astro";
---
 
<Hamburger />
 
<script>
    import "../scripts/menu.js";
</script>
webp

布局

第一个布局组件

创建一个 src/layouts/BaseLayout.astro,给 BaseLayout 设置插槽:

注意

<slot /> 允许你将写在开放和闭合 <Component></Component> 标签之间的子内容注入(或者说是「插入」)到任何 Component.astro 文件中。

astro
---
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import '../styles/global.css';
const { pageTitle } = Astro.props;
---
<html lang="zh-cn">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <title>{pageTitle}</title>
  </head>
  <body>
    <Header />
    <h1>{pageTitle}</h1>
    <slot />
    <Footer />
    <script>
      import "../scripts/menu.js";
    </script>
  </body>
</html>

修改 src/pages/index.astro,使用这个布局组件,并传参:

astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
const pageTitle = "首页";
---
<BaseLayout pageTitle={pageTitle}>
  <h2>My awesome blog subtitle</h2>
</BaseLayout>
webp

博客布局

创建 src/layouts.MarkdownPostLayout.astro

astro
---
const { frontmatter } = Astro.props;
---
 
<html lang="zh-cn">
    <head>
        <meta charset="utf-8" />
        <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
        <meta name="viewport" content="width=device-width" />
        <meta name="generator" content={Astro.generator} />
    </head>
    <body>
        <h1>{frontmatter.title}</h1>
        <p>Written by {frontmatter.author}</p>
        <slot />
    </body>
</html>

src/pages/posts/post-1.md 中,设置 frontmatter 属性。

markdown
---
layout: ../../layouts/MarkdownPostLayout.astro
title: '我的第一篇博客文章'
pubDate: 2022-07-01
description: '这是我 Astro 博客的第一篇文章。'
author: 'Astro 学习者'
image:
    url: 'https://docs.astro.build/assets/rose.webp'
    alt: 'The Astro logo on a dark background with a pink glow.'
tags: ["astro", "blogging", "learning in public"]
---
 
# 我的第一篇博客文章
 
 欢迎来到我学习关于 Astro 的新博客!在这里,我将分享我建立新网站的学习历程。
 
 ## 我做了什么
 
 1. **安装 Astro**:首先,我创建了一个新的 Astro 项目并设置好了我的在线账号。
 
 2. **制作页面**:然后我学习了如何通过创建新的 `.astro` 文件并将它们保存在 `src/pages/` 文件夹里来制作页面。
 
 3. **发表博客文章**:这是我的第一篇博客文章!我现在有用 Astro 编写的页面和用 Markdown 写的文章了!
 
 ## 下一步计划
 
 我将完成 Astro 教程,然后继续编写更多内容。关注我以获取更多信息。
 
 ```C++
 #include <iostream>
 using namespace std;
 int main() {
   cout << "Hello, world!" << endl;
   return 0;
 }

![webp](10.webp)

​	可以看到 Markdown 的内容被插入到了 `<slot/>` 中。

### 布局结合,两全其美

​	修改 `MarkdownPostLayout.astro` 里的内容:

```astro
---
import BaseLayout from './BaseLayout.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout pageTitle={frontmatter.title}>
  <h1>{frontmatter.title}</h1>
  <p>{frontmatter.pubDate.toString().slice(0,10)}</p>
  <p><em>{frontmatter.description}</em></p>
  <p>Written by: {frontmatter.author}</p>
  <img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
  <slot />
</BaseLayout>

这么做在布局里又套了一个布局。

这说明 astro 的布局使用 const { frontmatter } = Astro.props; 接收子对象的数据,使用 <BaseLayout pageTitle={frontmatter.title}> 将数据传递给父对象。

webp

Astro API

创建文章存档页

创建 src/pages/blog.astro,内容如下:

astro
---
import BaseLayout from '../layouts/BaseLayout.astro'
const allPosts = await Astro.glob('./posts/*.md');
const pageTitle = "我的 Astro 学习博客";
---
<BaseLayout pageTitle={pageTitle}>
  <p>在这里,我将分享我的学习 Astro 的之旅。</p>
  <ul>
    {allPosts.map((post) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
  </ul>
</BaseLayout>

这里说明了 const allPosts = await Astro.glob('./posts/*.md'); 会将所有 ./posts/ 下的 .md 文件读取,并将其中的 frontmatter 信息以对象的形式存在 allPosts 中。

之后就可以调用这个对象。

webp

生成标签页面

创建 src/pages/tags/[tag].astro

astro
---
import BaseLayout from '../../layouts/BaseLayout.astro';
 
export async function getStaticPaths() {
  return [
    { params: { tag: "astro" } },
    { params: { tag: "successes" } },
    { params: { tag: "community" } },
    { params: { tag: "blogging" } },
    { params: { tag: "setbacks" } },
    { params: { tag: "learning in public" } },
  ];
}
 
const { tag } = Astro.params;
---
<BaseLayout pageTitle={tag}>
  <p>包含「{tag}」标签的文章</p>
</BaseLayout>

之后就会创建 /tags/astro/tags/successes……等页面。

之后我们将其改为以动态路由的形式:

astro
---
import BaseLayout from '../../layouts/BaseLayout.astro';
 
export async function getStaticPaths(): Promise<{ params: { tag: string }, props: { posts: any[] } }[]> {
  const allPosts = await Astro.glob('../posts/*.md');
 
  return [
    {params: {tag: "astro"}, props: {posts: allPosts}},
    {params: {tag: "successes"}, props: {posts: allPosts}},
    {params: {tag: "community"}, props: {posts: allPosts}},
    {params: {tag: "blogging"}, props: {posts: allPosts}},
    {params: {tag: "setbacks"}, props: {posts: allPosts}},
    {params: {tag: "learning in public"}, props: {posts: allPosts}}
  ];
}
 
const { tag } = Astro.params;
const { posts } = Astro.props;
const filteredPosts = posts.filter((post) => post.frontmatter.tags?.includes(tag));
---
 
<BaseLayout pageTitle={tag}>
    <p>包含「{tag}」标签的文章</p>
    <ul>
        {
            filteredPosts.map((post) => (
                <li>
                    <a href={post.url}>{post.frontmatter.title}</a>
                </li>
            ))
        }
    </ul>
</BaseLayout>

重要

  • 如果需要构建页面路由的信息,请将其编写在 getStaticPaths() 内部

  • 要在页面路由的 HTML 模板中接收信息,请将其编写在 getStaticPaths() 外部

从现有标签生成页面

如此做,将会从现有标签生成标签页,避免了手动定义:

astro
---
import BaseLayout from '../../layouts/BaseLayout.astro';
 
export async function getStaticPaths(): Promise<{ params: { tag: string }, props: { posts: any[] } }[]> {
  const allPosts = await Astro.glob('../posts/*.md');
 
  const uniqueTags = [...new Set(allPosts.map((post) => post.frontmatter.tags).flat())];
 
  return uniqueTags.map((tag) => {
    const filteredPosts = allPosts.filter((post) => post.frontmatter.tags.includes(tag));
    return {
      params: { tag },
      props: { posts: filteredPosts },
    };
  });
}
 
const { tag } = Astro.params;
const { posts } = Astro.props;
---
<BaseLayout pageTitle={tag}>
  <p>包含「{tag}」标签的文章</p>
  <ul>
    {posts.map((post) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
  </ul>
</BaseLayout>
webp

RSS

安装 RSS 包:

shell
npm install @astrojs/rss
up to date in 9s

174 packages are looking for funding
  run `npm fund` for details

重新启动服务器:

shell
npm run dev

创建 src/pages/rss.xml.js

javascript
import rss, { pagesGlobToRssItems } from '@astrojs/rss';
 
export async function GET(context) {
  return rss({
    title: 'Astro Learner | Blog',
    description: 'My journey learning Astro',
    site: context.site,
    items: await pagesGlobToRssItems(import.meta.glob('./**/*.md')),
    customData: `<language>en-us</language>`,
  });
}

访问 http://localhost:4321/rss.xml:

xml
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Astro Learner | Blog</title><description>My journey learning Astro</description><link>https://example.com/</link><language>en-us</language><item><title>我的第一篇博客文章</title><link>https://example.com/posts/post-1/</link><guid isPermaLink="true">https://example.com/posts/post-1/</guid><description>这是我 Astro 博客的第一篇文章。</description><pubDate>Fri, 01 Jul 2022 00:00:00 GMT</pubDate><author>Astro 学习者</author></item><item><title>我的第二篇博客文章</title><link>https://example.com/posts/post-2/</link><guid isPermaLink="true">https://example.com/posts/post-2/</guid><description>学习了一些 Astro 后,我根本停不下来!</description><pubDate>Fri, 08 Jul 2022 00:00:00 GMT</pubDate><author>Astro 学习者</author></item><item><title>我的第三篇博客文章</title><link>https://example.com/posts/post-3/</link><guid isPermaLink="true">https://example.com/posts/post-3/</guid><description>我遇到了一些问题,但是在社区里面提问真的很有帮助!</description><pubDate>Fri, 15 Jul 2022 00:00:00 GMT</pubDate><author>Astro 学习者</author></item></channel></rss>

其中,<link>https://example.com/</link> 里的值在 astro.config.mjs 中编辑。

群岛

搭建 Astro 岛屿

添加 Preact:

shell
npx astro add preact
√ Resolving packages...

  Astro will run the following command:
  If you skip this step, you can always run it yourself later

 ╭─────────────────────────────────────────────────────╮
 │ npm install @astrojs/preact@^4.0.0 preact@^10.25.2  │
 ╰─────────────────────────────────────────────────────╯

√ Continue? ... yes
√ Installing dependencies...

  Astro will make the following changes to your config file:

 ╭ astro.config.mjs ────────────────────────────────╮
 │ // @ts-check                                     │
 │ import { defineConfig } from 'astro/config';     │
 │ import mdx from '@astrojs/mdx';                  │
 │ import sitemap from '@astrojs/sitemap';          │
 │                                                  │
 │ import preact from '@astrojs/preact';            │
 │                                                  │
 │ // https://astro.build/config                    │
 │ export default defineConfig({                    │
 │     site: 'https://example.com',                 │
 │     integrations: [mdx(), sitemap(), preact()],  │
 │ });                                              │
 ╰──────────────────────────────────────────────────╯

√ Continue? ... yes
  
   success  Added the following integration to your project:
  - @astrojs/preact

  Astro will make the following changes to your tsconfig.json:

 ╭ tsconfig.json ──────────────────────────╮
 │ {                                       │
 │   "extends": "astro/tsconfigs/strict",  │
 │   "include": [                          │
 │     ".astro/types.d.ts",                │
 │     "**/*"                              │
 │   ],                                    │
 │   "exclude": [                          │
 │     "dist"                              │
 │   ],                                    │
 │   "compilerOptions": {                  │
 │     "strictNullChecks": true,           │
 │     "jsx": "react-jsx",                 │
 │     "jsxImportSource": "preact"         │
 │   }                                     │
 │ }                                       │
 ╰─────────────────────────────────────────╯

√ Continue? ... yes
  
   success  Successfully updated TypeScript settings

创建 src/components/Greeting.jsx

提示

Astro 支持多种 JavaScript 配置文件格式,如:astro.config.jsastro.config.mjsastro.config.cjsastro.config.ts。我们推荐在大多数情况下使用 .mjs,如果你想在配置文件中编写 TypeScript,则建议使用 .ts

jsx
import { useState } from 'preact/hooks';
 
export default function Greeting({messages}) {
 
  const randomMessage = () => messages[(Math.floor(Math.random() * messages.length))];
 
  const [greeting, setGreeting] = useState(messages[0]);
 
  return (
    <div>
      <h3>{greeting}! Thank you for visiting!</h3>
      <button onClick={() => setGreeting(randomMessage())}>
        New Greeting
      </button>
    </div>
  );
}

src/pages/index.astro 里:

注意

client:load 指令告诉 Astro 在页面加载时将其 JavaScript 发送并重新运行到客户端上,使组件可交互。这称为 被 Hydrate 的 组件。

  • <ReactCounter client:load /> 组件会载入 HTML、CSS 和 JavaScript。
  • <SvelteCard /> 组件会载入仅 HTML 和 CSS。
astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
import Greeting from '../components/Greeting';
const pageTitle = "首页";
---
<BaseLayout pageTitle={pageTitle}>
  <h2>My awesome blog subtitle</h2>
  <Greeting client:load messages={["Hi", "Hello", "Howdy", "Hey there"]} />
</BaseLayout>
webp

其他

命令对照:

AstroHexo功能
npm run devhexo s服务器浏览
npm run buildhexo d构建项目

使用主题:astro-theme-pure

下载主题:

shell
git clone https://github.com/cworld1/astro-theme-pure.git
cd astro-theme-pure

安装依赖:

shell
npm install

之后要自己慢慢折腾了。

使用主题:Startlight

创建项目:

shell
npm create astro@latest -- --template starlight

进这个项目,安装依赖:

shell
npm install

启动服务器:

shell
astro dev
webp

提示

更新内容

编辑 src/content/docs/index.mdx 文件即可查看此页面的更改。

添加新内容

将 Markdown 或 MDX 文件添加到 src/content/docs 目录中即可创建新页面。

配置您的站点

astro.config.mjs 文件中编辑 sidebar 和其他配置项。

阅读文档

Starlight 文档 中了解更多信息。

渲染插件

注意

Astro 支持添加第三方 remarkrehype 插件来解析 Markdown。

  • remark 处理 Markdown
  • rehype 处理 Html

这里,添加一个 remark 插件:remcohaszing/remark-mermaidjs: A remark plugin to render mermaid diagrams using playwright

shell
npm install remark-mermaidjs
npm install playwright
npx playwright install --with-deps chromium

astro.config.mjs 中引入插件:

javascript
import remarkMermaid from 'remark-mermaidjs'

astro.config.mjs 中使用插件:

javascript
export default defineConfig({
    ...
	// Markdown Options
    markdown: {
        remarkPlugins: [remarkReadingTime, remarkMath, remarkArxivCards, remarkMermaid],
        rehypePlugins: [
          [rehypeKatex, {}],
          rehypeHeadingIds,
          [
            rehypeAutolinkHeadings,
            {
              behavior: 'append',
              properties: { className: ['anchor'] },
              content: { type: 'text', value: '#' }
            }
          ]
    ],
    ...
})

预览:

webp