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。