跳至主要内容
Deno 2 终于来了 🎉️
了解更多

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 Actions 步骤

- 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 运行器准备就绪。对于不涉及 GitHub Actions 的“自动”模式部署,我们的平均部署时间在 1-10 秒之间。

如果您有任何问题、意见或建议,请在 反馈仓库 中打开一个问题,或发送 电子邮件 联系我们。祝您 Deno 快乐!


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