跳到主要内容
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 网站。
An npm client 你还需要一个 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 隔离 而不是虚拟机驱动。 这是将你的应用程序代码靠近用户运行的好方法,从而为他们提供尽可能快的加载速度。

一旦安装了 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.build/chat

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

生成新项目后,你可以按照指示进入新项目的文件夹 - 在上面的示例中为 cd ./extraterrestrial-equator - 并使用 npm start 测试你的新应用程序。 基本的 Astro 演示应用程序将如下所示,默认情况下在 https://127.0.0.1:3000 上运行。

default astro website layout

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

接下来,让我们设置从 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”按钮(如果你没有看到它,请向上滚动页面)直接从这个 UI 将其提交到你的仓库。

edit and commit the config file

🤔  上面配置中的 “file_server.ts” 行是怎么回事?Deno 标准库 提供了一个实用程序,可以从文件夹中提供静态资源。 Deno Deploy 默认使用它来提供你的 Astro 网站中的静态资源。 如果你想设置你自己的静态文件服务器,请随意! 你可以 在这篇博客文章中 学习如何做到这一点。

在提交对此文件的更改后,应该在你的 GitHub 仓库中触发新的构建。 不久之后,你的 Deno Deploy 仪表板应该会使用最新的代码进行更新,这些代码是从 GitHub 构建和部署的。

your project dashboard

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

接下来让我们看看如何构建一个带有服务器端渲染的 Astro 网站。

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

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

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

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.build/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 文件顶部带围栏的 “front matter” 代码(称为 “组件脚本”)将在静态生成的页面的构建时运行,或在服务器渲染页面的每个请求上运行。 此模板修改了 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,你需要成为 private beta 的一部分。 但不久之后,Deno KV 将对所有 Deploy 用户开放,因此如果你在发布几周后阅读本文,那么你很可能可以立即在 Deploy 上使用 KV :)

Deno 和 Astro,迈向月球!

Astro 作为一个 Web 框架有很多值得喜爱的地方。 Astro 组件功能强大且灵活,无需太多帮助,但也可以 与你最喜欢的 UI 框架一起使用。 选择静态网站或服务器渲染页面(或两者的一些组合)使得使用 Astro 交付完全动态的网站成为可能。 将这种出色的 API 设计与 Deno 运行时和 Deno Deploy 的强大功能相结合,是一种强大的组合,我鼓励你进一步深入探索。

你是否正在一起使用 Astro 和 Deno? 请务必告诉我,或者加入 Discord 让我们知道你正在构建什么!