Astro是一个为以内容为中心的网站设计的Web框架。它的 API 设计和工具使得逐步构建更复杂的网站变得容易,同时在默认情况下不会向客户端发送 JavaScript。就像鸡肉和华夫饼一样,Deno和Astro合作了一段时间。但是随着最近 Deno 运行时的 1.35 版本,Astro 与 Deno 一起使用的体验变得更好了。

今天,我们将介绍结合使用 Astro 和 Deno 的所有方法,然后使用 Deno Deploy 在生产环境中运行您的应用程序。

开始之前

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

你需要。。。因为。。。
Deno CLI我们将使用 Deno CLI 在 Deno 运行时本地运行您的 Astro 站点。
npm 客户端你还需要一个与 npm 兼容的客户端,比如 npm、yarn 或 pnpm。由于 Astro 是为 Node.js 和 npm 设计的,因此您需要通过 package.json 来管理依赖项,并使用基于 npm 的命令,如 Astro 文档中所述。如果你没有安装 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。

  • 生成和部署静态站点。
  • 使用 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 演示应用程序看起来像这样,默认情况下在 http://localhost:3000 上运行。

default astro website layout

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

接下来,让我们从 GitHub 设置自动部署。首先将您刚刚生成的 Astro 项目推送到公共或私有 GitHub 存储库。如果您不熟悉使用 GitHub 执行此操作,请参阅此处的文档。

一旦您的 Astro 站点上传到 GitHub 存储库,请注册 Deno Deploy 并导航到您的项目仪表板。单击“新建项目”按钮,然后选择部署现有的 GitHub 存储库,如下所示。

create a new Deploy project from an existing GitHub repo

Deno Deploy 应该有助于检测到您正在尝试部署使用 Astro 构建的静态站点。但是,若要设置自动部署,需要配置 GitHub 操作,以便在将新代码推送到 main 存储库分支时执行必要的生成步骤。单击提示您下一步执行此操作的按钮。

continue to set up auto deploys

在 GitHub 上,您可以设置一个工作流配置文件,该文件将在您每次推送到 main 分支时执行构建任务。除非您更改了有关 Astro 构建静态站点资产的方式和位置,否则您应该能够原封不动地使用此文件 - Deno Deploy 将为您注入新的项目名称。对此配置感到满意后,可以通过单击屏幕右上角的绿色“提交更改”按钮,从此 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。除了生成在浏览器中运行的静态HTML,CSS和JavaScript之外,使用SSR的Astro站点还将在构建过程中生成服务器端代码。此代码将在您的页面投放之前运行,并允许您动态生成发送到浏览器的内容。

要开始使用 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.config.mjs 像在 Astro 文档中那样 - 默认情况下,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 文件顶部的受保护的“前言”代码(称为“组件脚本”)将在构建时为静态生成的页面运行,或者在服务器呈现页面的每个请求上运行。此模板修改组件 index.astro 脚本,使其既具有允许用户提交新链接卡内容的表单,也具有将在每个请求中获取页面新内容的数据查询。

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

索引中的组件脚本

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

此模板还使用在客户端上运行的 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 端点

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

resources.json.ts 中的 API route

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

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