跳到主要内容

Deno Deploy 上的静态文件

Deno Deploy 一直非常擅长在边缘动态生成内容。我们可以在靠近用户的位置运行 JavaScript 代码,这可以显著减少响应时间延迟。然而,许多应用程序并非完全动态:它们拥有静态资源,如 CSS 文件、客户端 JS 和图像。

到目前为止,Deno Deploy 还没有一个很好的方法来处理静态资源。您可以选择将它们编码到 JavaScript 代码中、手动滚动 CDN,或者从您的 GitHub 仓库中拉取文件。这些选项都不是理想的。

一个新的原语

Deno Deploy 现在对静态文件提供一流的支持。您的静态文件在您部署代码时存储在我们的网络上,然后分布在全球各地,靠近您的用户。您可以使用 Deno 文件系统 API 和 fetch 从在边缘运行的 JavaScript 代码访问这些文件。

由于文件的实际服务仍然由您在边缘运行的代码控制,因此您可以完全控制所有响应,即使是对静态文件的响应也是如此。例如,这可以用于

  • 仅向已登录用户提供文件
  • 向您的文件添加 CORS 标头
  • 在静态文件提供服务之前,在边缘使用一些动态内容修改文件
  • 根据用户的浏览器提供不同的文件

在 Deno Deploy 中,静态文件不是一个完全独立的系统。

您可以做的最基本的事情是将整个文件读入内存并提供给用户

import { serve } from "https://deno.land/[email protected]/http/server.ts";

const HTML = await Deno.readFile("./index.html");

serve(async () => {
  return new Response(HTML, {
    headers: {
      "content-type": "text/html",
    },
  });
});

这对于小文件非常有效。对于较大的文件,您可以将文件直接流式传输给用户,而不是将其缓存在内存中

import { serve } from "https://deno.land/[email protected]/http/server.ts";

const FILE_URL = new URL("/movie.webm", import.meta.url).href;

serve(async () => {
  const resp = await fetch(FILE_URL);
  return new Response(resp.body, {
    headers: {
      "content-type": "video/webm",
    },
  });
});

想要列出所有可用文件的目录?使用 Deno.readDir 即可轻松实现

import { serve } from "https://deno.land/[email protected]/http/server.ts";

serve(async () => {
  const entries = [];
  for await (const entry of Deno.readDir(".")) {
    entries.push(entry);
  }

  const list = entries.map((entry) => {
    return `<li>${entry.name}</li>`;
  }).join("");

  return new Response(`<ul>${list}</ul>`, {
    headers: {
      "content-type": "text/html",
    },
  });
});

标准库的文件服务实用程序可用于提供静态文件。这些实用程序将设置适当的 Content-Type 标头,并支持更复杂的功能,如 Range 请求,开箱即用

import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { serveFile } from "https://deno.land/[email protected]/http/file_server.ts";

serve(async (req) => {
  return await serveFile(req, `${Deno.cwd()}/static/index.html`);
});

如果您正在使用像 oak 这样的成熟 HTTP 框架来提供静态内容,您也可以使用 Deno Deploy

import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();
app.use(async (ctx) => {
  try {
    await ctx.send({
      root: `${Deno.cwd()}/static`,
      index: "index.html",
    });
  } catch {
    ctx.response.status = 404;
    ctx.response.body = "404 File not found";
  }
});

await app.listen({ port: 8000 });

Deno Deploy 当前支持的文件系统 API 的完整列表

  • Deno.readFile 将文件读入内存
  • Deno.readTextFile 将文件作为 UTF-8 字符串读入内存
  • Deno.readDir 获取文件夹中文件和文件夹的列表
  • Deno.open 打开文件以分块读取(用于流式传输)
  • Deno.stat 获取有关文件或文件夹的信息(获取大小或类型)
  • Deno.lstat 与上述相同,但不遵循符号链接
  • Deno.realPath 获取文件或文件夹的路径,在解析符号链接之后
  • Deno.readLink 获取符号链接的目标

Github 集成

现在您可能会问自己:如何将这些新颖的静态文件添加到我的部署中?

默认情况下,如果您已将 GitHub 仓库链接到 Deploy,则您仓库中的所有文件都将作为静态文件提供。无需更改。如果您对存储在仓库中的一些资源(如图像或博客的 markdown 文件)使用静态文件,这将非常棒。

然而,有时您希望在部署时生成静态文件。例如,当使用像 Remix.run 这样的框架,或者当您使用静态站点生成器时。对于这种情况,我们现在允许您使用 deployctl 工具部署您的代码和静态资源。在边缘提供您当前的工作目录非常简单,只需

deployctl deploy --project my-project --prod https://deno.land/[email protected]/http/file_server.ts

当您只想部署一次或者您的代码不在版本控制中并且始终从本地计算机部署时,这非常有效,但对于大多数项目来说并非如此。

托管在 Github 上的项目将希望使用 Github Actions 运行构建步骤以生成 HTML 或其他静态内容,然后上传到 Deno Deploy。我们为此目的提供了一个特殊的 Github Action 步骤

- name: Upload to Deno Deploy
  uses: denoland/deployctl@v1
  with:
    project: my-project
    entrypoint: main.js
    root: dist

甚至不需要为此配置任何访问令牌或密钥。只需在 Deno Deploy 仪表板中链接您的 GitHub 仓库,并将项目设置为“GitHub Actions”部署模式。身份验证由 GitHub Actions 透明地处理。

为什么选择 GitHub Actions 而不是自定义 CI 系统?GitHub Actions 现在是持续集成的实际标准,许多开发人员已经熟悉它。为什么要重新发明已经很棒的东西呢?

示例:静态生成的站点

为了结束这篇博文,这是一个真实示例,展示了在 deploy 上运行的服务器如何提供在 GitHub Actions 中构建的静态站点。该站点由静态站点生成器构建(在本例中是令人惊叹的 https://lumeland.github.io)。除了静态文件外,该站点还包含一个 /api/time 端点,该端点动态返回当前时间。

https://lume-example.deno.dev/ 上尝试该示例。

这是项目使用的 GitHub Actions 工作流程文件

name: ci
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Clone repository
        uses: actions/checkout@v2

      - name: Install Deno
        uses: denoland/setup-deno@main
        with:
          deno-version: 1.18.2

      - name: Build site
        run: deno run -A https://deno.land/x/lume/ci.ts

      - name: Upload to Deno Deploy
        uses: denoland/deployctl@v1
        with:
          project: lume-example
          entrypoint: server/main.ts

以及提供站点和 API 端点的实际代码

import { Application, Router } from "https://deno.land/x/[email protected]/mod.ts";

const app = new Application();

// First we try to serve static files from the _site folder. If that fails, we
// fall through to the router below.
app.use(async (ctx, next) => {
  try {
    await ctx.send({
      root: `${Deno.cwd()}/_site`,
      index: "index.html",
    });
  } catch {
    next();
  }
});

const router = new Router();

// The /api/time endpoint returns the current time in ISO format.
router.get("/api/time", (ctx) => {
  ctx.response.body = { time: new Date().toISOString() };
});

// After creating the router, we can add it to the app.
app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 8000 });

部署有多快?从 git push 到全球范围内的更改生效的时间约为 25 秒。其中 15 秒用于等待 GitHub Actions runner 准备就绪。对于不涉及 GitHub Actions 的“自动”模式部署,我们的平均部署时间在 1-10 秒之间。

如果您有任何问题、意见或建议,请通过在反馈仓库上开设 issue 或发送电子邮件告诉我们。祝您 Denoing 愉快!


您可以在我们的文档中阅读更多关于文件系统 API 的信息:https://deno.org.cn/deploy/docs/runtime-fs。有关 GitHub 集成的更多信息,请访问此处:https://deno.org.cn/deploy/docs/projects#git-integration