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/std@0.140.0/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/std@0.140.0/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/std@0.140.0/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/std@0.140.0/http/server.ts";
import { serveFile } from "https://deno.land/std@0.140.0/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/std@0.140.0/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/oak@v10.2.0/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 秒之间。
如果您有任何问题、意见或建议,请在反馈仓库中提出问题,或发送电子邮件给我们。愉快的 Denoing!
您可以在我们的文档中阅读更多关于文件系统 API 的信息:https://deno.org.cn/deploy/docs/runtime-fs。有关 GitHub 集成的更多信息可以在这里找到:https://deno.org.cn/deploy/docs/projects#git-integration。