SvelteKit 最新中文文档教程(8)—— 部署 Node 服务端
<h2 id="前言">前言</h2><p>Svelte,一个语法简洁、入门容易,面向未来的前端框架。</p>
<p>从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,<strong>从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1</strong>:</p>
<p><img src="https://yayujs-blog.oss-cn-beijing.aliyuncs.com/405488775-48df16b1-939c-489b-8d52-6071869893f0.png"></p>
<p>Svelte 以其独特的编译时优化机制著称,具有<strong>轻量级</strong>、<strong>高性能</strong>、<strong>易上手</strong>等特性,<strong>非常适合构建轻量级 Web 项目</strong>。</p>
<p>为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。</p>
<p>如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p>
<h2 id="node-服务端">Node 服务端</h2>
<p>要生成独立的 Node 服务端,请使用 <code>adapter-node</code>。</p>
<h2 id="使用方法">使用方法</h2>
<p>使用 <code>npm i -D @sveltejs/adapter-node</code> 安装,然后将适配器添加到您的 <code>svelte.config.js</code>:</p>
<pre><code class="language-js">// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter()
}
};
</code></pre>
<h2 id="部署">部署</h2>
<p>首先,使用 <code>npm run build</code> 构建您的应用。这将在适配器选项中指定的输出目录(默认为 <code>build</code>)中创建生产服务端。</p>
<p>要运行应用程序,您需要输出目录、项目的 <code>package.json</code> 和 <code>node_modules</code> 中的生产依赖项。生产依赖项可以通过复制 <code>package.json</code> 和 <code>package-lock.json</code> 然后运行 <code>npm ci --omit dev</code> 来生成(如果您的应用没有任何依赖项,可以跳过此步骤)。然后您可以使用以下命令启动您的应用:</p>
<pre><code class="language-bash">node build
</code></pre>
<p>开发依赖项将使用 Rollup 打包到您的应用中。要控制某个包是打包还是外部化,请将其分别放在 <code>package.json</code> 的 <code>devDependencies</code> 或 <code>dependencies</code> 中。</p>
<h3 id="压缩响应">压缩响应</h3>
<p>通常您会希望压缩来自服务端的响应。如果您已经在为 SSL 或负载均衡部署了反向代理服务端,那么在该层处理压缩通常会带来更好的性能,因为 Node.js 是单线程的。</p>
<p>但是,如果您正在构建自定义服务端并确实想在那里添加压缩中间件,请注意我们建议使用 <code>@polka/compression</code>,因为 SvelteKit 会流式传输响应,而更流行的 <code>compression</code> 包不支持流式传输,使用时可能会导致错误。</p>
<h2 id="环境变量">环境变量</h2>
<p>在 <code>dev</code> 和 <code>preview</code> 模式下,SvelteKit 将从您的 <code>.env</code> 文件(或 <code>.env.local</code>,或 <code>.env.</code>,由 Vite 决定)中读取环境变量。</p>
<p>在生产环境中,不会自动加载 <code>.env</code> 文件。要做到这一点,请在您的项目中安装 <code>dotenv</code>...</p>
<pre><code class="language-bash">npm install dotenv
</code></pre>
<p>...并在运行构建的应用之前调用它:</p>
<pre><code class="language-bash">node +++-r dotenv/config+++ build
</code></pre>
<p>如果您使用的是 Node.js v20.6+,您可以使用 <code>--env-file</code> 标志代替:</p>
<pre><code class="language-bash">node +++--env-file=.env+++ build
</code></pre>
<h3 id="port-host-和-socket_path"><code>PORT</code>, <code>HOST</code> 和 <code>SOCKET_PATH</code></h3>
<p>默认情况下,服务端将使用 <code>0.0.0.0</code> 并在端口 3000 上接受连接。可以使用 <code>PORT</code> 和 <code>HOST</code> 环境变量对其进行自定义:</p>
<pre><code>HOST=127.0.0.1 PORT=4000 node build
</code></pre>
<p>或者,还可以配置服务端在指定的 socket 路径上接受连接。如果您使用 <code>SOCKET_PATH</code> 环境变量来执行此操作,则会忽略 <code>HOST</code> 和 <code>PORT</code> 环境变量。</p>
<pre><code>SOCKET_PATH=/tmp/socket node build
</code></pre>
<h3 id="origin-protocol_header-host_header-和-port_header"><code>ORIGIN</code>, <code>PROTOCOL_HEADER</code>, <code>HOST_HEADER</code> 和 <code>PORT_HEADER</code></h3>
<p>HTTP 并不会为 SvelteKit 提供一种可靠的方法来获取当前请求的 URL。最简单的方式是设置 <code>ORIGIN</code> 环境变量来告诉 SvelteKit 应用在哪里被提供服务:</p>
<pre><code>ORIGIN=https://my.site node build
# 或者,例如本地预览和测试
ORIGIN=http://localhost:3000 node build
</code></pre>
<p>这样,当请求 <code>/stuff</code> 路径名时,就能正确解析到 <code>https://my.site/stuff</code>。或者,您可以指定用于告诉 SvelteKit 关于请求协议和主机的标头,由此 SvelteKit 可以构建 origin URL:</p>
<pre><code>PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build
</code></pre>
<blockquote>
<p>[!NOTE] <code>x-forwarded-proto</code> 和 <code>x-forwarded-host</code> 是事实上的标准请求头,用于在使用反向代理(如负载均衡器和 CDN)时转发原始协议和主机。只有在您的服务端位于受信任的反向代理之后时,才应设置这些变量;否则,客户端可能会伪造这些标头。<br>
如果您在非标准端口托管代理,并且您的反向代理支持 <code>x-forwarded-port</code>,您也可以设置 <code>PORT_HEADER=x-forwarded-port</code>。</p>
</blockquote>
<p>如果 <code>adapter-node</code> 无法正确确定您的部署的 URL,在使用 表单 actions 时可能会出现以下错误:</p>
<blockquote>
<p>[!NOTE] Cross-site POST form submissions are forbidden</p>
</blockquote>
<h3 id="address_header-和-xff_depth"><code>ADDRESS_HEADER</code> 和 <code>XFF_DEPTH</code></h3>
<p>传递给 hooks 和端点的 RequestEvent 对象包含一个 <code>event.getClientAddress()</code> 函数,用于返回客户端的 IP 地址。默认情况下,这是发起连接的 <code>remoteAddress</code>。如果您的服务器位于一个或多个代理(如负载均衡器)之后,这个值将包含最内部代理的 IP 地址,而不是客户端的 IP 地址,因此我们需要指定一个 <code>ADDRESS_HEADER</code> 读取地址:</p>
<pre><code>ADDRESS_HEADER=True-Client-IP node build
</code></pre>
<blockquote>
<p>[!NOTE] 标头很容易被伪造。与 <code>PROTOCOL_HEADER</code> 和 <code>HOST_HEADER</code> 一样,只有在您了解相关风险的情况下才应设置这些变量。</p>
</blockquote>
<p>如果 <code>ADDRESS_HEADER</code> 是 <code>X-Forwarded-For</code>,其值会包含用逗号分隔的 IP 地址列表。此时应通过 <code>XFF_DEPTH</code> 环境变量指定在您的服务器前有多少个受信任的代理。例如,如果有三个受信任的代理,代理 3 会转发客户端原始连接和前两个代理的地址:</p>
<pre><code><client address>, <proxy 1 address>, <proxy 2 address>
</code></pre>
<p>有些指南会告诉您读取最左边的地址,但这样会容易被伪造:</p>
<pre><code><spoofed address>, <client address>, <proxy 1 address>, <proxy 2 address>
</code></pre>
<p>因此,我们从右侧读取,并依据受信任的代理数量进行处理。在这个示例里,我们会使用 <code>XFF_DEPTH=3</code>。</p>
<blockquote>
<p>[!NOTE] 如果您确实需要读取最左侧的地址(并且不在意被伪造)——例如提供地理位置服务,在此情况下,IP 地址“真实性”比“可信度”更重要,您可以在应用中自行检查 <code>x-forwarded-for</code> 标头来实现这一点。</p>
</blockquote>
<h3 id="body_size_limit"><code>BODY_SIZE_LIMIT</code></h3>
<p>接受的最大请求体大小,以字节为单位(包括流式传输时)。</p>
<p>请求体大小也可以使用单位后缀指定,包括千字节(K)、兆字节(M)或千兆字节(G)。例如 <code>512K</code> 或 <code>1M</code>。默认值为 512kb。</p>
<p>您可以设置值为 <code>Infinity</code>(在适配器的旧版本中为 0)来禁用此选项,如果需要更高级的功能,可以在 <code>handle</code> 中自行实现更高级的检查逻辑。</p>
<h3 id="shutdown_timeout"><code>SHUTDOWN_TIMEOUT</code></h3>
<p>接收到 <code>SIGTERM</code> 或 <code>SIGINT</code> 信号后,在强制关闭任何剩余连接之前等待的秒数。默认值是 <code>30</code>。在内部,适配器会调用 <code>closeAllConnections</code>。更多细节请参见 优雅关闭。</p>
<h3 id="idle_timeout"><code>IDLE_TIMEOUT</code></h3>
<p>在使用 systemd 套接字激活时,<code>IDLE_TIMEOUT</code> 用于指定当应用在没有请求的情况下经过多少秒会自动休眠。如果未设置,则应用会一直运行。详见 套接字激活 获取更多信息。</p>
<h2 id="options">Options</h2>
<p>该适配器可以通过多种选项进行配置:</p>
<pre><code class="language-js">// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter({
// 以下为默认选项
out: 'build',
precompress: true,
envPrefix: ''
})
}
};
</code></pre>
<h3 id="out">out</h3>
<p>构建服务端输出的目录,默认为 <code>build</code> —— 也就是说,如果您使用默认目录,执行 <code>node build</code> 将在本地启动服务端。</p>
<h3 id="precompress">precompress</h3>
<p>使用 gzip 和 brotli 对资源和预渲染页面进行预压缩。默认值为 <code>true</code>。</p>
<h3 id="envprefix">envPrefix</h3>
<p>如果您需要更改用于配置部署的环境变量名称(例如,与您无法控制的环境变量冲突),可以指定一个前缀:</p>
<pre><code class="language-js">envPrefix: 'MY_CUSTOM_';
</code></pre>
<pre><code class="language-sh">MY_CUSTOM_HOST=127.0.0.1 \
MY_CUSTOM_PORT=4000 \
MY_CUSTOM_ORIGIN=https://my.site \
node build
</code></pre>
<h2 id="优雅关闭">优雅关闭</h2>
<p>默认情况下,当接收到 <code>SIGTERM</code> 或 <code>SIGINT</code> 信号时,<code>adapter-node</code> 会优雅地关闭 HTTP 服务器。它将:</p>
<ol>
<li>拒绝新的请求(<code>server.close</code>)</li>
<li>等待已经发出的请求但尚未收到响应的请求完成,并在连接变为空闲后关闭连接(<code>server.closeIdleConnections</code>)</li>
<li>最后,在超过 <code>SHUTDOWN_TIMEOUT</code> 秒后强制关闭所有仍处于活动状态的连接(<code>server.closeAllConnections</code>)。</li>
</ol>
<blockquote>
<p>[!NOTE] 如果您想自定义这一行为,您可以使用自定义服务端。</p>
</blockquote>
<p>您还可以监听 <code>sveltekit:shutdown</code> 事件,该事件会在 HTTP 服务器关闭全部连接后触发。与 Node 的 <code>exit</code> 事件不同,<code>sveltekit:shutdown</code> 事件支持异步操作,并且无论服务器是否有未完成的任务(如未关闭的数据库连接),在所有连接都关闭后都会被触发:</p>
<pre><code class="language-js">// @errors: 2304
process.on('sveltekit:shutdown', async (reason) => {
await jobs.stop();
await db.close();
});
</code></pre>
<p>参数 <code>reason</code> 的可能取值包括:</p>
<ul>
<li><code>SIGINT</code> - 关机由 <code>SIGINT</code> 信号触发</li>
<li><code>SIGTERM</code> - 关机由 <code>SIGTERM</code> 信号触发</li>
<li><code>IDLE</code> - 关机由 <code>IDLE_TIMEOUT</code> 触发</li>
</ul>
<h2 id="套接字激活">套接字激活</h2>
<p>当今大多数 Linux 操作系统都使用名为 systemd 的现代进程管理器来启动、运行和管理服务。您可以配置服务器来分配一个套接字,并在需要时按需启动应用。这被称为 套接字激活。在这种情况下,操作系统会向您的应用传递两个环境变量:<code>LISTEN_PID</code> 和 <code>LISTEN_FDS</code>。然后,适配器会在文件描述符 3 上进行监听,该描述符对应您创建的 systemd 套接字单元。</p>
<blockquote>
<p>[!NOTE] 您仍然可以在 systemd 套接字激活中使用 <code>envPrefix</code>。<code>LISTEN_PID</code> 和 <code>LISTEN_FDS</code> 始终无需前缀即可读取。</p>
</blockquote>
<p>要利用套接字激活,请按以下步骤操作:</p>
<ol>
<li>让您的应用作为一个 systemd 服务 运行。它既可以直接运行在主机系统上,也可以在容器内(例如使用 Docker 或 systemd 可移植服务)运行。</li>
</ol>
<p>如果您额外向应用传递一个 <code>IDLE_TIMEOUT</code> 环境变量,它将在没有请求持续 <code>IDLE_TIMEOUT</code> 秒时,优雅地关闭。之后如果有新的请求到来,systemd 将自动重新启动您的应用。</p>
<pre><code class="language-ini">/// file: /etc/systemd/system/myapp.service
Environment=NODE_ENV=production IDLE_TIMEOUT=60
ExecStart=/usr/bin/node /usr/bin/myapp/build
</code></pre>
<ol start="2">
<li>创建一个配套的 socket 单元。适配器仅接受单个 socket。</li>
</ol>
<pre><code class="language-ini">/// file: /etc/systemd/system/myapp.socket
ListenStream=3000
WantedBy=sockets.target
</code></pre>
<ol start="3">
<li>通过运行 <code>sudo systemctl daemon-reload</code> 确保 systemd 识别了这两个单元。然后使用 <code>sudo systemctl enable --now myapp.socket</code> 在启动时启用该 socket 并立即启动它。这样当第一个请求到达 <code>localhost:3000</code> 时,应用将自动启动。</li>
</ol>
<h2 id="自定义服务端">自定义服务端</h2>
<p>该适配器会在您的构建目录中创建两个文件——<code>index.js</code> 和 <code>handler.js</code>。运行 <code>index.js</code>(例如,如果您使用默认 <code>build</code> 目录,那么执行 <code>node build</code>)将会在指定端口上启动服务器。</p>
<p>或者,您可以导入 <code>handler.js</code> 文件,它导出一个兼容 Express、Connect 或 Polka (甚至是内置的 <code>http.createServer</code>)的处理程序,并且设置你自己的服务器:</p>
<pre><code class="language-js">// @errors: 2307 7006
/// file: my-server.js
import { handler } from './build/handler.js';
import express from 'express';
const app = express();
// 添加一个独立于 SvelteKit 应用的路由
app.get('/healthcheck', (req, res) => {
res.end('ok');
});
// 让 SvelteKit 处理其他所有内容,包括提供预渲染页面和静态资源
app.use(handler);
app.listen(3000, () => {
console.log('listening on port 3000');
});
</code></pre>
<h2 id="svelte-中文文档">Svelte 中文文档</h2>
<p>点击查看中文文档 - SvelteKit Node 服务端。</p>
<p>系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p><br><br>
来源:https://www.cnblogs.com/yayujs/p/18785901
頁:
[1]