项目标题与描述
Astro 是一个现代化的全栈 Web 框架,旨在构建快速的网站。它将强大的开发者体验与轻量级输出相结合,允许您从任何地方拉取内容并部署到任何地方,并由您喜爱的 UI 组件和库驱动。
Astro 的核心价值在于其“岛屿架构”(Islands Architecture),该架构允许您有选择性地激活页面上的交互式组件,从而显著减少发送到浏览器的 JavaScript,确保页面加载迅速且运行流畅。
功能特性
- 岛屿架构: 默认情况下,Astro 发送零客户端 JavaScript。您可以通过
client:*指令有选择性地激活页面上的交互式组件(“岛屿”),从而获得快速的页面加载和交互。 - 服务器优先渲染: 页面在服务器上渲染为静态 HTML,提供出色的 SEO 性能和首次加载速度。
- 多框架支持: 在同一个项目中无缝使用 React、Preact、Vue、Svelte、Solid 等流行的 UI 框架组件。
- 基于文件的路由: 直观的文件系统路由,使得创建页面和布局变得简单。
- 内容集合: 类型安全的内容管理,用于组织、验证和查询博客文章、文档等。
- 视图过渡: 内置的客户端导航和页面过渡动画,提供类似应用的用户体验。
- 内置图像优化: 自动优化图片,支持响应式图片和现代格式。
- Markdown 和 MDX 支持: 编写内容并直接在 Markdown 文件中嵌入组件。
- 环境变量和操作: 安全地管理服务器端和客户端环境变量,并提供类型安全的服务器操作。
- 国际化 (i18n): 轻松构建多语言网站,支持路由和内容本地化。
- 基准测试套件: 项目内置了全面的性能基准测试工具(如渲染、内存、服务器压力测试),确保框架持续优化。
安装指南
快速开始(推荐)
使用以下命令创建一个新的 Astro 项目:
npm create astro@latest
该命令会引导您完成项目设置,包括选择模板和配置。
手动安装
您也可以在现有项目中手动安装 Astro:
npm install --save-dev astro
先决条件
- Node.js:
>=18.20.8 - 包管理器: 建议使用
pnpm(^10.21.0) 进行开发。可以通过 Corepack 启用。
本地开发设置
- 克隆仓库:
git clone <repository-url> cd astro - 安装依赖(使用根目录的
pnpm install):pnpm install - 构建项目:
pnpm run build
注意: Astro 使用 pnpm 工作区,请务必从顶级项目目录运行 pnpm install。
使用 GitHub Codespaces
点击此链接在基于 Web 的 VS Code 中创建此仓库的代码空间,所有开发依赖项将预安装。
使用说明
创建新页面
在 src/pages/ 目录下创建 .astro、.md 或 .mdx 文件即可创建新页面。
示例:src/pages/index.astro
<!-- 组件模板 (HTML + JSX) -->
<html lang="zh">
<head>
<title>{title}</title>
</head>
<body>
<h1>欢迎来到 {title}</h1>
<p>这是一个静态渲染的页面。</p>
</body>
</html>
添加交互式组件(岛屿)
您可以使用 client:* 指令激活框架组件。
示例:使用 React 计数器
<!-- 这个组件现在将在客户端交互 -->
<MyReactCounter client:load />
内容集合
定义内容模式并查询内容。
示例:src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
schema: z.object({
title: z.string(),
date: z.date(),
draft: z.boolean().optional(),
}),
});
export const collections = { blog };
在页面中查询:
<ul>
{blogPosts.map((post) => (
<li><a href={`/blog/${post.slug}`}>{post.data.title}</a></li>
))}
</ul>
运行基准测试
Astro 内置了性能基准测试套件。
# 运行所有基准测试
pnpm run benchmark
# 运行特定测试,例如内存测试
pnpm run benchmark memory
# 针对特定项目运行测试
pnpm run benchmark memory --project render-default
可用的基准测试命令包括 memory(内存和构建速度)、render(渲染速度)、server-stress(服务器压力测试)和 cli-startup(CLI 启动速度测试)。
核心代码
以下展示了 Astro 项目中几个核心模块的代码,这些代码体现了其架构和功能。
1. 基准测试运行器主脚本 (bench/index.js)
此脚本是 astro-benchmark CLI 的核心,负责调度和执行不同的性能基准测试。
import fs from 'node:fs/promises';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import mri from 'mri';
import { makeProject } from './bench/_util.js';
const args = mri(process.argv.slice(2));
if (args.help || args.h) {
// 显示帮助信息
console.log(`\
astro-benchmark <command> [options]
Command
[empty] Run all benchmarks
memory Run build memory and speed test
render Run rendering speed test
server-stress Run server stress test
cli-startup Run CLI startup speed test
Options
--project <project-name> Project to use for benchmark, see benchmark/make-project/ for available names
--output <output-file> Output file to write results to
`);
process.exit(0);
}
// 定义可用的基准测试模块
const commandName = args._[0];
const benchmarks = {
memory: () => import('./bench/memory.js'),
render: () => import('./bench/render.js'),
'server-stress': () => import('./bench/server-stress.js'),
'cli-startup': () => import('./bench/cli-startup.js'),
};
if (commandName && !(commandName in benchmarks)) {
console.error(`Invalid benchmark name: ${commandName}`);
process.exit(1);
}
/**
* 获取输出文件路径
* @param {string} benchmarkName
*/
export async function getOutputFile(benchmarkName) {
let file;
if (args.output) {
// 如果用户指定了输出文件,则使用该路径
file = pathToFileURL(path.resolve(args.output));
} else {
// 否则,生成默认的结果文件路径
file = new URL(`./results/${benchmarkName}-bench-${Date.now()}.json`, import.meta.url);
}
// 确保输出目录存在
await fs.mkdir(new URL('./', file), { recursive: true });
return file;
}
// 执行单个或所有基准测试
if (commandName) {
// 运行单个基准测试
const bench = benchmarks[commandName];
const benchMod = await bench();
const projectDir = await makeProject(args.project || benchMod.defaultProject);
const outputFile = await getOutputFile(commandName);
await benchMod.run(projectDir, outputFile);
} else {
// 运行所有基准测试
for (const name in benchmarks) {
const bench = benchmarks[name];
const benchMod = await bench();
const projectDir = await makeProject(args.project || benchMod.defaultProject);
const outputFile = await getOutputFile(name);
await benchMod.run(projectDir, outputFile);
}
}
2. 渲染性能基准测试 (bench/render.js)
此模块测量 Astro 页面的服务器端渲染时间,是性能评估的关键部分。
import fs from 'node:fs/promises';
import http from 'node:http';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { markdownTable } from 'markdown-table';
import { waitUntilBusy } from 'port-authority';
import { exec } from 'tinyexec';
import { renderPages } from '../make-project/render-default.js';
import { astroBin, calculateStat } from './_util.js';
const port = 4322;
export const defaultProject = 'render-default';
/**
* 运行渲染基准测试的主要函数
* @param {URL} projectDir 要测试的项目目录
* @param {URL} outputFile 结果输出文件
*/
export async function run(projectDir, outputFile) {
const root = fileURLToPath(projectDir);
console.log('Building...');
// 1. 构建项目
await exec(astroBin, ['build'], {
nodeOptions: {
cwd: root,
stdio: 'inherit',
},
throwOnError: true,
});
console.log('Previewing...');
// 2. 启动预览服务器
const previewProcess = exec(astroBin, ['preview', '--port', port], {
nodeOptions: {
cwd: root,
stdio: 'inherit',
},
throwOnError: true,
});
console.log('Waiting for server ready...');
await waitUntilBusy(port, { timeout: 5000 });
console.log('Running benchmark...');
// 3. 执行基准测试
const result = await benchmarkRenderTime();
console.log('Killing server...');
if (!previewProcess.kill('SIGTERM')) {
console.warn('Failed to kill server process id:', previewProcess.pid);
}
// 4. 保存结果
console.log('Writing results to', fileURLToPath(outputFile));
await fs.writeFile(outputFile, JSON.stringify(result, null, 2));
// 5. 在控制台输出格式化结果
console.log('Result preview:');
console.log('='.repeat(10));
console.log(`#### Render\n\n`);
console.log(printResult(result));
console.log('='.repeat(10));
console.log('Done!');
}
/**
* 核心测试逻辑:获取每个页面的渲染时间
* @param {number} portToListen 服务器端口
* @returns {Promise<Record<string, Stat>>} 页面路径到统计数据的映射
*/
export async function benchmarkRenderTime(portToListen = port) {
/** @type {Record<string, number[]>} */
const result = {};
// 对预定义的页面列表进行测试
for (const fileName of renderPages) {
// 每个页面渲染100次以获取稳定数据
for (let i = 0; i < 100; i++) {
const pathname = '/' + fileName.slice(0, -path.extname(fileName).length);
const renderTime = await fetchRenderTime(`http://localhost:${portToListen}${pathname}`);
if (!result[pathname]) result[pathname] = [];
result[pathname].push(renderTime);
}
}
/** @type {Record<string, import('./_util.js').Stat>} */
const processedResult = {};
// 计算平均值、标准差和最大值
for (const [pathname, times] of Object.entries(result)) {
processedResult[pathname] = calculateStat(times);
}
return processedResult;
}
/**
* 将结果格式化为 Markdown 表格
* @param {Record<string, import('./_util.js').Stat>} result
*/
function printResult(result) {
return markdownTable(
[
['Page', 'Avg (ms)', 'Stdev (ms)', 'Max (ms)'],
...Object.entries(result).map(([pathname, { avg, stdev, max }]) => [
pathname,
avg.toFixed(2),
stdev.toFixed(2),
max.toFixed(2),
]),
],
{
align: ['l', 'r', 'r', 'r'],
},
);
}
/**
* 简单的 HTTP 请求工具,获取由 `@benchmark/timer` 适配器返回的渲染时间(纯文本)
* @param {string} url 要请求的页面 URL
* @returns {Promise<number>} 渲染耗时(毫秒)
*/
function fetchRenderTime(url) {
return new Promise((resolve, reject) => {
const req = http.request(url, (res) => {
res.setEncoding('utf8');
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('error', (e) => reject(e));
res.on('end', () => resolve(+data)); // 将响应文本转换为数字
});
req.on('error', (e) => reject(e));
req.end();
});
}
3. 工具函数:计算统计数据 (bench/_util.js)
此模块提供了基准测试中常用的工具函数,例如计算统计指标。
import { createRequire } from 'node:module';
import path from 'node:path';
// 解析 astro 主包的路径,用于获取可执行文件
const astroPkgPath = createRequire(import.meta.url).resolve('astro/package.json');
export const astroBin = path.resolve(astroPkgPath, '../astro.js');
/** @typedef {{ avg: number, stdev: number, max: number }} Stat 统计数据类型定义 */
/**
* 计算一组数字的平均值、标准差和最大值
* @param {number[]} numbers 输入的数字数组
* @returns {Stat} 包含平均值、标准差和最大值的对象
*/
export function calculateStat(numbers) {
// 计算平均值
const avg = numbers.reduce((a, b) => a + b, 0) / numbers.length;
// 计算标准差
const stdev = Math.sqrt(
numbers.map((x) => Math.pow(x - avg, 2)).reduce((a, b) => a + b, 0) / numbers.length,
);
// 计算最大值
const max = Math.max(...numbers);
return { avg, stdev, max };
}
/**
* 根据名称创建或准备基准测试项目
* @param {string} name 项目名称
* @returns {Promise<URL>} 项目目录的 URL
*/
export async function makeProject(name) {
console.log('Making project:', name);
const projectDir = new URL(`../projects/${name}/`, import.meta.url);
// 动态导入对应的项目生成脚本并执行
const makeProjectMod = await import(`../make-project/${name}.js`);
await makeProjectMod.run(projectDir);
console.log('Finished making project:', name);
return projectDir;
}
1dDUwxC45ELRatHf3TeyfhGwlsb3hwnamiAbjKU99UM=