跳到主要内容
Deno 2.4 发布,带来 deno bundle、字节/文本导入、OTel 稳定版等新特性
了解更多
Astro websites run great on Deno

使用 Deno 和 Deno Deploy 构建和发布 Astro 站点

Astro 是一个专为以内容为中心的网站设计的 Web 框架。其 API 设计和工具使其能够轻松地逐步构建更复杂的网站,但默认情况下不向客户端发送任何 JavaScript。就像炸鸡华夫饼一样,Deno 和 Astro 已经很好地协同工作了一段时间。但随着最近 Deno 运行时 1.35 版本的发布,将 Astro 与 Deno 结合使用的体验变得更好了。

今天,我们将介绍所有可以将 Astro 和 Deno 结合使用的方式,然后使用 Deno Deploy 在生产环境中运行您的应用程序。如果您愿意,也可以查看此YouTube 上的现场编程会话,其中我将介绍本文中的大部分代码和概念。

准备好尝试自辣味蜂蜜搭配意大利辣香肠披萨以来最棒的组合了吗?如果是这样,希望您能继续阅读。

开始之前

要使用 Astro 和 Deno 构建静态或服务器渲染的站点,您需要安装一些工具。

您需要…… 因为……
我们将使用 Deno CLI 在 Deno 运行时上本地运行您的 Astro 站点。
一个 npm 客户端 您还需要一个与 npm 兼容的客户端,例如原版 npmyarnpnpm。由于 Astro 是为 Node.js 和 npm 设计的,因此您需要通过 package.json 管理依赖项,并按照 Astro 文档中的描述使用基于 npm 的命令。

如果您尚未安装 Node 和 npm,我建议安装 pnpm。它独立运行,并且在磁盘空间方面既快速又高效。
🤔  为什么我需要 Deno 和 npm?虽然您可以使用 npm(和 Node.js)构建 Astro 站点,但使用 Deno 运行时构建和运行您的 Astro 站点有几个原因。
  • Deno 运行时特性:Deno 运行时支持服务器上的高级 JavaScript 和 TypeScript 语法。它还具有使开发更轻松的内置功能,例如 Deno KV,这是一个无需额外配置即可在本地和 Deno Deploy 中工作的键值数据库。
  • 在生产中使用 Deno Deploy:Deno Deploy 是一个全球无服务器 JavaScript 平台,部署速度超快,由 V8 isolates 而非虚拟机提供支持。这是让您的应用程序代码靠近用户的好方法,为他们提供最快的加载时间。

安装 Deno 和 npm 客户端后,我们可以尝试两种方式将 Astro 和 Deno 结合使用。

我们将介绍这两种方式,但首先让我们构建一个简单的静态站点并将其托管在 Deno Deploy 上。

构建和部署静态站点

默认情况下,Astro 配置为为您的网站生成静态 HTML 和 CSS。在此模式下,您可以在任何可以托管这些文件的环境中提供您的网站,包括 Deno Deploy。让我们看看它是如何工作的。首先在您的终端中执行以下命令生成一个默认的 Astro 项目。

npm create astro@latest

这将启动一个交互式提示,您可以在其中配置 Astro 项目。为了我们的目的,您可以接受所有默认配置选项。最终过程将类似于这样。

kevin@kevin-deno astro-demo % npm create astro@latest

╭─────╮  Houston:
│ ◠ ◡ ◠  Let's build something fast!
╰─────╯

 astro   v2.8.5 Launch sequence initiated.

   dir   Where should we create your new project?
         ./extraterrestrial-equator

  tmpl   How would you like to start your new project?
         Include sample files
      ✔  Template copied

  deps   Install dependencies?
         Yes
      ✔  Dependencies installed

    ts   Do you plan to write TypeScript?
         Yes

   use   How strict should TypeScript be?
         Strict
      ✔  TypeScript customized

   git   Initialize a new git repository?
         Yes
      ✔  Git initialized

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./extraterrestrial-equator
         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.js.cn/chat

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut! 🚀
╰─────╯
kevin@kevin-deno astro-demo %

生成新项目后,您可以按照指示进入新项目文件夹——在上面的示例中是 cd ./extraterrestrial-equator——并使用 npm start 测试您的新应用程序。基本的 Astro 演示应用程序将看起来像这样,默认运行在 https://:3000

default astro website layout

要为生产环境构建静态站点,请使用 npm run build 命令。这将在您当前目录中创建一个 dist 文件夹,其中包含运行您的应用程序所需的所有 HTML、CSS 和(最终)JavaScript 代码。这个默认应用程序足以让我们了解如何使用 Deno Deploy 在互联网上发布这个静态站点。

接下来,让我们设置从 GitHub 的自动部署。首先将您刚刚生成的 Astro 项目推送到公共或私有 GitHub 仓库。如果您是 GitHub 新手,请在此处查阅他们的文档

将您的 Astro 站点上传到 GitHub 仓库后,注册 Deno Deploy 并导航到您的项目仪表板。点击“New Project”(新建项目)按钮,然后选择部署一个现有的 GitHub 仓库,如下图所示。

create a new Deploy project from an existing GitHub repo

Deno Deploy 应该会智能地检测到您正在尝试部署一个使用 Astro 构建的静态站点。但是,要设置自动部署,您需要配置一个 GitHub Action,以便在您将新代码推送到仓库的 main 分支时执行必要的构建步骤。接下来点击提示您执行此操作的按钮。

continue to set up auto deploys

在 GitHub 上,您可以设置一个工作流配置文件,每次您推送到 main 分支时都会执行构建任务。除非您更改了 Astro 构建静态站点资产的方式和位置,否则您应该可以不更改此文件使用它——Deno Deploy 将为您注入新的项目名称。一旦您对配置满意,可以通过点击屏幕右上角的绿色“Commit changes”(提交更改)按钮,直接从该界面将其提交到您的仓库(如果您没有看到该按钮,请一直向上滚动页面)。

edit and commit the config file

🤔  上面配置中“file_server.ts”这一行是什么意思?Deno 标准库提供了一个实用程序,可以从文件夹中提供静态资产。Deno Deploy 默认使用此功能来提供 Astro 站点中的静态资产。如果您想设置自己的静态文件服务器,请随意!您可以在这篇博客文章中了解如何操作。

提交此文件更改后,您的 GitHub 仓库中应该会触发新的构建。不久之后,您的 Deno Deploy 仪表板将更新为最新代码,这些代码已从 GitHub 构建和部署。

your project dashboard

干得好!您刚刚使用 Astro 构建并部署了一个静态站点,并在 Deno Deploy 的帮助下将其发布到互联网上。尽管这令人兴奋,但许多现代 Web 应用程序不能仅仅依靠静态 HTML。要在我们的站点中动态生成页面,我们需要开始在服务器上编写一些逻辑。而这就是 Deno 真正开始为您工作的地方,特别是当您使用我们内置的数据库 Deno KV 时。

接下来,让我们看看如何使用服务器端渲染构建 Astro 站点。

使用服务器端渲染构建动态站点

虽然 Astro 的默认配置是用于生成静态站点,但它也非常擅长在服务器上渲染部分或全部页面。为此,Astro 为许多流行的托管服务提供了适配器,其中就包括 Deno Deploy。使用 SSR 的 Astro 站点不仅会生成在浏览器中运行的静态 HTML、CSS 和 JavaScript,还会在构建过程中生成服务器端代码。这些代码将在您的页面提供服务之前运行,并允许您动态生成发送到浏览器内容。

要开始使用 Astro 和 Deno 构建动态 Web 应用程序,我们推荐使用此模板应用程序。它已预配置了您需要使用 Deno 和 Astro 进行 SSR 的所有更改,以及一些展示如何在 Astro 中构建数据驱动型 Web 应用程序的 CRUD 功能。您可以使用此模板在终端中通过此命令生成一个新项目……

npm create astro@latest -- --template denoland/deno-astro-template

……但是,由于我们将演示如何从 GitHub 自动部署此应用程序,因此您可以通过直接在 GitHub 网站上从该模板创建一个新仓库省去一些步骤。点击下方显示的按钮,并按照提示设置您自己的此应用程序版本。

use template to bootstrap SSR app

在深入了解此应用程序的工作原理之前,让我们先将其部署到 Deno Deploy 并看看它的作用。回到 Deno 项目仪表板,再次选择创建新应用程序并从现有 GitHub 仓库部署。选择您刚刚创建的项目后,Deno Deploy 这次将检测到您正在构建一个使用 SSR 的 Astro 应用程序。

deploy detects Astro SSR

和之前一样,您将收到设置 build.yml 文件的提示,该文件将在每次更改推送到 main 分支时部署您的站点。然而,这次的配置将略有不同,因为我们将使用 Astro 生成的 Deno 脚本作为我们应用程序的入口点。

- name: Upload to Deno Deploy
  uses: denoland/deployctl@v1
  with:
    project: "empty-hedgehog-41"
    entrypoint: "server/entry.mjs" # This file is generated by "npm build"
    root: "dist" # SSR apps still output to the "dist" folder by default

将此文件提交到您的 GitHub 仓库后,您的站点将开始构建,您将在几分钟内看到它上线。该模板应用程序将看起来像这样。

Deno + Astro demo app

它看起来与我们之前使用 Astro 部署的默认静态站点非常相似,不同之处在于它允许您使用页面顶部的表单添加链接卡。您还可以通过点击每个卡片右上角的 X 来删除卡片。

此时,您可以克隆您版本的模板项目并研究一下代码。克隆项目后,使用以下命令安装所需的依赖项

npm install

然后,您可以使用以下命令在您的计算机上运行应用程序,与之前的方式相同:

npm start

不过,这个应用程序还有一些额外的组成部分——让我们来看看一些更重要的功能更改。

配置 Astro 以实现 SSR

为了让 Astro 知道它应该优先在服务器上渲染页面,您需要进行一些配置更改。在我们的模板项目中,此文件名为 astro.config.js,而不是 Astro 文档中的 astro.config.mjs——Deno 中的所有 JavaScript 文件默认都是 ESM 模块。以下是您在此文件中将找到的内容。

import { defineConfig } from "astro/config";
// import deno from "@astrojs/deno";
import deno from "deno-astro-adapter";

// https://astro.js.cn/config
export default defineConfig({
  output: "server",
  adapter: deno(),
});

在上面,我们将 output 模式设置为 server,这告诉 Astro 我们希望默认在每次请求时动态渲染页面,除非我们在特定页面上另有说明。我们还配置了一个 Deno adapter(适配器),它有助于生成服务器端代码,这些代码将随我们站点的每次请求一起运行,并动态生成响应。

Astro 团队维护着一个 Deno 适配器,但截至本文撰写之时(2023 年 7 月),存在一个小问题,即使用现代语言特性的有效 Deno 代码无法与他们的适配器一起使用。上面的代码使用了修补版本,但这种情况应该不会持续太久。

在 Deno 中运行开发服务器

使用 Deno 和 Astro 的主要原因之一是,您的服务器端代码可以利用 Deno 运行时的功能。为了有效地测试我们的代码,我们需要使用 Deno 运行本地开发服务器。我们处理这个问题的方法是替换 package.json 中配置的一些 npm 脚本,让它们使用 Deno 而不是 Node 执行相同的任务。

{
  "name": "deno-astro-template",
  "type": "module",
  "version": "1.0.0",
  "scripts": {
    "dev": "deno run -A --unstable npm:astro dev",
    "start": "deno run -A --unstable npm:astro dev",
    "build": "astro build",
    "preview": "deno run -A --unstable ./dist/server/entry.mjs",
    "astro": "astro",
    "format": "deno fmt && prettier --write ."
  }
}

运行代码以在服务器上生成页面

Astro 组件中,.astro 文件顶部的围栏式“前置元数据”(“Component Script”/组件脚本)代码将在静态生成页面时在构建时运行,或者在服务器渲染页面时在每次请求时运行。此模板修改了 index.astro 组件脚本,使其既包含一个允许用户提交新链接卡内容的表单,又包含一个在每次请求时为页面获取新内容的数据查询。

此页面上的表单还将向同一 URL 发送一个 POST 请求,这将导致此代码在响应表单提交时以不同方式响应(保存新资源,然后重定向到 GET 请求)。

index.astro 中的组件脚本

import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro";
import { addResource, listResources, Resource } from "../data/resources";

// Process form submission if required
if (Astro.request.method === "POST") {
  try {
    const data = await Astro.request.formData();
    const resource: Resource = {
      url: data.get("url")?.toString() || "",
      title: data.get("title")?.toString() || "",
      summary: data.get("summary")?.toString() || "",
    };
    await addResource(resource);
  } catch (error) {
    console.error(error);
  }

  // Redirect to home page to avoid duplicate form submissions
  return Astro.redirect("/");
}

// Get a list of resources
const resources: Resource[] = await listResources();

此模板还使用旨在在客户端运行的 JavaScript,以删除链接卡而无需完全刷新页面。

Card.astro 中的客户端脚本

document.querySelectorAll('span.delete').forEach((span) => {
  span.addEventListener('click', async (e) => {
    e.preventDefault();
    e.stopPropagation();

    const title = (span as HTMLElement).dataset.title || '';
    const encTitle = encodeURIComponent(title);
    const url = `/api/resources.json?title=${encTitle}`;

    try {
      const res = await fetch(url, { method: 'DELETE' });
    if (res.ok) {
      span.parentElement?.remove();
    }
    } catch (err) {
      console.log(err);
    }
  });
});

此模板还有一个 API 端点,用于处理上面 JavaScript 代码中使用的异步 DELETE 请求。

resources.json.ts 中的 API 路由

import { APIRoute } from "astro";
import { deleteResource } from "../../data/resources.ts";

export const del: APIRoute = async ({ request }) => {
  const title = new URL(request.url).searchParams.get("title");
  if (!title) return new Response(null, { status: 400 });

  await deleteResource(title);
  return new Response(null, { status: 204 });
};

切换到 Deno KV

模板项目默认将数据保存到内存中的 Map 对象。但只需进行一些小的更改,就可以将模板应用程序配置为将数据存储在 Deno KV 中。此代码使用 Deno KV 暴露与内存数据存储相同的 API,但使用 Deno KV 使这些更改持久化。

resources_kv.ts 中的 Deno KV 数据访问

const db = await Deno.openKv();

export interface Resource {
  url: string;
  title: string;
  summary: string;
}

export async function addResource(resource: Resource) {
  return await db.set(["resources", resource.title], resource);
}

export async function listResources(): Promise<Resource[]> {
  const iter = db.list({ prefix: ["resources"] });
  const resources = [];
  for await (const res of iter) resources.push(res.value as Resource);
  return resources;
}

export async function deleteResource(title: string) {
  return await db.delete(["resources", title]);
}

请注意,要在 Deno Deploy 上使用 Deno KV,您需要成为私有测试版的一部分。但不久之后,Deno KV 将对所有 Deploy 用户开放,因此如果您在发布几周后阅读此文,很有可能您可以立即在 Deploy 上使用 KV :)

Deno 和 Astro,直冲云霄!

作为 Web 框架,Astro 有许多令人喜爱之处。Astro 组件无需太多帮助即可强大灵活,但也可以与您喜欢的 UI 框架一起使用。无论是静态站点还是服务器渲染页面(或两者的某种组合)的选项,都使得使用 Astro 交付完全动态的网站成为可能。将这种出色的 API 设计与 Deno 运行时和 Deno Deploy 的强大功能相结合,是一种绝佳的组合,我鼓励您深入探索。

您正在将 Astro 和 Deno 结合使用吗?务必告诉我,或者加入 Discord 群组,告诉我们您正在构建什么!