Skip to main content

Node 服务端

要生成独立的 Node 服务端,请使用 adapter-node

使用方法

使用 npm i -D @sveltejs/adapter-node 安装,然后将适配器添加到您的 svelte.config.js

svelte.config
import import adapteradapter from '@sveltejs/adapter-node';

export default {
	
kit: {
    adapter: any;
}
kit
: {
adapter: anyadapter: import adapteradapter() } };

部署

首先,使用 npm run build 构建您的应用。这将在适配器选项中指定的输出目录(默认为 build)中创建生产服务端。

要运行应用程序,您需要输出目录、项目的 package.jsonnode_modules 中的生产依赖项。生产依赖项可以通过复制 package.jsonpackage-lock.json 然后运行 npm ci --omit dev 来生成(如果您的应用没有任何依赖项,可以跳过此步骤)。然后您可以使用以下命令启动您的应用:

node build

开发依赖项将使用 Rollup 打包到您的应用中。要控制某个包是打包还是外部化,请将其分别放在 package.jsondevDependenciesdependencies 中。

压缩响应

通常您会希望压缩来自服务端的响应。如果您已经在为 SSL 或负载均衡部署了反向代理服务端,那么在该层处理压缩通常会带来更好的性能,因为 Node.js 是单线程的。

但是,如果您正在构建自定义服务端并确实想在那里添加压缩中间件,请注意我们建议使用 @polka/compression,因为 SvelteKit 会流式传输响应,而更流行的 compression 包不支持流式传输,使用时可能会导致错误。

环境变量

devpreview 模式下,SvelteKit 将从您的 .env 文件(或 .env.local,或 .env.[mode]由 Vite 决定)中读取环境变量。

在生产环境中,不会自动加载 .env 文件。要做到这一点,请在您的项目中安装 dotenv...

npm install dotenv

...并在运行构建的应用之前调用它:

node -r dotenv/config build

如果您使用的是 Node.js v20.6+,您可以使用 --env-file 标志代替:

node --env-file=.env build

PORT, HOST 和 SOCKET_PATH

默认情况下,服务端将使用 0.0.0.0 并在端口 3000 上接受连接。可以使用 PORTHOST 环境变量对其进行自定义:

HOST=127.0.0.1 PORT=4000 node build

或者,还可以配置服务端在指定的 socket 路径上接受连接。如果您使用 SOCKET_PATH 环境变量来执行此操作,则会忽略 HOSTPORT 环境变量。

SOCKET_PATH=/tmp/socket node build

ORIGIN, PROTOCOL_HEADER, HOST_HEADER 和 PORT_HEADER

HTTP 并不会为 SvelteKit 提供一种可靠的方法来获取当前请求的 URL。最简单的方式是设置 ORIGIN 环境变量来告诉 SvelteKit 应用在哪里被提供服务:

ORIGIN=https://my.site node build

# 或者,例如本地预览和测试
ORIGIN=http://localhost:3000 node build

这样,当请求 /stuff 路径名时,就能正确解析到 https://my.site/stuff。或者,您可以指定用于告诉 SvelteKit 关于请求协议和主机的标头,由此 SvelteKit 可以构建 origin URL:

PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build

x-forwarded-protox-forwarded-host 是事实上的标准请求头,用于在使用反向代理(如负载均衡器和 CDN)时转发原始协议和主机。只有在您的服务端位于受信任的反向代理之后时,才应设置这些变量;否则,客户端可能会伪造这些标头。
如果您在非标准端口托管代理,并且您的反向代理支持 x-forwarded-port,您也可以设置 PORT_HEADER=x-forwarded-port

如果 adapter-node 无法正确确定您的部署的 URL,在使用 表单 actions 时可能会出现以下错误:

Cross-site POST form submissions are forbidden

ADDRESS_HEADER 和 XFF_DEPTH

传递给 hooks 和端点的 RequestEvent 对象包含一个 event.getClientAddress() 函数,用于返回客户端的 IP 地址。默认情况下,这是发起连接的 remoteAddress。如果您的服务器位于一个或多个代理(如负载均衡器)之后,这个值将包含最内部代理的 IP 地址,而不是客户端的 IP 地址,因此我们需要指定一个 ADDRESS_HEADER 读取地址:

ADDRESS_HEADER=True-Client-IP node build

标头很容易被伪造。与 PROTOCOL_HEADERHOST_HEADER 一样,只有在您了解相关风险的情况下才应设置这些变量。

如果 ADDRESS_HEADERX-Forwarded-For,其值会包含用逗号分隔的 IP 地址列表。此时应通过 XFF_DEPTH 环境变量指定在您的服务器前有多少个受信任的代理。例如,如果有三个受信任的代理,代理 3 会转发客户端原始连接和前两个代理的地址:

<client address>, <proxy 1 address>, <proxy 2 address>

有些指南会告诉您读取最左边的地址,但这样会容易被伪造

<spoofed address>, <client address>, <proxy 1 address>, <proxy 2 address>

因此,我们从右侧读取,并依据受信任的代理数量进行处理。在这个示例里,我们会使用 XFF_DEPTH=3

如果您确实需要读取最左侧的地址(并且不在意被伪造)——例如提供地理位置服务,在此情况下,IP 地址“真实性”比“可信度”更重要,您可以在应用中自行检查 x-forwarded-for 标头来实现这一点。

BODY_SIZE_LIMIT

接受的最大请求体大小,以字节为单位(包括流式传输时)。

请求体大小也可以使用单位后缀指定,包括千字节(K)、兆字节(M)或千兆字节(G)。例如 512K1M。默认值为 512kb。

您可以设置值为 Infinity(在适配器的旧版本中为 0)来禁用此选项,如果需要更高级的功能,可以在 handle 中自行实现更高级的检查逻辑。

SHUTDOWN_TIMEOUT

接收到 SIGTERMSIGINT 信号后,在强制关闭任何剩余连接之前等待的秒数。默认值是 30。在内部,适配器会调用 closeAllConnections。更多细节请参见 优雅关闭

IDLE_TIMEOUT

在使用 systemd 套接字激活时,IDLE_TIMEOUT 用于指定当应用在没有请求的情况下经过多少秒会自动休眠。如果未设置,则应用会一直运行。详见 套接字激活 获取更多信息。

Options

该适配器可以通过多种选项进行配置:

svelte.config
import import adapteradapter from '@sveltejs/adapter-node';

export default {
	
kit: {
    adapter: any;
}
kit
: {
adapter: anyadapter: import adapteradapter({ // 以下为默认选项 out: stringout: 'build', precompress: booleanprecompress: true, envPrefix: stringenvPrefix: '' }) } };

out

构建服务端输出的目录,默认为 build —— 也就是说,如果您使用默认目录,执行 node build 将在本地启动服务端。

precompress

使用 gzip 和 brotli 对资源和预渲染页面进行预压缩。默认值为 true

envPrefix

如果您需要更改用于配置部署的环境变量名称(例如,与您无法控制的环境变量冲突),可以指定一个前缀:

envPrefix: 'MY_CUSTOM_';
MY_CUSTOM_HOST=127.0.0.1 \
MY_CUSTOM_PORT=4000 \
MY_CUSTOM_ORIGIN=https://my.site \
node build

优雅关闭

默认情况下,当接收到 SIGTERMSIGINT 信号时,adapter-node 会优雅地关闭 HTTP 服务器。它将:

  1. 拒绝新的请求(server.close
  2. 等待已经发出的请求但尚未收到响应的请求完成,并在连接变为空闲后关闭连接(server.closeIdleConnections
  3. 最后,在超过 SHUTDOWN_TIMEOUT 秒后强制关闭所有仍处于活动状态的连接(server.closeAllConnections)。

如果您想自定义这一行为,您可以使用自定义服务端

您还可以监听 sveltekit:shutdown 事件,该事件会在 HTTP 服务器关闭全部连接后触发。与 Node 的 exit 事件不同,sveltekit:shutdown 事件支持异步操作,并且无论服务器是否有未完成的任务(如未关闭的数据库连接),在所有连接都关闭后都会被触发:

var process: NodeJS.Processprocess.NodeJS.Process.on(event: string | symbol, listener: (...args: any[]) => void): NodeJS.Process (+12 overloads)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
  console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
//   b
//   a
@sincev0.1.101
@parameventName The name of the event.
@paramlistener The callback function
on
('sveltekit:shutdown', async (reason: anyreason) => {
await jobs.stop(); await db.close(); });

参数 reason 的可能取值包括:

  • SIGINT - 关机由 SIGINT 信号触发
  • SIGTERM - 关机由 SIGTERM 信号触发
  • IDLE - 关机由 IDLE_TIMEOUT 触发

套接字激活

当今大多数 Linux 操作系统都使用名为 systemd 的现代进程管理器来启动、运行和管理服务。您可以配置服务器来分配一个套接字,并在需要时按需启动应用。这被称为 套接字激活。在这种情况下,操作系统会向您的应用传递两个环境变量:LISTEN_PIDLISTEN_FDS。然后,适配器会在文件描述符 3 上进行监听,该描述符对应您创建的 systemd 套接字单元。

您仍然可以在 systemd 套接字激活中使用 envPrefixLISTEN_PIDLISTEN_FDS 始终无需前缀即可读取。

要利用套接字激活,请按以下步骤操作:

  1. 让您的应用作为一个 systemd 服务 运行。它既可以直接运行在主机系统上,也可以在容器内(例如使用 Docker 或 systemd 可移植服务)运行。

如果您额外向应用传递一个 IDLE_TIMEOUT 环境变量,它将在没有请求持续 IDLE_TIMEOUT 秒时,优雅地关闭。之后如果有新的请求到来,systemd 将自动重新启动您的应用。

/etc/systemd/system/myapp
[Service]
Environment=NODE_ENV=production IDLE_TIMEOUT=60
ExecStart=/usr/bin/node /usr/bin/myapp/build
  1. 创建一个配套的 socket 单元。适配器仅接受单个 socket。
/etc/systemd/system/myapp
[Socket]
ListenStream=3000

[Install]
WantedBy=sockets.target
  1. 通过运行 sudo systemctl daemon-reload 确保 systemd 识别了这两个单元。然后使用 sudo systemctl enable --now myapp.socket 在启动时启用该 socket 并立即启动它。这样当第一个请求到达 localhost:3000 时,应用将自动启动。

自定义服务端

该适配器会在您的构建目录中创建两个文件——index.jshandler.js。运行 index.js(例如,如果您使用默认 build 目录,那么执行 node build)将会在指定端口上启动服务器。

或者,您可以导入 handler.js 文件,它导出一个兼容 ExpressConnectPolka (甚至是内置的 http.createServer)的处理程序,并且设置你自己的服务器:

my-server
import { import handlerhandler } from './build/handler.js';
import import expressexpress from 'express';

const const app: anyapp = import expressexpress();

// 添加一个独立于 SvelteKit 应用的路由
const app: anyapp.get('/healthcheck', (req, res) => {

	res: anyres.end('ok');
});

// 让 SvelteKit 处理其他所有内容,包括提供预渲染页面和静态资源
const app: anyapp.use(import handlerhandler);

const app: anyapp.listen(3000, () => {
	var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without calling require('console').

Warning: The global console object’s methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
//   Error: Whoops, something bad happened
//     at [eval]:5:15
//     at Script.runInThisContext (node:vm:132:18)
//     at Object.runInThisContext (node:vm:309:38)
//     at node:internal/process/execution:77:19
//     at [eval]-wrapper:6:22
//     at evalScript (node:internal/process/execution:76:60)
//     at node:internal/main/eval_string:23:3

const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);

myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err

const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
@seesource
console
.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100
log
('listening on port 3000');
});

在 GitHub 编辑此页面