什么是Astro?一款流行静态网站生成器

什么是Astro?一款流行静态网站生成器

从早期的静态、单页的个人网站来看,网络开发已经有了长足的进步。现在,我们有大量不同的语言、框架和内容管理系统可供选择,这些都是为了满足每一个可以想象到的利基市场。

这就是Astro出现的地方,它是JavaScript框架领域最新的酷儿之一。

由Fred K. Schott和其他一些贡献者创建的Astro已经迅速成为开发社区的最爱。它是一个多合一的框架,工作起来很像一个静态网站生成器。

在这篇文章中,我们将解释为什么这么多开发者喜欢Astro,并选择它而不是其他解决方案。我们还将指导你如何使用该框架建立一个基于markdown的博客。

  1. 什么是Astro?
  2. Astro的结构
  3. 定制和扩展Astro
  4. 如何使用Kinsta部署Astro

什么是Astro?

astro-logo

Astro

Astro,或Astro.js,是一个流行的静态网站生成器,为那些想创建内容丰富、运行快速和流畅的网站的人设计的。它的轻量级性质、直观的结构和温和的学习曲线使它对所有经验水平的开发者都有吸引力。

尽管Astro占地面积小,但它配备了强大的工具,大大增加了网站的灵活性,为你节省了内容和主题管理的时间。此外,它还为开发者提供了与他们喜欢的框架一起使用Astro的选择–这对已经有很多喜欢的经验丰富的编码员来说是一个很有吸引力的前景。

以下是Astro从人群中脱颖而出的一些方法:

  • 孤岛架构:Astro将您的用户界面(UI)提取成较小的、孤立的组件,称为 “Astro岛”,可以在任何页面上使用。未使用的JavaScript被替换成轻量级的HTML。
  • 零JavaScript(默认):虽然你可以使用所有你想要的JavaScript来创建你的网站,但Astro将试图通过为你转录代码来部署零JavaScript到生产中。如果你关注的是网站速度,这是一个完美的方法。
  • 包括SSG和SSR:Astro开始时是一个静态网站生成器,但随着时间的推移,它成为一个同时使用静态网站生成(SSG)和服务器端渲染(SSR)的框架。而且你可以选择哪些页面使用哪种方法。
  • Framework-agnostic:当使用Astro时,你可以使用任何你喜欢的JavaScript框架–甚至同时使用多个框架。(我们将在本文后面更详细地讨论这个问题)。

更重要的是,Astro是边缘就绪的,这意味着它可以随时随地、轻松地部署。

准备好了解更多了吗?那就让我们深入了解一下Astro是如何工作的。

Astro的结构

在我们进一步冒险之前,重要的是要了解Astro是如何设置的,以便你能有效地使用它。让我们看一下Astro的核心文件结构:

├── dist/
├── src/
│   ├── components/
│   ├── layouts/
│   └── pages/
│       └── index.astro
├── public/
└── package.json

正如你所看到的,结构本身相当简单。然而,有一些关键点你应该记住:

  • 我们项目的大部分内容都住在src文件夹中。你可以将你的组件、布局和页面安排在子文件夹中。你可以添加额外的文件夹以使你的项目更容易浏览。
  • public文件夹用于存放构建过程之外的所有文件,如字体、图片或robots.txt文件。
  • dist文件夹将包含你想在生产服务器上部署的所有内容。

接下来,让我们更深入地了解Astro的主要组成部分:组件、布局和页面。

组件

组件是可重复使用的代码块,可以包含在你的网站上,类似于WordPress中的短代码。默认情况下,它们的文件扩展名为.astro,但你也可以使用由VueReact、Preact或Svelte构建的非Astro组件。

下面是一个简单组件的例子–在这个例子中,是一个包含 h2 的类 div 标签:

<!-- src/components/Wbolt.astro -->
<div class="wbolt_component">
<h2>Hello, Wbolt!</h2>
</div>

而这里是我们如何将该组件纳入我们的网站:

---
import WboltComponent from ../components/Wbolt.astro
---
<div>
<WboltComponent />
</div>

如上所示,你首先要导入该组件。只有这样,它才能被包含在页面上。

现在是时候给我们的组件添加一些属性了。让我们从 {title} 开始。属性:

---
const { title = 'Hello' } = Astro.props
---
<div class="wbolt_component">
<h2>{title}</h2>
</div>

这里是我们的资产如何实现的:

---
import WboltComponent from ../components/Wbolt.astro
---
<div>
<!-- This shows "Good day" -->
<WboltComponent title="Good day"/>
<!-- This shows "Hello" -->
<WboltComponent />
</div>

很简单,对吗?

你可能已经意识到,Astro组件的真正力量在于它们的全局性和可重用性。它们使你能够通过编辑几行代码来对你的整个网站进行全面的改变,这可以为你节省无数的时间,否则这些时间将花在乏味的、痛苦的文本替换上。

布局

现在,让我们来谈谈布局。除了他们熟悉的主题功能外,Astro中的布局也是可重复使用的组件,但他们是作为代码包装器使用的。

看一下这个例子吧:

---
// src/layouts/Base.astro
const { pageTitle = 'Hello world' } = Astro.props
---
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>{pageTitle}</title>
</head>
<body>
<main>
<slot />
</main>
</body>
</html>

注意这里的 <slot /> 标签。在Astro中, <slot /> 元素充当了实际HTML标签和内容的占位符。

让我们看看它的作用。

下面的代码显示了我们的 <slot /> 标签被替换成了我们想要的代码,所有这些都被我们的Base.astro布局包裹着:

---
import Base from '../layouts/Base.astro';
---
<Base title="Hello world">
<div>
<p>Some example text.</p>
</div>
</Base>

正如你所看到的,我们的 <slot /> 标签被它所代表的HTML所取代,这就是:

<div>
<p>Some example text.</p>
</div>

正如你所看到的,布局,像组件一样,允许你在你的网站上重复使用大块的代码,简化了更新全局内容和设计的挑战。

页面

页面是一种特殊类型的组件,负责路由、数据加载和模板设计。

Astro使用基于文件的路由来生成页面,而不是动态路由。基于文件的方法不仅消耗较少的带宽,而且还可以省去你手动导入组件的麻烦。

下面是一个定义路由的例子:

src/pages/index.astro => yourdomain.com
src/pages/test.astro => domain.com/test
src/pages/test/subpage => domain.com/test/subpage

有了这些路线,我们的结果主页将呈现如下:

<!-- src/pages/index.astro -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>Hello World</title>
</head>
<body>
<h1>Hello, Wbolt</h1>
</body>
</html>

但我们已经知道如何使用布局,所以让我们把它转换为全球通用的东西:

---
import Base from '../layouts/Base.astro';
---
<Base>
<h1>Hello, Wbolt</h1>
</Base>

这样–就干净多了。

我们将在本文后面更详细地讨论Astro的路由问题,但现在,让我们进入有趣的东西:网站建设和定制。

定制和扩展Astro

现在是时候学习如何定制你的Astro网站了! 我们将使用Markdown集合、路由、图像处理以及与React的集成来构建和个性化我们的静态网站。

Markdown集合

在2.0版本中,Astro引入了一种比以前更好的方式来维护Markdown内容。由于有了集合,我们可以确保我们所有的前题数据都被包括在内,并且有正确的关联类型。

最近,在2.5版本中,他们增加了一种可能性,也可以把JSON和YAML文件作为集合来管理。

准备好上手了吗?

首先,把你所有的Markdown文章放在src/content/collection_name文件夹中。我们将为这个项目创建一个博客集合,所以在我们的演示中,这个文件夹将是 src/content/blog

现在是时候在我们的 src/content/config.ts 文件中定义所有需要的 frontmatter 字段。我们的博客将需要以下内容:

  • title (字符串)
  • tags (数组)
  • publishDate (时间)
  • image (字符串,可选)

这就是所有东西放在一起的样子:

import { z, defineCollection } from 'astro:content';
const blogCollection = defineCollection({ 
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
publishDate: z.date(),
}),
});
export const collections = {
'blog': blogCollection,
};

这就是我们的article-about-astro.md Markdown文件的内容:

---
title: Article about Astro
tags: [tag1, tag3]
publishDate: 2023-03-01
---
## Tamen risit
Lorem *markdownum flumina*, laceraret quodcumque Pachyne, **alter** enim
cadavera choro.

的确,我们的Markdown文件没有什么特别之处。但这里有一些隐藏的魔法,如果我们打错了字,就会显现出来。

比如说,我们没有输入 publishDate ,而是不小心输入了 publishData 。在这种拼写错误的情况下,Astro会抛出一个错误:

blog → article-about-astro.md frontmatter does not match collection schema.
"publishDate" is required.

很神奇,对吗?这个灵巧的功能可以帮助我们在几秒钟内找到与前沿问题有关的错误。

我们需要添加的最后一件事是一个显示我们数据的页面。让我们在 src/page/blog/[slug].astro 创建一个文件,代码如下:

---
import Base from '../../layouts/Base.astro';
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Base>
<h1>{entry.data.title} </h1>
<Content />
</Base>

感谢 getStaticPaths ,Astro将为博客集合中的每篇文章创建所有的静态页面。

我们现在唯一缺少的是我们所有文章的列表:

---
import Base from '../../layouts/Base.astro';
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog');
---
<Base>
<ul>
{blogEntries.map(item => <li> <strong><a href={'/blog/' + item.slug}>{item.data.title}</a></strong></li>)}
</ul>
</Base>

正如你所看到的,使用集合使这项任务变得非常简单。

现在,让我们来创建一个数据类型集合。首先,我们必须再次打开 src/content/config.ts 文件并添加一个新的数据集合:

import { z, defineCollection, referenece } from 'astro:content';
const blogCollection = defineCollection({ 
type: 'content',
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
publishDate: z.date(),
author: reference('authors')
}),
});
const authorsCollection = defineCollection({ 
type: 'data',
schema: z.object({
fullName: z.string(),
country: z.string()
}),
});
export const collections = {
'blog': blogCollection,
'authors': authorsCollection,
};

除了创建一个新的集合,我们还在blogCollection中添加了author的引用。

是时候创建一个新的作者了。我们必须在content/authors.json中创建一个名为maciek-palmowski.json的文件:

{
"fullName": "Maciek Palmowski",
"country": "Poland"
}

剩下的最后一件事是在我们的Post中抓取这些数据。要做到这一点,我们需要使用getEntry

---
import Base from '../../layouts/Base.astro';
import { getCollection, getEntry } from 'astro:content';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
const { entry } = Astro.props;
const author = await getEntry(entry.data.author);
const { Content } = await entry.render();
---
<Base>
<h1>{entry.data.title}</h1>
<h2>Author: {author.data.fullName}</h2>
<Content />
</Base>

路由

Astro有两种不同的路由模式。我们已经了解了第一种模式–静态(基于文件的)路由–当我们在前面介绍页面时。

现在我们要把重点转移到动态路由上。

使用动态路由参数,你可以指示一个Astro页面文件,以自动创建具有相同结构的多个页面。当你有很多特定类型的页面时,这很有用(想想作者简介、用户资料、文档文章等等)。

在接下来的例子中,我们将致力于为我们的作者生成生物页面。

在Astro默认的静态输出模式下,这些页面是在构建时生成的,这意味着你必须预先确定获得相应文件的作者名单。另一方面,在动态模式下,页面是在请求时为任何匹配的路线生成的。

如果你想传递一个变量作为你的文件名,在它周围加上括号:

pages/blog/[slug].astro -> blog/test, blog/about-me

让我们使用src/page/blog/[slug]文件中的代码深入了解一下:

---
import Base from '../../layouts/Base.astro';
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Base>
<h1>{entry.data.title}</h1>
<Content />
</Base>

getStaticPaths 路由负责生成所有静态页面。它返回两个对象:

  • params:用来填补我们的URL中的括号
  • props:我们要传递给页面的所有值

就这样,你的页面生成已经搞定了。

图像处理

在谈论高性能网站时,我们不能不提到现代图片格式、正确的大小调整方法和懒惰加载。

幸运的是,Astro在这里也为我们提供了保障。多亏了 @astrojs/image 包,我们可以在几分钟内介绍上述所有内容。

安装该包后,我们可以访问两个组件: ImagePicture

Image 组件用于创建优化的 <img /> 标记。以下是一个示例:

---
import { Image } from '@astrojs/image/components';
import heroImage from '../assets/hero.png';
---
<Image src={heroImage} format="avif" alt="descriptive text" />
<Image src={heroImage} width={300} alt="descriptive text" />
<Image src={heroImage} width={300} height={600} alt="descriptive text" />

类似地,Picture 组件创建了一个优化的 <Picture/> 组件:

---
import { Picture } from '@astrojs/image/components';
import hero from '../assets/hero.png';
---
<Picture src={hero} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="descriptive text" />

SSG vs SSR

默认情况下,Astro作为一个静态网站生成器运行。这意味着所有的内容都被转换为静态HTML页面。

虽然从许多方面来看这是一个完美的方法(尤其是与速度有关的),但我们有时可能更喜欢一个更动态的方法。例如,如果你想为每个用户建立一个单独的个人资料页面,或者如果你的网站上有成千上万的文章,每次重新渲染所有内容都会太耗时。

幸运的是,Astro也可以作为一个完全的服务器端渲染的框架来工作,或者以两者之间的混合模式工作。

要启用侧边的SSR,我们需要在astro.config.mjs中添加以下代码:

import { defineConfig } from 'astro/config';
export default defineConfig({
output: 'server'
});

这是标准的方法。

混合方法意味着在默认情况下,除了添加了 export const prerender = true 的页面外,一切都会动态生成。

在Astro 2.5中,也有可能将静态渲染设置为默认,并手动选择动态路由

由于这些,我们可以,例如,创建一个完全静态生成的网站,有动态的登录和个人资料页面。很好,对吗?

你可以在官方文档中读到更多关于这方面的信息。

整合其他JavaScript框架

Astro的另一个惊人的功能允许你带上你最喜欢的框架,并与Astro协同使用。你可以将Astro与React、Preact、Svelte、Vue、Solid或Alpine混合使用(关于所有的集成,见Astro的 “添加集成” 文档)。

比方说,我们想使用React。首先,我们需要通过在npm中运行以下程序来安装集成:

npx astro add react

现在React已经被集成,我们可以创建一个React组件。在我们的例子中,它将是 src/components/ReactCounter.tsx 中的计数器组件:

import { useState } from 'react';
/** A counter written with React */
export function Counter({ children }) {
const [count, setCount] = useState(0);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);
return (
<>
<div className="counter">
<button onClick={subtract}>-</button>
<pre>{count}</pre>
<button onClick={add}>+</button>
</div>
<div className="counter-message">{children}</div>
</>
);
}

最后但并非最不重要的是,我们需要用以下代码将计数器放在我们的页面上:

---
import * as react from '../components/ReactCounter';
---
<main>
<react.Counter client:visible />
</main>

voilà:你的React组件已被无缝地集成到你的网站。

如何使用Kinsta部署Astro

现在是时候把我们的Astro网站放到网上了。幸运的是,Kinsta是一个完美的主机,可以快速而无痛地进行部署。

首先,为你的网站文件创建一个GitHub仓库。如果你还没有准备好使用自己的文件,你可以克隆我们团队创建的这个Astro入门网站模板

kinsta-astro-starter-repo

Kinsta的Astro启动网站模板的GitHub仓库

一旦你的Repo准备好了,请登录MyKinsta,在左边选择Applications,然后从紫色的Add service下拉菜单中选择Applications

mykinsta-application-add-service

在MyKinsta中添加一个应用程序

最后一步是向Kinsta提供你的构建和部署细节。

大部分你会被问到的问题,比如Process namePayment method,都会有明显或简单的答案。请注意,如果你选择不填 “Start command” 命令,Kinsta会自动指定 npm start 作为该命令。

如果你不确定如何应对任何其他提示,请参考Kinsta的文档,了解特定领域的指导Astro部署实例。你也可以查看Astro自己的在Kinsta部署的指南

一旦你完成了输入你的构建细节,点击Confirm payment method按钮来初始化你的构建。

这就是了! 你现在有了一个用Astro框架创建的实时的、功能齐全的静态网站。

astro-starter-homepage

我们实时的Astro主页

你可以在你的MyKinsta账户中的Deployments下找到你的实时URL和其他部署细节。

astro-deployment-mykinsta

一个成功的Astro部署

小结

Astro清晰的结构、简单的语法和全局组件使得构建和运行一个应用程序非常容易。它的轻量级性质和静态和动态路由的双重使用极大地提高了网站的响应速度,而它与其他JavaScript框架合作的能力,也使它对有经验的编码人员更有吸引力。

如果你的目标是创建一个内容丰富的网站,快速加载,授予模块化功能,并提供静态和动态生成,那么Astro可能是你的正确选择。

评论留言