```
# 服务工作者
服务工作者充当代理服务器,处理应用内的网络请求。这使得你的应用可以离线工作,但即使你不需要离线支持(或者由于你正在构建的应用类型而无法实际实现它),通常也值得使用服务工作者通过预先缓存你构建的 JS 和 CSS 来加快导航速度。
¥Service workers act as proxy servers that handle network requests inside your app. This makes it possible to make your app work offline, but even if you don't need offline support (or can't realistically implement it because of the type of app you're building), it's often worth using service workers to speed up navigation by precaching your built JS and CSS.
在 SvelteKit 中,如果你有 `src/service-worker.js` 文件(或 `src/service-worker/index.js`),它将被打包并自动注册。如果需要,你可以更改 [服务工作者的位置](configuration#files)。
¥In SvelteKit, if you have a `src/service-worker.js` file (or `src/service-worker/index.js`) it will be bundled and automatically registered. You can change the [location of your service worker](configuration#files) if you need to.
如果你需要使用自己的逻辑注册服务工作线程或使用其他解决方案,则可以 [禁用自动注册](configuration#serviceWorker)。默认注册如下所示:
¥You can [disable automatic registration](configuration#serviceWorker) if you need to register the service worker with your own logic or use another solution. The default registration looks something like this:
```js
if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('./path/to/service-worker.js');
});
}
```
## 服务工作者内部(Inside the service worker)
¥Inside the service worker
在服务工作线程中,你可以访问 [`$service-worker` module]($service-worker),它为你提供所有静态资源、构建文件和预渲染页面的路径。你还将获得一个应用版本字符串,你可以使用它来创建唯一的缓存名称和部署的 `base` 路径。如果你的 Vite 配置指定 `define`(用于全局变量替换),这将应用于服务工作者以及你的服务器/客户端构建。
¥Inside the service worker you have access to the [`$service-worker` module]($service-worker), which provides you with the paths to all static assets, build files and prerendered pages. You're also provided with an app version string, which you can use for creating a unique cache name, and the deployment's `base` path. If your Vite config specifies `define` (used for global variable replacements), this will be applied to service workers as well as your server/client builds.
以下示例预缓存构建的应用和 `static` 中的任何文件,并在发生所有其他请求时缓存它们。这将使每个页面在访问后都可以离线工作。
¥The following example caches the built app and any files in `static` eagerly, and caches all other requests as they happen. This would make each page work offline once visited.
```js
/// file: src/service-worker.js
// Disables access to DOM typings like `HTMLElement` which are not available
// inside a service worker and instantiates the correct globals
///
///
///
// Ensures that the `$service-worker` import has proper type definitions
///
// Only necessary if you have an import from `$env/static/public`
///
import { build, files, version } from '$service-worker';
// This gives `self` the correct types
const self = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (globalThis.self));
// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;
const ASSETS = [
...build, // the app itself
...files // everything in `static`
];
self.addEventListener('install', (event) => {
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}
event.waitUntil(addFilesToCache());
});
self.addEventListener('activate', (event) => {
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
});
self.addEventListener('fetch', (event) => {
// ignore POST requests etc
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);
if (response) {
return response;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
// if we're offline, fetch can return a value that is not a Response
// instead of throwing - and we can't pass this non-Response to respondWith
if (!(response instanceof Response)) {
throw new Error('invalid response from fetch');
}
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch (err) {
const response = await cache.match(event.request);
if (response) {
return response;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw err;
}
}
event.respondWith(respond());
});
```
> [!NOTE] 缓存时请小心!在某些情况下,过时的数据可能会更糟比离线时不可用的数据更安全。由于浏览器缓存过满会清空,因此在缓存视频文件等大型资源时也应谨慎。
## 开发期间(During development)
¥During development
服务工作者打包用于生产,但不用于开发。因此,只有支持 [服务工作者中的模块](https://web.dev/es-modules-in-sw) 的浏览器才能在开发时使用它们。如果你手动注册服务工作者,则需要在开发中传递 `{ type: 'module' }` 选项:
¥The service worker is bundled for production, but not during development. For that reason, only browsers that support [modules in service workers](https://web.dev/es-modules-in-sw) will be able to use them at dev time. If you are manually registering your service worker, you will need to pass the `{ type: 'module' }` option in development:
```js
import { dev } from '$app/environment';
navigator.serviceWorker.register('/service-worker.js', {
type: dev ? 'module' : 'classic'
});
```
> [!NOTE] 开发过程中,`build` 和 `prerendered` 为空数组
## 其他解决方案(Other solutions)
¥Other solutions
SvelteKit 的服务工作线程实现旨在易于使用,对于大多数用户来说可能是一个很好的解决方案。但是,在 SvelteKit 之外,许多 PWA 应用都利用了 [Workbox](https://web.dev/learn/pwa/workbox) 库。如果你习惯使用 Workbox,你可能更喜欢 [Vite PWA 插件](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html)。
¥SvelteKit's service worker implementation is designed to be easy to work with and is probably a good solution for most users. However, outside of SvelteKit, many PWA applications leverage the [Workbox](https://web.dev/learn/pwa/workbox) library. If you're used to using Workbox you may prefer [Vite PWA plugin](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html).
## 参考(References)
¥References
有关服务工作者的更多一般信息,我们推荐 [MDN 网络文档](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers)。
¥For more general information on service workers, we recommend [the MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers).
# 仅限服务器模块
就像好朋友一样,SvelteKit 会保守你的秘密。在同一个存储库中编写后端和前端时,很容易意外地将敏感数据导入前端代码(例如,包含 API 密钥的环境变量)。SvelteKit 提供了一种完全防止这种情况的方法:单一事实来源
¥Like a good friend, SvelteKit keeps your secrets. When writing your backend and frontend in the same repository, it can be easy to accidentally import sensitive data into your front-end code (environment variables containing API keys, for example). SvelteKit provides a way to prevent this entirely: server-only modules.
## 私有环境变量(Private environment variables)
¥Private environment variables
[`$env/static/private`]($env-static-private) 和 [`$env/dynamic/private`]($env-dynamic-private) 模块只能导入到仅在服务器上运行的模块中,例如 [`hooks.server.js`](hooks#Server-hooks) 或 [`+page.server.js`](routing#page-page.server.js)。
¥The [`$env/static/private`]($env-static-private) and [`$env/dynamic/private`]($env-dynamic-private) modules can only be imported into modules that only run on the server, such as [`hooks.server.js`](hooks#Server-hooks) or [`+page.server.js`](routing#page-page.server.js).
## 仅服务器实用程序(Server-only utilities)
¥Server-only utilities
[`$app/server`]($app-server) 模块包含一个用于从文件系统读取资源的 [`read`]($app-server#read) 函数,同样只能通过在服务器上运行的代码导入。
¥The [`$app/server`]($app-server) module, which contains a [`read`]($app-server#read) function for reading assets from the filesystem, can likewise only be imported by code that runs on the server.
## 你的模块(Your modules)
¥Your modules
你可以通过两种方式使自己的模块仅用于服务器:
¥You can make your own modules server-only in two ways:
* 将 `.server` 添加到文件名,例如 `secrets.server.js`
¥adding `.server` to the filename, e.g. `secrets.server.js`
* 将它们放在 `$lib/server` 中,例如 `$lib/server/secrets.js`
¥placing them in `$lib/server`, e.g. `$lib/server/secrets.js`
## 工作原理(How it works)
¥How it works
任何时候你有面向公众的代码导入仅限服务器的代码(无论是直接还是间接)...
¥Any time you have public-facing code that imports server-only code (whether directly or indirectly)...
```js
// @errors: 7005
/// file: $lib/server/secrets.js
export const atlantisCoordinates = [/* redacted */];
```
```js
// @errors: 2307 7006 7005
/// file: src/routes/utils.js
export { atlantisCoordinates } from '$lib/server/secrets.js';
export const add = (a, b) => a + b;
```
```html
/// file: src/routes/+page.svelte
```
...SvelteKit 将出错:
¥...SvelteKit will error:
```
Cannot import $lib/server/secrets.ts into code that runs in the browser, as this could leak sensitive information.
src/routes/+page.svelte imports
src/routes/utils.js imports
$lib/server/secrets.ts
If you're only using the import as a type, change it to `import type`.
```
即使面向公众的代码 — `src/routes/+page.svelte` — 仅使用 `add` 导出而不是秘密的 `atlantisCoordinates` 导出,秘密代码也可能最终出现在浏览器下载的 JavaScript 中,因此导入链被认为是不安全的。
¥Even though the public-facing code — `src/routes/+page.svelte` — only uses the `add` export and not the secret `atlantisCoordinates` export, the secret code could end up in JavaScript that the browser downloads, and so the import chain is considered unsafe.
此功能也适用于动态导入,甚至是像 `await import(`./${foo}.js`)` 这样的插值导入。
¥This feature also works with dynamic imports, even interpolated ones like ``await import(`./${foo}.js`)``.
> [!NOTE] 像 Vitest 这样的单元测试框架不区分服务器端代码和面向公众的代码。因此,根据 `process.env.TEST === 'true'` 的规定,运行测试时会禁用非法导入检测。
## 进一步阅读(Further reading)
¥Further reading
* [教程:环境变量](/tutorial/kit/env-static-private)
¥[Tutorial: Environment variables](/tutorial/kit/env-static-private)
# 快照
当你从一个页面导航到另一个页面时,短暂的 DOM 状态(如侧边栏上的滚动位置、`
` 元素的内容等)将被丢弃。
¥Ephemeral DOM state — like scroll positions on sidebars, the content of `
` elements and so on — is discarded when you navigate from one page to another.
例如,如果用户填写了表单,但在提交之前离开并返回,或者用户刷新了页面,他们填写的值将会丢失。如果保留该输入很有价值,你可以拍摄 DOM 状态的快照,然后在用户导航回时可以恢复该快照。
¥For example, if the user fills out a form but navigates away and then back before submitting, or if the user refreshes the page, the values they filled in will be lost. In cases where it's valuable to preserve that input, you can take a *snapshot* of DOM state, which can then be restored if the user navigates back.
为此,从 `+page.svelte` 或 `+layout.svelte` 导出具有 `capture` 和 `restore` 方法的 `snapshot` 对象:
¥To do this, export a `snapshot` object with `capture` and `restore` methods from a `+page.svelte` or `+layout.svelte`:
```svelte
```
当你离开此页面时,会在页面更新之前立即调用 `capture` 函数,并且返回的值与浏览器历史记录堆栈中的当前条目相关联。如果你向后导航,则页面更新后会立即使用存储的值调用 `restore` 函数。
¥When you navigate away from this page, the `capture` function is called immediately before the page updates, and the returned value is associated with the current entry in the browser's history stack. If you navigate back, the `restore` function is called with the stored value as soon as the page is updated.
数据必须可序列化为 JSON,以便可以将其持久化到 `sessionStorage`。这允许在重新加载页面时或用户从其他站点导航回来时恢复状态。
¥The data must be serializable as JSON so that it can be persisted to `sessionStorage`. This allows the state to be restored when the page is reloaded, or when the user navigates back from a different site.
> [!NOTE] 避免从 `capture` 返回非常大的对象 - 一旦捕获,对象将在整个会话期间保留在内存中,在极端情况下可能太大而无法持久保存到 `sessionStorage`。
# 浅路由
当你在 SvelteKit 应用中导航时,你会创建历史记录条目。单击后退和前进按钮会遍历此条目列表,重新运行任何 `load` 函数并根据需要替换页面组件。
¥As you navigate around a SvelteKit app, you create *history entries*. Clicking the back and forward buttons traverses through this list of entries, re-running any `load` functions and replacing page components as necessary.
有时,在不导航的情况下创建历史记录条目很有用。例如,你可能希望显示一个模态对话框,用户可以通过返回来关闭该对话框。这在移动设备上特别有价值,因为滑动手势通常比直接与 UI 交互更自然。在这些情况下,与历史记录条目无关的模式可能会令人沮丧,因为用户可能会向后滑动以试图关闭它并发现自己在错误的页面上。
¥Sometimes, it's useful to create history entries *without* navigating. For example, you might want to show a modal dialog that the user can dismiss by navigating back. This is particularly valuable on mobile devices, where swipe gestures are often more natural than interacting directly with the UI. In these cases, a modal that is *not* associated with a history entry can be a source of frustration, as a user may swipe backwards in an attempt to dismiss it and find themselves on the wrong page.
SvelteKit 通过 [`pushState`]($app-navigation#pushState) 和 [`replaceState`]($app-navigation#replaceState) 函数实现了这一点,这些函数允许你将状态与历史记录条目关联而无需导航。例如,要实现历史驱动的模式:
¥SvelteKit makes this possible with the [`pushState`]($app-navigation#pushState) and [`replaceState`]($app-navigation#replaceState) functions, which allow you to associate state with a history entry without navigating. For example, to implement a history-driven modal:
```svelte
{#if page.state.showModal}
history.back()} />
{/if}
```
可以通过向后导航(取消设置 `page.state.showModal`)或以导致 `close` 回调运行的方式与其交互来关闭模式,这将以编程方式向后导航。
¥The modal can be dismissed by navigating back (unsetting `page.state.showModal`) or by interacting with it in a way that causes the `close` callback to run, which will navigate back programmatically.
## API
`pushState` 的第一个参数是 URL,相对于当前 URL。要保留当前 URL,请使用 `''`。
¥The first argument to `pushState` is the URL, relative to the current URL. To stay on the current URL, use `''`.
第二个参数是新的页面状态,可以通过 [页面对象]($app-state#page) 作为 `page.state` 访问。你可以通过声明 [`App.PageState`](types#PageState) 接口(通常在 `src/app.d.ts` 中)使页面状态类型安全。
¥The second argument is the new page state, which can be accessed via the [page object]($app-state#page) as `page.state`. You can make page state type-safe by declaring an [`App.PageState`](types#PageState) interface (usually in `src/app.d.ts`).
要在不创建新历史记录条目的情况下设置页面状态,请使用 `replaceState` 而不是 `pushState`。
¥To set page state without creating a new history entry, use `replaceState` instead of `pushState`.
> [!LEGACY]
> `$app/state` 中的 `page.state` 已在 SvelteKit 2.12 中添加。如果你使用的是早期版本或正在使用 Svelte 4,请改用 `$app/stores` 中的 `$page.state`。
## 为路由加载数据(Loading data for a route)
¥Loading data for a route
当浅路由时,你可能希望在当前页面内渲染另一个 `+page.svelte`。例如,单击照片缩略图可以弹出详细信息视图而无需导航到照片页面。
¥When shallow routing, you may want to render another `+page.svelte` inside the current page. For example, clicking on a photo thumbnail could pop up the detail view without navigating to the photo page.
为了使其工作,你需要加载 `+page.svelte` 期望的数据。一种方便的方法是在 `` 元素的 `click` 处理程序内使用 [`preloadData`]($app-navigation#preloadData)。如果元素(或父元素)使用 [`data-sveltekit-preload-data`](link-options#data-sveltekit-preload-data),则数据已被请求,`preloadData` 将重用该请求。
¥For this to work, you need to load the data that the `+page.svelte` expects. A convenient way to do this is to use [`preloadData`]($app-navigation#preloadData) inside the `click` handler of an ` ` element. If the element (or a parent) uses [`data-sveltekit-preload-data`](link-options#data-sveltekit-preload-data), the data will have already been requested, and `preloadData` will reuse that request.
```svelte
{#each data.thumbnails as thumbnail}
{
if (innerWidth < 640 // bail if the screen is too small
|| e.shiftKey // or the link is opened in a new window
|| e.metaKey || e.ctrlKey // or a new tab (mac: metaKey, win/linux: ctrlKey)
// should also consider clicking with a mouse scroll wheel
) return;
// prevent navigation
e.preventDefault();
const { href } = e.currentTarget;
// run `load` functions (or rather, get the result of the `load` functions
// that are already running because of `data-sveltekit-preload-data`)
const result = await preloadData(href);
if (result.type === 'loaded' && result.status === 200) {
pushState(href, { selected: result.data });
} else {
// something bad happened! try navigating
goto(href);
}
}}
>
{/each}
{#if page.state.selected}
history.back()}>
{/if}
```
## 注意事项(Caveats)
¥Caveats
在服务器端渲染过程中,`page.state` 始终是一个空对象。用户进入的第一个页面也是如此 - 如果用户重新加载页面(或从另一个文档返回),则在他们导航之前不会应用状态。
¥During server-side rendering, `page.state` is always an empty object. The same is true for the first page the user lands on — if the user reloads the page (or returns from another document), state will *not* be applied until they navigate.
浅路由是一项需要 JavaScript 才能运行的功能。使用时要小心,并尝试在 JavaScript 不可用的情况下考虑合理的后备行为。
¥Shallow routing is a feature that requires JavaScript to work. Be mindful when using it and try to think of sensible fallback behavior in case JavaScript isn't available.
# 可观察性
自 2.31 起可用
¥Available since 2.31
有时,你可能需要观察应用的行为,以提高性能或找出棘手错误的根本原因。为了帮助实现此目的,SvelteKit 可以为以下对象发出服务器端 [OpenTelemetry](https://opentelemetry.io) span:
¥Sometimes, you may need to observe how your application is behaving in order to improve performance or find the root cause of a pesky bug. To help with this, SvelteKit can emit server-side [OpenTelemetry](https://opentelemetry.io) spans for the following:
* [`handle`](hooks#Server-hooks-handle) 钩子和 `handle` 函数在 [`sequence`](@sveltejs-kit-hooks#sequence) 中运行(它们将显示为彼此的子函数以及根 `handle` 钩子的子函数)
¥The [`handle`](hooks#Server-hooks-handle) hook and `handle` functions running in a [`sequence`](@sveltejs-kit-hooks#sequence) (these will show up as children of each other and the root `handle` hook)
* 服务器 [`load`](load) 函数和在服务器上运行时的通用 `load` 函数
¥Server [`load`](load) functions and universal `load` functions when they're run on the server
* [表单操作](form-actions)
¥[Form actions](form-actions)
* [远程函数](remote-functions)
¥[Remote functions](remote-functions)
但是,仅仅告诉 SvelteKit 发出 span 并不能让你走得太远 - 你需要将它们实际收集到某个地方才能查看它们。SvelteKit 提供 `src/instrumentation.server.ts` 作为编写跟踪设置和检测代码的位置。它保证在导入应用代码之前运行,前提是你的部署平台支持它并且你的适配器能够识别它。
¥Just telling SvelteKit to emit spans won't get you far, though — you need to actually collect them somewhere to be able to view them. SvelteKit provides `src/instrumentation.server.ts` as a place to write your tracing setup and instrumentation code. It's guaranteed to be run prior to your application code being imported, providing your deployment platform supports it and your adapter is aware of it.
这两项功能目前都处于实验阶段,这意味着它们可能包含错误,并且可能会随时更改,恕不另行通知。你必须在 `svelte.config.js` 中添加 `kit.experimental.tracing.server` 和 `kit.experimental.instrumentation.server` 选项来选择加入:
¥Both of these features are currently experimental, meaning they are likely to contain bugs and are subject to change without notice. You must opt in by adding the `kit.experimental.tracing.server` and `kit.experimental.instrumentation.server` option in your `svelte.config.js`:
```js
/// file: svelte.config.js
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
experimental: {
+++tracing: {
server: true
},
instrumentation: {
server: true
}+++
}
}
};
export default config;
```
> [!NOTE] 跟踪(更重要的是可观察性检测)可能会产生不小的开销。在全力投入跟踪之前,请考虑是否真的需要它,或者仅在开发和预览环境中启用它是否更合适。
## 增强内置跟踪(Augmenting the built-in tracing)
¥Augmenting the built-in tracing
SvelteKit 提供对请求事件中 `root` 跨度和 `current` 跨度的访问。根 span 与你的根 `handle` 函数关联,当前 span 可能与 `handle`、`load`、表单操作或远程函数关联,具体取决于上下文。你可以使用任何你想要记录的属性注释这些 span:
¥SvelteKit provides access to the `root` span and the `current` span on the request event. The root span is the one associated with your root `handle` function, and the current span could be associated with `handle`, `load`, a form action, or a remote function, depending on the context. You can annotate these spans with any attributes you wish to record:
```js
/// file: $lib/authenticate.ts
// @filename: ambient.d.ts
declare module '$lib/auth-core' {
export function getAuthenticatedUser(): Promise<{ id: string }>
}
// @filename: index.js
// ---cut---
import { getRequestEvent } from '$app/server';
import { getAuthenticatedUser } from '$lib/auth-core';
async function authenticate() {
const user = await getAuthenticatedUser();
const event = getRequestEvent();
event.tracing.root.setAttribute('userId', user.id);
}
```
## 开发快速入门(Development quickstart)
¥Development quickstart
要查看你的第一个跟踪,你需要设置一个本地收集器。我们将在此示例中使用 [Jaeger](https://www.jaegertracing.io/docs/getting-started/),因为它们提供了一个易于使用的快速启动命令。当你的收集器在本地运行时:
¥To view your first trace, you'll need to set up a local collector. We'll use [Jaeger](https://www.jaegertracing.io/docs/getting-started/) in this example, as they provide an easy-to-use quickstart command. Once your collector is running locally:
* 在你的 `svelte.config.js` 文件中启用前面提到的实验性标志
¥Turn on the experimental flags mentioned earlier in your `svelte.config.js` file
* 使用你的包管理器安装所需的依赖:
¥Use your package manager to install the dependencies you'll need:
```sh
npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-proto import-in-the-middle
```
* 使用以下命令创建 `src/instrumentation.server.js`:
¥Create `src/instrumentation.server.js` with the following:
```js
/// file: src/instrumentation.server.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { createAddHookMessageChannel } from 'import-in-the-middle';
import { register } from 'node:module';
const { registerOptions } = createAddHookMessageChannel();
register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions);
const sdk = new NodeSDK({
serviceName: 'test-sveltekit-tracing',
traceExporter: new OTLPTraceExporter(),
instrumentations: [getNodeAutoInstrumentations()]
});
sdk.start();
```
现在,服务器端请求将开始生成跟踪,你可以在 Jaeger 的 Web 控制台中以 [localhost:16686](http://localhost:16686) 为单位查看这些跟踪。
¥Now, server-side requests will begin generating traces, which you can view in Jaeger's web console at [localhost:16686](http://localhost:16686).
## `@opentelemetry/api`
SvelteKit 使用 `@opentelemetry/api` 生成其 span。这被声明为可选的对等依赖,以便不需要跟踪的用户不会看到对安装大小或运行时性能的影响。在大多数情况下,如果你将应用配置为收集 SvelteKit 的 span,你最终会安装像 `@opentelemetry/sdk-node` 或 `@vercel/otel` 这样的库,而这些库又依赖于 `@opentelemetry/api`,而 `@opentelemetry/api` 也会满足 SvelteKit 的依赖。如果你看到 SvelteKit 报错,提示你找不到 `@opentelemetry/api`,则可能只是因为你尚未设置跟踪收集。如果你已完成此操作但仍然看到错误,你可以自行安装 `@opentelemetry/api`。
¥SvelteKit uses `@opentelemetry/api` to generate its spans. This is declared as an optional peer dependency so that users not needing traces see no impact on install size or runtime performance. In most cases, if you're configuring your application to collect SvelteKit's spans, you'll end up installing a library like `@opentelemetry/sdk-node` or `@vercel/otel`, which in turn depend on `@opentelemetry/api`, which will satisfy SvelteKit's dependency as well. If you see an error from SvelteKit telling you it can't find `@opentelemetry/api`, it may just be because you haven't set up your trace collection yet. If you *have* done that and are still seeing the error, you can install `@opentelemetry/api` yourself.
# 打包
你可以使用 SvelteKit 构建应用以及组件库,使用 `@sveltejs/package` 包(`npx sv create` 有一个选项可以为你设置)。
¥You can use SvelteKit to build apps as well as component libraries, using the `@sveltejs/package` package (`npx sv create` has an option to set this up for you).
当你创建应用时,`src/routes` 的内容是面向公众的内容;[`src/lib`]($lib) 包含你的应用的内部库。
¥When you're creating an app, the contents of `src/routes` is the public-facing stuff; [`src/lib`]($lib) contains your app's internal library.
组件库具有与 SvelteKit 应用完全相同的结构,只是 `src/lib` 是面向公众的部分,而根 `package.json` 用于发布包。`src/routes` 可能是随库一起提供的文档或演示站点,也可能只是你在开发过程中使用的沙箱。
¥A component library has the exact same structure as a SvelteKit app, except that `src/lib` is the public-facing bit, and your root `package.json` is used to publish the package. `src/routes` might be a documentation or demo site that accompanies the library, or it might just be a sandbox you use during development.
从 `@sveltejs/package` 运行 `svelte-package` 命令将获取 `src/lib` 的内容并生成包含以下内容的 `dist` 目录(可以是 [configured](#Options)):
¥Running the `svelte-package` command from `@sveltejs/package` will take the contents of `src/lib` and generate a `dist` directory (which can be [configured](#Options)) containing the following:
* `src/lib` 中的所有文件。Svelte 组件将被预处理,TypeScript 文件将被转换为 JavaScript。
¥All the files in `src/lib`. Svelte components will be preprocessed, TypeScript files will be transpiled to JavaScript.
* 为 Svelte、JavaScript 和 TypeScript 文件生成的类型定义(`d.ts` 文件)。你需要为此安装 `typescript >= 4.0.0`。类型定义放在其实现旁边,手写的 `d.ts` 文件按原样复制。你可以使用 [禁用生成](#Options),但我们强烈建议不要这样做 - 使用你的库的人可能会使用 TypeScript,他们需要这些类型定义文件。
¥Type definitions (`d.ts` files) which are generated for Svelte, JavaScript and TypeScript files. You need to install `typescript >= 4.0.0` for this. Type definitions are placed next to their implementation, hand-written `d.ts` files are copied over as is. You can [disable generation](#Options), but we strongly recommend against it — people using your library might use TypeScript, for which they require these type definition files.
> [!NOTE] `@sveltejs/package` 版本 1 生成了一个 `package.json`。现在不再如此,它将使用你项目中的 `package.json` 并验证其是否正确。如果你仍在使用版本 1,请参阅 [this PR](https://github.com/sveltejs/kit/pull/8922) 中的迁移说明。
## package.json 的剖析(Anatomy of a package.json)
¥Anatomy of a package.json
由于你现在正在构建一个供公众使用的库,因此你的 `package.json` 的内容将变得更加重要。通过它,你可以配置包的入口点、哪些文件发布到 npm 以及你的库具有哪些依赖。让我们逐一介绍最重要的字段。
¥Since you're now building a library for public use, the contents of your `package.json` will become more important. Through it, you configure the entry points of your package, which files are published to npm, and which dependencies your library has. Let's go through the most important fields one by one.
### name
这是你的包的名称。它将可供其他人使用该名称安装,并在 `https://npmjs.com/package/` 上可见。
¥This is the name of your package. It will be available for others to install using that name, and visible on `https://npmjs.com/package/`.
```json
{
"name": "your-library"
}
```
阅读有关它的更多信息 [此处](https://www.npmjs.com/package/cli/v9/configuring-npm/package-json#name)。
¥Read more about it [here](https://www.npmjs.com/package/cli/v9/configuring-npm/package-json#name).
### license
每个包都应该有一个许可证字段,以便人们知道如何使用它。`MIT` 是一种非常流行的许可证,它在分发和重用方面也非常宽松,没有保证。
¥Every package should have a license field so people know how they are allowed to use it. A very popular license which is also very permissive in terms of distribution and reuse without warranty is `MIT`.
```json
{
"license": "MIT"
}
```
阅读有关它的更多信息 [此处](https://www.npmjs.com/package/cli/v9/configuring-npm/package-json#license)。请注意,你还应该在包中包含一个 `LICENSE` 文件。
¥Read more about it [here](https://www.npmjs.com/package/cli/v9/configuring-npm/package-json#license). Note that you should also include a `LICENSE` file in your package.
### files
这会告诉 npm 它将打包哪些文件并上传到 npm。它应该包含你的输出文件夹(默认为 `dist`)。你的 `package.json`、`README` 和 `LICENSE` 将始终包含在内,因此你无需指定它们。
¥This tells npm which files it will pack up and upload to npm. It should contain your output folder (`dist` by default). Your `package.json` and `README` and `LICENSE` will always be included, so you don't need to specify them.
```json
{
"files": ["dist"]
}
```
要排除不必要的文件(例如单元测试或仅从 `src/routes` 导入的模块等),你可以将它们添加到 `.npmignore` 文件中。这将导致更小的软件包,安装速度更快。
¥To exclude unnecessary files (such as unit tests, or modules that are only imported from `src/routes` etc) you can add them to an `.npmignore` file. This will result in smaller packages that are faster to install.
阅读有关它的更多信息 [此处](https://www.npmjs.com/package/cli/v9/configuring-npm/package-json#files)。
¥Read more about it [here](https://www.npmjs.com/package/cli/v9/configuring-npm/package-json#files).
### exports
`"exports"` 字段包含包的入口点。如果你通过 `npx sv create` 设置了一个新的库项目,它将被设置为单个导出,即包根:
¥The `"exports"` field contains the package's entry points. If you set up a new library project through `npx sv create`, it's set to a single export, the package root:
```json
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js"
}
}
}
```
这会告诉打包器和工具,你的包只有一个入口点,即根,所有内容都应该通过它导入,如下所示:
¥This tells bundlers and tooling that your package only has one entry point, the root, and everything should be imported through that, like this:
```js
// @errors: 2307
import { Something } from 'your-library';
```
`types` 和 `svelte` 的键是 [导出条件](https://nodejs.org/api/packages.html#conditional-exports)。当它们查找 `your-library` 导入时,它们会告诉工具要导入哪个文件:
¥The `types` and `svelte` keys are [export conditions](https://nodejs.org/api/packages.html#conditional-exports). They tell tooling what file to import when they look up the `your-library` import:
* TypeScript 看到 `types` 条件并查找类型定义文件。如果你不发布类型定义,请忽略此条件。
¥TypeScript sees the `types` condition and looks up the type definition file. If you don't publish type definitions, omit this condition.
* Svelte 感知工具可以看到 `svelte` 条件并知道这是一个 Svelte 组件库。如果你发布的库不导出任何 Svelte 组件,并且也可以在非 Svelte 项目中工作(例如 Svelte 存储库),则可以用 `default` 替换此条件。
¥Svelte-aware tooling sees the `svelte` condition and knows this is a Svelte component library. If you publish a library that does not export any Svelte components and that could also work in non-Svelte projects (for example a Svelte store library), you can replace this condition with `default`.
> [!NOTE] `@sveltejs/package` 的早期版本还添加了 `package.json` 导出功能。这不再是模板的一部分,因为现在所有工具都可以处理未明确导出的 `package.json`。
你可以根据自己的喜好调整 `exports` 并提供更多入口点。例如,如果你想要直接公开 `src/lib/Foo.svelte` 组件,而不是重新导出组件的 `src/lib/index.js` 文件,则可以创建以下导出映射...
¥You can adjust `exports` to your liking and provide more entry points. For example, if instead of a `src/lib/index.js` file that re-exported components you wanted to expose a `src/lib/Foo.svelte` component directly, you could create the following export map...
```json
{
"exports": {
"./Foo.svelte": {
"types": "./dist/Foo.svelte.d.ts",
"svelte": "./dist/Foo.svelte"
}
}
}
```
...并且你的库的消费者可以像这样导入组件:
¥...and a consumer of your library could import the component like so:
```js
// @filename: ambient.d.ts
declare module 'your-library/Foo.svelte';
// @filename: index.js
// ---cut---
import Foo from 'your-library/Foo.svelte';
```
> [!NOTE] 请注意,如果你提供类型定义,则执行此操作需要格外小心。阅读有关 [here](#TypeScript) 警告的更多信息。
一般来说,导出映射的每个键都是用户必须用来从包中导入某些内容的路径,值是将要导入的文件的路径或导出条件映射,而导出条件映射又包含这些文件路径。
¥In general, each key of the exports map is the path the user will have to use to import something from your package, and the value is the path to the file that will be imported or a map of export conditions which in turn contains these file paths.
阅读有关 `exports` [此处](https://nodejs.org/docs/latest-v18.x/api/packages.html#package-entry-points) 的更多信息。
¥Read more about `exports` [here](https://nodejs.org/docs/latest-v18.x/api/packages.html#package-entry-points).
### svelte
这是一个遗留字段,使工具能够识别 Svelte 组件库。使用 `svelte` [导出条件](#Anatomy-of-a-package.json-exports) 时不再需要它,但为了向后兼容尚不知道导出条件的过时工具,最好保留它。它应该指向你的根入口点。
¥This is a legacy field that enabled tooling to recognise Svelte component libraries. It's no longer necessary when using the `svelte` [export condition](#Anatomy-of-a-package.json-exports), but for backwards compatibility with outdated tooling that doesn't yet know about export conditions it's good to keep it around. It should point towards your root entry point.
```json
{
"svelte": "./dist/index.js"
}
```
### sideEffects
`package.json` 中的 `sideEffects` 字段由打包器用于确定模块是否可能包含具有副作用的代码。如果模块在导入时所做的更改可从模块外部的其他脚本观察到,则该模块被认为具有副作用。例如,副作用包括修改全局变量或内置 JavaScript 对象的原型。由于副作用可能会影响应用其他部分的行为,因此无论应用中是否使用它们的导出,这些文件/模块都将包含在最终包中。避免代码中的副作用是一种最佳实践。
¥The `sideEffects` field in `package.json` is used by bundlers to determine if a module may contain code that has side effects. A module is considered to have side effects if it makes changes that are observable from other scripts outside the module when it's imported. For example, side effects include modifying global variables or the prototype of built-in JavaScript objects. Because a side effect could potentially affect the behavior of other parts of the application, these files/modules will be included in the final bundle regardless of whether their exports are used in the application. It is a best practice to avoid side effects in your code.
在 `package.json` 中设置 `sideEffects` 字段可以帮助打包器更积极地从最终打包包中消除未使用的导出,这一过程称为 tree-shaking。这会导致更小、更高效的包。不同的打包器以不同的方式处理 `sideEffects`。虽然对于 Vite 来说不是必需的,但我们建议库声明所有 CSS 文件都有副作用,以便你的库将是 [与 webpack 兼容](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free)。这是新创建的项目附带的配置:
¥Setting the `sideEffects` field in `package.json` can help the bundler to be more aggressive in eliminating unused exports from the final bundle, a process known as tree-shaking. This results in smaller and more efficient bundles. Different bundlers handle `sideEffects` in various manners. While not necessary for Vite, we recommend that libraries state that all CSS files have side effects so that your library will be [compatible with webpack](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free). This is the configuration that comes with newly created projects:
```json
/// file: package.json
{
"sideEffects": ["**/*.css"]
}
```
> [!NOTE] 如果你库中的脚本具有副作用,请确保更新 `sideEffects` 字段。在新创建的项目中,所有脚本默认标记为无副作用。如果将具有副作用的文件错误地标记为无副作用,则可能导致功能中断。
如果你的包中有具有副作用的文件,你可以在数组中指定它们:
¥If your package has files with side effects, you can specify them in an array:
```json
/// file: package.json
{
"sideEffects": [
"**/*.css",
"./dist/sideEffectfulFile.js"
]
}
```
这将仅将指定的文件视为具有副作用。
¥This will treat only the specified files as having side effects.
## TypeScript
即使你自己不使用 TypeScript,你也应该为你的库提供类型定义,以便使用你的库的人获得正确的智能感知。`@sveltejs/package` 使生成类型的过程对你来说大多不透明。默认情况下,在打包库时,会自动为 JavaScript、TypeScript 和 Svelte 文件生成类型定义。你需要确保的是 [exports](#Anatomy-of-a-package.json-exports) 映射中的 `types` 条件指向正确的文件。通过 `npx sv create` 初始化库项目时,将自动为根导出设置。
¥You should ship type definitions for your library even if you don't use TypeScript yourself so that people who do get proper intellisense when using your library. `@sveltejs/package` makes the process of generating types mostly opaque to you. By default, when packaging your library, type definitions are auto-generated for JavaScript, TypeScript and Svelte files. All you need to ensure is that the `types` condition in the [exports](#Anatomy-of-a-package.json-exports) map points to the correct files. When initialising a library project through `npx sv create`, this is automatically setup for the root export.
但是,如果你有除根导出之外的其他内容(例如提供 `your-library/foo` 导入),则需要特别注意提供类型定义。不幸的是,TypeScript 默认不会解决像 `{ "./foo": { "types": "./dist/foo.d.ts", ... }}` 这样的导出的 `types` 条件。相反,它将搜索相对于库根的 `foo.d.ts`(即 `your-library/foo.d.ts` 而不是 `your-library/dist/foo.d.ts`)。要解决此问题,你有两个选择:
¥If you have something else than a root export however — for example providing a `your-library/foo` import — you need to take additional care for providing type definitions. Unfortunately, TypeScript by default will *not* resolve the `types` condition for an export like `{ "./foo": { "types": "./dist/foo.d.ts", ... }}`. Instead, it will search for a `foo.d.ts` relative to the root of your library (i.e. `your-library/foo.d.ts` instead of `your-library/dist/foo.d.ts`). To fix this, you have two options:
第一个选项是要求使用你的库的人将他们的 `tsconfig.json`(或 `jsconfig.json`)中的 `moduleResolution` 选项设置为 `bundler`(自 TypeScript 5 开始可用,这是未来最佳和推荐的选项)、`node16` 或 `nodenext`。这选择 TypeScript 实际查看导出映射并正确解析类型。
¥The first option is to require people using your library to set the `moduleResolution` option in their `tsconfig.json` (or `jsconfig.json`) to `bundler` (available since TypeScript 5, the best and recommended option in the future), `node16` or `nodenext`. This opts TypeScript into actually looking at the exports map and resolving the types correctly.
第二种选择是(滥用)使用 TypeScript 中的 `typesVersions` 功能来连接类型。这是 `package.json` 中的一个字段,TypeScript 使用它根据 TypeScript 版本检查不同的类型定义,并且还包含一个路径映射功能。我们利用该路径映射功能来获得我们想要的东西。对于上面提到的 `foo` 导出,相应的 `typesVersions` 如下所示:
¥The second option is to (ab)use the `typesVersions` feature from TypeScript to wire up the types. This is a field inside `package.json` TypeScript uses to check for different type definitions depending on the TypeScript version, and also contains a path mapping feature for that. We leverage that path mapping feature to get what we want. For the mentioned `foo` export above, the corresponding `typesVersions` looks like this:
```json
{
"exports": {
"./foo": {
"types": "./dist/foo.d.ts",
"svelte": "./dist/foo.js"
}
},
"typesVersions": {
">4.0": {
"foo": ["./dist/foo.d.ts"]
}
}
}
```
`>4.0` 告诉 TypeScript 检查内部映射,如果使用的 TypeScript 版本大于 4(实际上应该始终如此)。内部映射告诉 TypeScript,`your-library/foo` 的类型在 `./dist/foo.d.ts` 中找到,这基本上复制了 `exports` 条件。你还可以使用 `*` 作为通配符,以便一次提供许多类型定义而无需重复。请注意,如果你选择加入 `typesVersions`,则必须通过它声明所有类型导入,包括根导入(定义为 `"index.d.ts": [..]`)。
¥`>4.0` tells TypeScript to check the inner map if the used TypeScript version is greater than 4 (which should in practice always be true). The inner map tells TypeScript that the typings for `your-library/foo` are found within `./dist/foo.d.ts`, which essentially replicates the `exports` condition. You also have `*` as a wildcard at your disposal to make many type definitions at once available without repeating yourself. Note that if you opt into `typesVersions` you have to declare all type imports through it, including the root import (which is defined as `"index.d.ts": [..]`).
你可以阅读有关该功能 [此处](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions) 的更多信息。
¥You can read more about that feature [here](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions).
## 最佳实践(Best practices)
¥Best practices
你应该避免在你的软件包中使用 SvelteKit 特定的模块(如 `$app/environment`),除非你打算让它们仅供其他 SvelteKit 项目使用。例如你可以使用 `import { BROWSER } from 'esm-env'`([参见 esm-env 文档](https://github.com/benmccann/esm-env)),而不是使用 `import { browser } from '$app/environment'`。你可能还希望将当前 URL 或导航操作等内容作为 prop 传递,而不是直接依赖 `$app/state`、`$app/navigation` 等。以这种更通用的方式编写应用还将使设置测试、UI 演示等工具变得更加容易。
¥You should avoid using SvelteKit-specific modules like `$app/environment` in your packages unless you intend for them to only be consumable by other SvelteKit projects. E.g. rather than using `import { browser } from '$app/environment'` you could use `import { BROWSER } from 'esm-env'` ([see esm-env docs](https://github.com/benmccann/esm-env)). You may also wish to pass in things like the current URL or a navigation action as a prop rather than relying directly on `$app/state`, `$app/navigation`, etc. Writing your app in this more generic fashion will also make it easier to setup tools for testing, UI demos and so on.
确保通过 `svelte.config.js`(而不是 `vite.config.js` 或 `tsconfig.json`)添加 [aliases](configuration#alias),以便它们由 `svelte-package` 处理。
¥Ensure that you add [aliases](configuration#alias) via `svelte.config.js` (not `vite.config.js` or `tsconfig.json`), so that they are processed by `svelte-package`.
你应该仔细考虑对软件包所做的更改是错误修复、新功能还是重大更改,并相应地更新软件包版本。请注意,如果你从现有库中删除 `exports` 中的任何路径或其中的任何 `export` 条件,则应将其视为重大更改。
¥You should think carefully about whether or not the changes you make to your package are a bug fix, a new feature, or a breaking change, and update the package version accordingly. Note that if you remove any paths from `exports` or any `export` conditions inside them from your existing library, that should be regarded as a breaking change.
```json
{
"exports": {
".": {
"types": "./dist/index.d.ts",
// changing `svelte` to `default` is a breaking change:
--- "svelte": "./dist/index.js"---
+++ "default": "./dist/index.js"+++
},
// removing this is a breaking change:
--- "./foo": {
"types": "./dist/foo.d.ts",
"svelte": "./dist/foo.js",
"default": "./dist/foo.js"
},---
// adding this is ok:
+++ "./bar": {
"types": "./dist/bar.d.ts",
"svelte": "./dist/bar.js",
"default": "./dist/bar.js"
}+++
}
}
```
## 源映射(Source maps)
¥Source maps
你可以通过在 `tsconfig.json` 中设置 `"declarationMap": true` 来创建所谓的声明映射(`d.ts.map` 文件)。这将允许 VS Code 等编辑器在使用“转到定义”等功能时转到原始 `.ts` 或 `.svelte` 文件。这意味着你还需要将源文件与 dist 文件夹一起发布,以便声明文件内的相对路径指向磁盘上的文件。假设你拥有 Svelte CLI 建议的所有库代码都在 `src/lib` 中,这就像在 `package.json` 中将 `src/lib` 添加到 `files` 一样简单:
¥You can create so-called declaration maps (`d.ts.map` files) by setting `"declarationMap": true` in your `tsconfig.json`. This will allow editors such as VS Code to go to the original `.ts` or `.svelte` file when using features like *Go to Definition*. This means you also need to publish your source files alongside your dist folder in a way that the relative path inside the declaration files leads to a file on disk. Assuming that you have all your library code inside `src/lib` as suggested by Svelte's CLI, this is as simple as adding `src/lib` to `files` in your `package.json`:
```json
{
"files": [
"dist",
"!dist/**/*.test.*",
"!dist/**/*.spec.*",
+++"src/lib",
"!src/lib/**/*.test.*",
"!src/lib/**/*.spec.*"+++
]
}
```
## 选项(Options)
¥Options
`svelte-package` 接受以下选项:
¥`svelte-package` accepts the following options:
* `-w`/`--watch` — 监视 `src/lib` 中的文件是否有变化并重建包
¥`-w`/`--watch` — watch files in `src/lib` for changes and rebuild the package
* `-i`/`--input` — 包含包的所有文件的输入目录。默认为 `src/lib`
¥`-i`/`--input` — the input directory which contains all the files of the package. Defaults to `src/lib`
* `-o`/`--output` — 处理后的文件写入的输出目录。你的 `package.json` 的 `exports` 应该指向里面的文件,并且 `files` 数组应该包含该文件夹。默认为 `dist`
¥`-o`/`--output` — the output directory where the processed files are written to. Your `package.json`'s `exports` should point to files inside there, and the `files` array should include that folder. Defaults to `dist`
* `-p`/`--preserve-output` — 防止在打包前删除输出目录。默认为 `false`,这意味着输出目录将首先被清空。
¥`-p`/`--preserve-output` — prevent deletion of the output directory before packaging. Defaults to `false`, which means that the output directory will be emptied first
* `-t`/`--types` — 是否创建类型定义(`d.ts` 文件)。我们强烈建议这样做,因为它可以提高生态系统库的质量。默认为 `true`
¥`-t`/`--types` — whether or not to create type definitions (`d.ts` files). We strongly recommend doing this as it fosters ecosystem library quality. Defaults to `true`
* `--tsconfig` - tsconfig 或 jsconfig 的路径。未提供时,在工作区路径中搜索下一个上层 tsconfig/jsconfig。
¥`--tsconfig` - the path to a tsconfig or jsconfig. When not provided, searches for the next upper tsconfig/jsconfig in the workspace path.
## 发布(Publishing)
¥Publishing
要发布生成的包:
¥To publish the generated package:
```sh
npm publish
```
## 注意事项(Caveats)
¥Caveats
所有相关文件导入都需要完全指定,并遵守 Node 的 ESM 算法。这意味着对于像 `src/lib/something/index.js` 这样的文件,你必须包含带有扩展名的文件名:
¥All relative file imports need to be fully specified, adhering to Node's ESM algorithm. This means that for a file like `src/lib/something/index.js`, you must include the filename with the extension:
```js
// @errors: 2307
import { something } from './something+++/index.js+++';
```
如果你使用的是 TypeScript,则需要以相同的方式导入 `.ts` 文件,但使用 `.js` 文件结尾,而不是 `.ts` 文件结尾。(这是我们无法控制的 TypeScript 设计决策。)在你的 `tsconfig.json` 或 `jsconfig.json` 中设置 `"moduleResolution": "NodeNext"` 将帮助你实现这一点。
¥If you are using TypeScript, you need to import `.ts` files the same way, but using a `.js` file ending, *not* a `.ts` file ending. (This is a TypeScript design decision outside our control.) Setting `"moduleResolution": "NodeNext"` in your `tsconfig.json` or `jsconfig.json` will help you with this.
除 Svelte 文件(预处理)和 TypeScript 文件(转换为 JavaScript)之外的所有文件都按原样复制。
¥All files except Svelte files (preprocessed) and TypeScript files (transpiled to JavaScript) are copied across as-is.
# Auth
Auth 是指身份验证和授权,这是构建 Web 应用时的常见需求。身份验证意味着根据用户提供的凭据验证用户是否是他们所说的那个人。授权意味着确定他们可以采取哪些操作。
¥Auth refers to authentication and authorization, which are common needs when building a web application. Authentication means verifying that the user is who they say they are based on their provided credentials. Authorization means determining which actions they are allowed to take.
## 会话与令牌(Sessions vs tokens)
¥Sessions vs tokens
在用户提供其凭据(例如用户名和密码)后,我们希望允许他们使用该应用,而无需在未来的请求中再次提供其凭据。用户通常在后续请求中使用会话标识符或签名令牌(例如 JSON Web 令牌 (JWT))进行身份验证。
¥After the user has provided their credentials such as a username and password, we want to allow them to use the application without needing to provide their credentials again for future requests. Users are commonly authenticated on subsequent requests with either a session identifier or signed token such as a JSON Web Token (JWT).
会话 ID 通常存储在数据库中。它们可以立即撤销,但需要在每个请求上进行数据库查询。
¥Session IDs are most commonly stored in a database. They can be immediately revoked, but require a database query to be made on each request.
相比之下,JWT 通常不会针对数据存储进行检查,这意味着它们不能立即被撤销。此方法的优点是可以改善延迟并减少数据存储区的负载。
¥In contrast, JWT generally are not checked against a datastore, which means they cannot be immediately revoked. The advantage of this method is improved latency and reduced load on your datastore.
## 集成点(Integration points)
¥Integration points
可以在 [服务器钩子](hooks#Server-hooks) 内部检查 Auth [cookies](@sveltejs-kit#Cookies)。如果发现用户与提供的凭据匹配,则可以将用户信息存储在 [`locals`](hooks#Server-hooks-locals) 中。
¥Auth [cookies](@sveltejs-kit#Cookies) can be checked inside [server hooks](hooks#Server-hooks). If a user is found matching the provided credentials, the user information can be stored in [`locals`](hooks#Server-hooks-locals).
## 指南(Guides)
¥Guides
[Lucia](https://lucia-auth.com/) 是基于会话的 Web 应用身份验证的一个很好的参考。它包含用于在 SvelteKit 和其他 JS 项目中实现基于会话的身份验证的示例代码片段和项目。你可以在创建新项目时使用 `npx sv create` 将遵循 Lucia 指南的代码添加到你的项目中,也可以为现有项目使用 `npx sv add lucia`。
¥[Lucia](https://lucia-auth.com/) is a good reference for session-based web app auth. It contains example code snippets and projects for implementing session-based auth within SvelteKit and other JS projects. You can add code which follows the Lucia guide to your project with `npx sv create` when creating a new project or `npx sv add lucia` for an existing project.
身份验证系统与 Web 框架紧密耦合,因为大多数代码都用于验证用户输入、处理错误以及将用户引导到适当的下一个页面。因此,许多通用 JS 身份验证库都包含一个或多个 Web 框架。因此,许多用户会发现遵循 SvelteKit 特定的指南(例如 [Lucia](https://lucia-auth.com/) 中的示例)比在他们的项目中拥有多个 Web 框架更可取。
¥An auth system is tightly coupled to a web framework because most of the code lies in validating user input, handling errors, and directing users to the appropriate next page. As a result, many of the generic JS auth libraries include one or more web frameworks within them. For this reason, many users will find it preferrable to follow a SvelteKit-specific guide such as the examples found in [Lucia](https://lucia-auth.com/) rather than having multiple web frameworks inside their project.
# 性能
开箱即用,SvelteKit 做了很多工作来使你的应用尽可能高效:
¥Out of the box, SvelteKit does a lot of work to make your applications as performant as possible:
* 代码分割,这样只加载当前页面所需的代码
¥Code-splitting, so that only the code you need for the current page is loaded
* 资源预加载,以便防止 'waterfalls'(请求其他文件的文件)
¥Asset preloading, so that 'waterfalls' (of files requesting other files) are prevented
* 文件哈希,以便你的资源可以永久缓存
¥File hashing, so that your assets can be cached forever
* 请求合并,以便从单独的服务器 `load` 功能获取的数据被分组为单个 HTTP 请求
¥Request coalescing, so that data fetched from separate server `load` functions is grouped into a single HTTP request
* 并行加载,以便单独的通用 `load` 函数同时获取数据
¥Parallel loading, so that separate universal `load` functions fetch data simultaneously
* 数据内联,以便在服务器渲染期间使用 `fetch` 发出的请求可以在浏览器中重播,而无需发出新请求
¥Data inlining, so that requests made with `fetch` during server rendering can be replayed in the browser without issuing a new request
* 保守的失效,以便 `load` 函数仅在必要时重新运行
¥Conservative invalidation, so that `load` functions are only re-run when necessary
* 预渲染(如果需要,可以根据每个路由进行配置),以便可以立即提供没有动态数据的页面
¥Prerendering (configurable on a per-route basis, if necessary) so that pages without dynamic data can be served instantaneously
* 链接预加载,以便热切期待客户端导航的数据和代码要求
¥Link preloading, so that data and code requirements for a client-side navigation are eagerly anticipated
尽管如此,我们(目前)还不能消除所有导致速度缓慢的根源。为了获得最佳性能,你应该注意以下提示。
¥Nevertheless, we can't (yet) eliminate all sources of slowness. To eke out maximum performance, you should be mindful of the following tips.
## 诊断问题(Diagnosing issues)
¥Diagnosing issues
Google 的 [PageSpeed Insights](https://pagespeed.web.dev/) 和(用于更高级的分析)[WebPageTest](https://www.webpagetest.org/) 是了解已部署到互联网的网站的性能特性的绝佳方法。
¥Google's [PageSpeed Insights](https://pagespeed.web.dev/) and (for more advanced analysis) [WebPageTest](https://www.webpagetest.org/) are excellent ways to understand the performance characteristics of a site that is already deployed to the internet.
你的浏览器还包含有用的开发者工具,用于分析你的网站,无论是部署还是本地运行:
¥Your browser also includes useful developer tools for analysing your site, whether deployed or running locally:
* Chrome - [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview#devtools)、[网络](https://developer.chrome.com/docs/devtools/network) 和 [性能](https://developer.chrome.com/docs/devtools/performance) devtools
¥Chrome - [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview#devtools), [Network](https://developer.chrome.com/docs/devtools/network), and [Performance](https://developer.chrome.com/docs/devtools/performance) devtools
* Edge - [Lighthouse](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/lighthouse/lighthouse-tool)、[网络](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/network/) 和 [性能](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/evaluate-performance/) devtools
¥Edge - [Lighthouse](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/lighthouse/lighthouse-tool), [Network](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/network/), and [Performance](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/evaluate-performance/) devtools
* Firefox - [网络](https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/) 和 [性能](https://hacks.mozilla.org/2022/03/performance-tool-in-firefox-devtools-reloaded/) devtools
¥Firefox - [Network](https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/) and [Performance](https://hacks.mozilla.org/2022/03/performance-tool-in-firefox-devtools-reloaded/) devtools
* Safari - [增强网页性能](https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/Web_Inspector_Tutorial/EnhancingyourWebpagesPerformance/EnhancingyourWebpagesPerformance.html)
¥Safari - [enhancing the performance of your webpage](https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/Web_Inspector_Tutorial/EnhancingyourWebpagesPerformance/EnhancingyourWebpagesPerformance.html)
请注意,在 `dev` 模式下本地运行的站点将表现出与你的生产应用不同的行为,因此你应该在构建后在 [preview](building-your-app#Preview-your-app) 模式下进行性能测试。
¥Note that your site running locally in `dev` mode will exhibit different behaviour than your production app, so you should do performance testing in [preview](building-your-app#Preview-your-app) mode after building.
### 仪器(Instrumenting)
¥Instrumenting
如果你在浏览器的网络选项卡中看到 API 调用花费了很长时间,并且想要了解原因,你可以考虑使用 [OpenTelemetry](https://opentelemetry.io/) 或 [Server-Timing 标头](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) 等工具来检测后端。
¥If you see in the network tab of your browser that an API call is taking a long time and you'd like to understand why, you may consider instrumenting your backend with a tool like [OpenTelemetry](https://opentelemetry.io/) or [Server-Timing headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing).
## 优化资源(Optimizing assets)
¥Optimizing assets
### 图片(Images)
¥Images
减小图片文件的大小通常是对网站性能影响最大的更改之一。Svelte 提供 `@sveltejs/enhanced-img` 包(详情请参阅 [images](images) 页面),以简化此操作。此外,Lighthouse 可用于识别最严重的违规者。
¥Reducing the size of image files is often one of the most impactful changes you can make to a site's performance. Svelte provides the `@sveltejs/enhanced-img` package, detailed on the [images](images) page, for making this easier. Additionally, Lighthouse is useful for identifying the worst offenders.
### 视频(Videos)
¥Videos
视频文件可能非常大,因此应格外小心以确保它们得到优化:
¥Video files can be very large, so extra care should be taken to ensure that they're optimized:
* 使用 [Handbrake](https://handbrake.fr/) 等工具压缩视频。考虑将视频转换为 Web 友好格式,例如 `.webm` 或 `.mp4`。
¥Compress videos with tools such as [Handbrake](https://handbrake.fr/). Consider converting the videos to web-friendly formats such as `.webm` or `.mp4`.
* 你可以使用 `preload="none"` 位于折叠下方的 [延迟加载视频](https://web.dev/articles/lazy-loading-video)(但请注意,当用户启动它时,这会减慢播放速度)。
¥You can [lazy-load videos](https://web.dev/articles/lazy-loading-video) located below the fold with `preload="none"` (though note that this will slow down playback when the user *does* initiate it).
* 使用 [FFmpeg](https://ffmpeg.org/) 等工具从静音视频中剥离音轨。
¥Strip the audio track out of muted videos using a tool like [FFmpeg](https://ffmpeg.org/).
### 字体(Fonts)
¥Fonts
SvelteKit 在用户访问页面时会自动预加载关键的 `.js` 和 `.css` 文件,但默认情况下不会预加载字体,因为这可能会导致下载不必要的文件(例如 CSS 引用但当前页面实际上未使用的字体粗细)。话虽如此,正确预加载字体可以对你的网站速度产生很大影响。在你的 [`handle`](hooks#Server-hooks-handle) 钩子中,你可以使用包含字体的 `preload` 过滤器调用 `resolve`。
¥SvelteKit automatically preloads critical `.js` and `.css` files when the user visits a page, but it does *not* preload fonts by default, since this may cause unnecessary files (such as font weights that are referenced by your CSS but not actually used on the current page) to be downloaded. Having said that, preloading fonts correctly can make a big difference to how fast your site feels. In your [`handle`](hooks#Server-hooks-handle) hook, you can call `resolve` with a `preload` filter that includes your fonts.
你可以通过 [subsetting](https://web.dev/learn/performance/optimize-web-fonts#subset_your_web_fonts) 字体来减小字体文件的大小。
¥You can reduce the size of font files by [subsetting](https://web.dev/learn/performance/optimize-web-fonts#subset_your_web_fonts) your fonts.
## 减少代码大小(Reducing code size)
¥Reducing code size
### Svelte 版本(Svelte version)
¥Svelte version
我们建议运行最新版本的 Svelte。Svelte 5 比 Svelte 4 更小更快,而 Svelte 4 又比 Svelte 3 更小更快。
¥We recommend running the latest version of Svelte. Svelte 5 is smaller and faster than Svelte 4, which is smaller and faster than Svelte 3.
### 包(Packages)
¥Packages
[`rollup-plugin-visualizer`](https://www.npmjs.com/package/rollup-plugin-visualizer) 有助于识别哪些包对你网站的大小贡献最大。你还可以通过手动检查构建输出(在 [Vite 配置](https://vitejs.dev/config/build-options.html#build-minify) 中使用 `build: { minify: false }` 使输出可读,但请记住在部署应用之前撤消该操作)或通过浏览器的 devtools 的网络选项卡来找到删除代码的机会。
¥[`rollup-plugin-visualizer`](https://www.npmjs.com/package/rollup-plugin-visualizer) can be helpful for identifying which packages are contributing the most to the size of your site. You may also find opportunities to remove code by manually inspecting the build output (use `build: { minify: false }` in your [Vite config](https://vitejs.dev/config/build-options.html#build-minify) to make the output readable, but remember to undo that before deploying your app), or via the network tab of your browser's devtools.
### 外部脚本(External scripts)
¥External scripts
尝试尽量减少浏览器中运行的第三方脚本的数量。例如,不要使用基于 JavaScript 的分析,而要考虑使用服务器端实现,例如许多带有 SvelteKit 适配器的平台提供的实现,包括 [Cloudflare](https://www.cloudflare.com/web-analytics/)、[Netlify](https://docs.netlify.com/monitor-sites/site-analytics/) 和 [Vercel](https://vercel.com/docs/analytics)。
¥Try to minimize the number of third-party scripts running in the browser. For example, instead of using JavaScript-based analytics consider using server-side implementations, such as those offered by many platforms with SvelteKit adapters including [Cloudflare](https://www.cloudflare.com/web-analytics/), [Netlify](https://docs.netlify.com/monitor-sites/site-analytics/), and [Vercel](https://vercel.com/docs/analytics).
要在 Web 工作器中运行第三方脚本(避免阻塞主线程),请使用 [Partytown 的 SvelteKit 集成](https://partytown.builder.io/sveltekit)。
¥To run third party scripts in a web worker (which avoids blocking the main thread), use [Partytown's SvelteKit integration](https://partytown.builder.io/sveltekit).
### 选择性加载(Selective loading)
¥Selective loading
使用静态 `import` 声明导入的代码将自动与页面的其余部分打包在一起。如果有一段代码仅在满足某些条件时才需要,请使用动态 `import(...)` 表单有选择地延迟加载组件。
¥Code imported with static `import` declarations will be automatically bundled with the rest of your page. If there is a piece of code you need only when some condition is met, use the dynamic `import(...)` form to selectively lazy-load the component.
## 导航(Navigation)
¥Navigation
### 预加载(Preloading)
¥Preloading
你可以使用 [链接选项](link-options) 预先加载必要的代码和数据,从而加快客户端导航速度。当你创建新的 SvelteKit 应用时,默认情况下会在 `` 元素上配置此功能。
¥You can speed up client-side navigations by eagerly preloading the necessary code and data, using [link options](link-options). This is configured by default on the `` element when you create a new SvelteKit app.
### 非必要数据(Non-essential data)
¥Non-essential data
对于加载缓慢且不需要立即使用的数据,从 `load` 函数返回的对象可以包含 promise 而不是数据本身。对于服务器 `load` 函数,这将导致数据在导航(或初始页面加载)后进入 [stream](load#Streaming-with-promises)。
¥For slow-loading data that isn't needed immediately, the object returned from your `load` function can contain promises rather than the data itself. For server `load` functions, this will cause the data to [stream](load#Streaming-with-promises) in after the navigation (or initial page load).
### 防止瀑布(Preventing waterfalls)
¥Preventing waterfalls
最大的性能杀手之一是所谓的瀑布,即按顺序发出的一系列请求。这可以在服务器上或浏览器中进行,但在处理需要传输更远或跨较慢网络的数据时成本尤其高昂,例如移动用户调用远程服务器的调用。
¥One of the biggest performance killers is what is referred to as a *waterfall*, which is a series of requests that is made sequentially. This can happen on the server or in the browser, but is especially costly when dealing with data that has to travel further or across slower networks, such as a mobile user making a call to a distant server.
在浏览器中,当你的 HTML 启动请求链(例如请求 JS,JS 请求 CSS,CSS 请求背景图片和 Web 字体)时,可能会出现瀑布流。SvelteKit 将通过添加 [`modulepreload`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/modulepreload) 标签或标题在很大程度上为你解决此类问题,但你应该查看 [devtools 中的网络选项卡](#Diagnosing-issues) 以检查是否需要预加载其他资源。
¥In the browser, waterfalls can occur when your HTML kicks off request chains such as requesting JS which requests CSS which requests a background image and web font. SvelteKit will largely solve this class of problems for you by adding [`modulepreload`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/modulepreload) tags or headers, but you should view [the network tab in your devtools](#Diagnosing-issues) to check whether additional resources need to be preloaded.
* 如果你使用 [网页字体](#Optimizing-assets-Fonts),请特别注意这一点,因为它们需要手动处理。
¥Pay special attention to this if you use [web fonts](#Optimizing-assets-Fonts) since they need to be handled manually.
* 启用 [单页应用 (SPA) 模式](single-page-apps) 将导致此类瀑布。在 SPA 模式下,会生成一个空页面,该页面会获取 JavaScript,最终加载并渲染页面。这会导致在显示单个像素之前进行额外的网络往返。
¥Enabling [single page app (SPA) mode](single-page-apps) will cause such waterfalls. With SPA mode, an empty page is generated, which fetches JavaScript, which ultimately loads and renders the page. This results in extra network round trips before a single pixel can be displayed.
瀑布流也可能出现在对后端的调用中,无论是从浏览器还是服务器发出。例如如果通用 `load` 函数调用 API 获取当前用户信息,然后使用该响应中的详细信息获取已保存商品列表,再使用该响应获取每个商品的详细信息,则浏览器最终将发出多个连续的请求。这对性能来说是致命的,尤其是对于物理位置远离后端的用户。
¥Waterfalls can also occur on calls to the backend whether made from the browser or server. E.g. if a universal `load` function makes an API call to fetch the current user, then uses the details from that response to fetch a list of saved items, and then uses *that* response to fetch the details for each item, the browser will end up making multiple sequential requests. This is deadly for performance, especially for users that are physically located far from your backend.
* 为了避免此问题,请使用 [服务器 `load` 函数](load#Universal-vs-server) 向依赖服务器而非浏览器的后端服务发出请求。但请注意,服务器 `load` 函数也无法免受瀑布流的影响(尽管瀑布流的成本要低得多,因为它们很少涉及高延迟的往返)。例如,如果你查询数据库以获取当前用户,然后使用该数据对已保存项目列表进行第二次查询,则通常使用数据库连接发出单个查询会更高效。
¥Avoid this issue by using [server `load` functions](load#Universal-vs-server) to make requests to backend services that are dependencies from the server rather than from the browser. Note, however, that server `load` functions are also not immune to waterfalls (though they are much less costly since they rarely involve round trips with high latency). For example, if you query a database to get the current user and then use that data to make a second query for a list of saved items, it will typically be more performant to issue a single query with a database join.
## 托管(Hosting)
¥Hosting
你的前端应与后端位于同一数据中心,以最大限度地减少延迟。对于没有中央后端的站点,许多 SvelteKit 适配器支持部署到边缘,这意味着从附近的服务器处理每个用户的请求。这可以显著减少加载时间。某些适配器甚至支持 [按路由配置部署](page-options#config)。你还应该考虑从 CDN(通常是边缘网络)提供图片 - 许多 SvelteKit 适配器的主机将自动执行此操作。
¥Your frontend should be located in the same data center as your backend to minimize latency. For sites with no central backend, many SvelteKit adapters support deploying to the *edge*, which means handling each user's requests from a nearby server. This can reduce load times significantly. Some adapters even support [configuring deployment on a per-route basis](page-options#config). You should also consider serving images from a CDN (which are typically edge networks) — the hosts for many SvelteKit adapters will do this automatically.
确保你的主机使用 HTTP/2 或更新版本。Vite 的代码拆分会创建大量小文件以提高可缓存性,从而实现出色的性能,但这确实假设你的文件可以与 HTTP/2 并行加载。
¥Ensure your host uses HTTP/2 or newer. Vite's code splitting creates numerous small files for improved cacheability, which results in excellent performance, but this does assume that your files can be loaded in parallel with HTTP/2.
## 进一步阅读(Further reading)
¥Further reading
在大多数情况下,构建高性能 SvelteKit 应用与构建任何高性能 Web 应用相同。你应该能够将来自通用性能资源(如 [核心 Web 要素](https://web.dev/explore/learn-core-web-vitals))的信息应用于你构建的任何 Web 体验。
¥For the most part, building a performant SvelteKit app is the same as building any performant web app. You should be able to apply information from general performance resources such as [Core Web Vitals](https://web.dev/explore/learn-core-web-vitals) to any web experience you build.
# 图标
## CSS
使用图标的一个好方法是纯粹通过 CSS 来定义它们。Iconify 支持 [许多流行的图标集](https://icon-sets.iconify.design/) 和 [可以通过以下方式包含 CSS](https://iconify.design/docs/usage/css/)。此方法也可以通过利用 Iconify [Tailwind CSS 插件](https://iconify.design/docs/usage/css/tailwind/) 或 [UnoCSS 插件](https://iconify.design/docs/usage/css/unocss/) 与流行的 CSS 框架一起使用。与基于 Svelte 组件的库不同,它不需要将每个图标导入到你的 `.svelte` 文件中。
¥A great way to use icons is to define them purely via CSS. Iconify offers support for [many popular icon sets](https://icon-sets.iconify.design/) that [can be included via CSS](https://iconify.design/docs/usage/css/). This method can also be used with popular CSS frameworks by leveraging the Iconify [Tailwind CSS plugin](https://iconify.design/docs/usage/css/tailwind/) or [UnoCSS plugin](https://iconify.design/docs/usage/css/unocss/). As opposed to libraries based on Svelte components, it doesn't require each icon to be imported into your `.svelte` file.
## Svelte
有许多 [Svelte 图标库](/packages#icons)。选择图标库时,建议避免使用每个图标都提供 `.svelte` 文件的库,因为这些库可能包含数千个 `.svelte` 文件,会严重拖慢 [Vite 的依赖优化](https://vite.dev/guide/dep-pre-bundling.html) 的速度。如果通过伞形导入和子路径导入 [如 `vite-plugin-svelte` FAQ 中所述](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies) 导入图标,这可能会变得特别病态。
¥There are many [icon libraries for Svelte](/packages#icons). When choosing an icon library, it is recommended to avoid those that provide a `.svelte` file per icon, as these libraries can have thousands of `.svelte` files which really slow down [Vite's dependency optimization](https://vite.dev/guide/dep-pre-bundling.html). This can become especially pathological if the icons are imported both via an umbrella import and subpath import [as described in the `vite-plugin-svelte` FAQ](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies).
# 图片
图片会对应用的性能产生很大影响。为了获得最佳效果,你应该通过执行以下操作来优化它们:
¥Images can have a big impact on your app's performance. For best results, you should optimize them by doing the following:
* 生成最佳格式,如 `.avif` 和 `.webp`
¥generate optimal formats like `.avif` and `.webp`
* 为不同的屏幕创建不同的尺寸
¥create different sizes for different screens
* 确保资源可以有效地缓存
¥ensure that assets can be cached effectively
手动执行此操作很繁琐。你可以使用多种技术,具体取决于你的需求和偏好。
¥Doing this manually is tedious. There are a variety of techniques you can use, depending on your needs and preferences.
## Vite 的内置处理(Vite's built-in handling)
¥Vite's built-in handling
[Vite 将自动处理导入的资源](https://vitejs.dev/guide/assets.html) 用于提高性能。这包括通过 CSS `url()` 函数引用的资源。文件名将添加哈希值,以便可以缓存它们,并且小于 `assetsInlineLimit` 的资源将被内联。Vite 的资源处理最常用于图片,但也可用于视频、音频等。
¥[Vite will automatically process imported assets](https://vitejs.dev/guide/assets.html) for improved performance. This includes assets referenced via the CSS `url()` function. Hashes will be added to the filenames so that they can be cached, and assets smaller than `assetsInlineLimit` will be inlined. Vite's asset handling is most often used for images, but is also useful for video, audio, etc.
```svelte
```
## @sveltejs/enhanced-img
`@sveltejs/enhanced-img` 是在 Vite 内置资源处理之上提供的插件。它提供即插即用图片处理,可为较小的文件格式(如 `avif` 或 `webp`)提供服务,自动设置图片的内在 `width` 和 `height` 以避免布局偏移,为各种设备创建多种尺寸的图片,并剥离 EXIF 数据以保护隐私。它将在任何基于 Vite 的项目中工作,包括但不限于 SvelteKit 项目。
¥`@sveltejs/enhanced-img` is a plugin offered on top of Vite's built-in asset handling. It provides plug and play image processing that serves smaller file formats like `avif` or `webp`, automatically sets the intrinsic `width` and `height` of the image to avoid layout shift, creates images of multiple sizes for various devices, and strips EXIF data for privacy. It will work in any Vite-based project including, but not limited to, SvelteKit projects.
> [!NOTE] 作为构建插件,`@sveltejs/enhanced-img` 只能在构建过程中优化位于你计算机上的文件。如果你的图片位于其他位置(例如,从数据库、CMS 或后端提供的路径),请阅读有关 [loading images dynamically from a CDN](#Loading-images-dynamically-from-a-CDN) 的信息。
### 设置(Setup)
¥Setup
安装:
¥Install:
```sh
npm i -D @sveltejs/enhanced-img
```
调整 `vite.config.js`:
¥Adjust `vite.config.js`:
```js
import { sveltekit } from '@sveltejs/kit/vite';
+++import { enhancedImages } from '@sveltejs/enhanced-img';+++
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
+++enhancedImages(), // must come before the SvelteKit plugin+++
sveltekit()
]
});
```
由于转换图片的计算成本,首次构建将花费更长时间。但是,构建输出将缓存在 `./node_modules/.cache/imagetools` 中,以便后续构建速度更快。
¥Building will take longer on the first build due to the computational expense of transforming images. However, the build output will be cached in `./node_modules/.cache/imagetools` so that subsequent builds will be fast.
### 基本用法(Basic usage)
¥Basic usage
在你的 `.svelte` 组件中使用 `` 而不是 ` ` 并使用 [Vite 资源导入](https://vitejs.dev/guide/assets.html#static-asset-handling) 路径引用图片文件:
¥Use in your `.svelte` components by using `` rather than ` ` and referencing the image file with a [Vite asset import](https://vitejs.dev/guide/assets.html#static-asset-handling) path:
```svelte
```
在构建时,你的 `` 标签将被替换为由 `` 封装的 ` `,提供多种图片类型和大小。只能在不损失质量的情况下缩小图片,这意味着你应该提供所需的最高分辨率图片 - 将为可能请求图片的各种设备类型生成较小的版本。
¥At build time, your `` tag will be replaced with an ` ` wrapped by a `` providing multiple image types and sizes. It's only possible to downscale images without losing quality, which means that you should provide the highest resolution image that you need — smaller versions will be generated for the various device types that may request an image.
你应该以 2 倍分辨率为 HiDPI 显示器(又名视网膜显示器)提供图片。`` 将自动负责为较小的设备提供较小的版本。
¥You should provide your image at 2x resolution for HiDPI displays (a.k.a. retina displays). `` will automatically take care of serving smaller versions to smaller devices.
> [!NOTE] 如果你希望在 `
```
### `srcset` 和 `sizes`(`srcset` and `sizes`)
¥`srcset` and `sizes`
如果你有一个大图片,例如占据设计宽度的英雄图片,你应该指定 `sizes`,以便在较小的设备上请求较小的版本。例如如果你有一个 1280px 的图片,你可能需要指定类似以下内容:
¥If you have a large image, such as a hero image taking the width of the design, you should specify `sizes` so that smaller versions are requested on smaller devices. E.g. if you have a 1280px image you may want to specify something like:
```svelte
```
如果指定了 `sizes`,`` 将为较小的设备生成小图片并填充 `srcset` 属性。
¥If `sizes` is specified, `` will generate small images for smaller devices and populate the `srcset` attribute.
自动生成的最小图片宽度为 540px。如果你想要更小的图片或想要指定自定义宽度,可以使用 `w` 查询参数执行此操作:
¥The smallest picture generated automatically will have a width of 540px. If you'd like smaller images or would otherwise like to specify custom widths, you can do that with the `w` query parameter:
```svelte
```
如果没有提供 `sizes`,则会生成 HiDPI / Retina 图片和标准分辨率图片。你提供的图片应该是你希望显示的分辨率的 2 倍,以便浏览器可以在具有高 [设备像素比](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) 的设备上显示该图片。
¥If `sizes` is not provided, then a HiDPI/Retina image and a standard resolution image will be generated. The image you provide should be 2x the resolution you wish to display so that the browser can display that image on devices with a high [device pixel ratio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio).
### 每个图片转换(Per-image transforms)
¥Per-image transforms
默认情况下,增强图片将转换为更高效的格式。但是,你可能希望应用其他转换,例如模糊、质量、展平或旋转操作。你可以通过附加查询字符串来运行每个图片的转换:
¥By default, enhanced images will be transformed to more efficient formats. However, you may wish to apply other transforms such as a blur, quality, flatten, or rotate operation. You can run per-image transforms by appending a query string:
```svelte
```
[查看 imagetools repo 以获取完整的指令列表](https://github.com/JonasKruckenberg/imagetools/blob/main/docs/directives.md)。
¥[See the imagetools repo for the full list of directives](https://github.com/JonasKruckenberg/imagetools/blob/main/docs/directives.md).
## 从 CDN 动态加载图片(Loading images dynamically from a CDN)
¥Loading images dynamically from a CDN
在某些情况下,图片在构建时可能无法访问 - 例如,它们可能位于内容管理系统或其他地方。
¥In some cases, the images may not be accessible at build time — e.g. they may live inside a content management system or elsewhere.
使用内容分发网络 (CDN) 可以让你动态优化这些图片,并在大小方面提供更大的灵活性,但它可能涉及一些设置开销和使用成本。根据缓存策略,浏览器可能无法使用资源的缓存副本,直到从 CDN 收到 [304 响应](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304)。构建 HTML 以目标 CDN 允许使用 ` ` 标签,因为 CDN 可以根据 `User-Agent` 标头提供适当的格式,而构建时优化必须生成具有多个来源的 `` 标签。最后,一些 CDN 可能会延迟生成图片,这可能会对流量低且图片频繁更改的站点产生负面性能影响。
¥Using a content delivery network (CDN) can allow you to optimize these images dynamically, and provides more flexibility with regards to sizes, but it may involve some setup overhead and usage costs. Depending on caching strategy, the browser may not be able to use a cached copy of the asset until a [304 response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) is received from the CDN. Building HTML to target CDNs allows using an ` ` tag since the CDN can serve the appropriate format based on the `User-Agent` header, whereas build-time optimizations must produce `` tags with multiple sources. Finally, some CDNs may generate images lazily, which could have a negative performance impact for sites with low traffic and frequently changing images.
CDN 通常可以在不需要任何库的情况下使用。但是,有许多支持 Svelte 的库可以简化这一过程。[`@unpic/svelte`](https://unpic.pics/img/svelte/) 是一个与 CDN 无关的库,支持大量提供商。你还可能会发现特定的 CDN(如 [Cloudinary](https://svelte.cloudinary.dev/))具有 Svelte 支持。最后,一些支持 Svelte 的内容管理系统 (CMS)(例如 [Contentful](https://www.contentful.com/sveltekit-starter-guide/)、[Storyblok](https://github.com/storyblok/storyblok-svelte) 和 [Contentstack](https://www.contentstack.com/docs/developers/sample-apps/build-a-starter-website-with-sveltekit-and-contentstack))内置了对图片处理的支持。
¥CDNs can generally be used without any need for a library. However, there are a number of libraries with Svelte support that make it easier. [`@unpic/svelte`](https://unpic.pics/img/svelte/) is a CDN-agnostic library with support for a large number of providers. You may also find that specific CDNs like [Cloudinary](https://svelte.cloudinary.dev/) have Svelte support. Finally, some content management systems (CMS) which support Svelte (such as [Contentful](https://www.contentful.com/sveltekit-starter-guide/), [Storyblok](https://github.com/storyblok/storyblok-svelte), and [Contentstack](https://www.contentstack.com/docs/developers/sample-apps/build-a-starter-website-with-sveltekit-and-contentstack)) have built-in support for image handling.
## 最佳实践(Best practices)
¥Best practices
* 对于每种图片类型,请使用上面讨论的解决方案中的适当解决方案。你可以在一个项目中混合搭配所有三种解决方案。例如,你可以使用 Vite 的内置处理为 ` ` 标签提供图片,使用 `@sveltejs/enhanced-img` 在你的主页上显示图片,并以动态方式显示用户提交的内容。
¥For each image type, use the appropriate solution from those discussed above. You can mix and match all three solutions in one project. For example, you may use Vite's built-in handling to provide images for ` ` tags, display images on your homepage with `@sveltejs/enhanced-img`, and display user-submitted content with a dynamic approach.
* 无论你使用哪种图片优化类型,都考虑通过 CDN 提供所有图片。CDN 通过在全球范围内分发静态资源的副本来减少延迟。
¥Consider serving all images via CDN regardless of the image optimization types you use. CDNs reduce latency by distributing copies of static assets globally.
* 你的原始图片应具有良好的质量/分辨率,并且应具有 2 倍的显示宽度,以服务于 HiDPI 设备。图片处理可以在服务较小的屏幕时缩小图片尺寸以节省带宽,但发明像素来放大图片尺寸会浪费带宽。
¥Your original images should have a good quality/resolution and should have 2x the width it will be displayed at to serve HiDPI devices. Image processing can size images down to save bandwidth when serving smaller screens, but it would be a waste of bandwidth to invent pixels to size images up.
* 对于比移动设备宽度大得多的图片(大约 400px),例如占据页面设计宽度的英雄图片,请指定 `sizes`,以便在较小的设备上提供较小的图片。
¥For images which are much larger than the width of a mobile device (roughly 400px), such as a hero image taking the width of the page design, specify `sizes` so that smaller images can be served on smaller devices.
* 对于重要的图片,例如 [最大内容绘制 (LCP)](https://web.dev/articles/lcp) 图片,请设置 `fetchpriority="high"` 并避免使用 `loading="lazy"` 以尽早优先加载。
¥For important images, such as the [largest contentful paint (LCP)](https://web.dev/articles/lcp) image, set `fetchpriority="high"` and avoid `loading="lazy"` to prioritize loading as early as possible.
* 为图片提供容器或样式,使其受到约束,并且在页面加载时不会跳来跳去,从而影响你的 [累积布局偏移 (CLS)](https://web.dev/articles/cls)。`width` 和 `height` 帮助浏览器在图片仍在加载时保留空间,因此 `@sveltejs/enhanced-img` 将为你添加 `width` 和 `height`。
¥Give the image a container or styling so that it is constrained and does not jump around while the page is loading affecting your [cumulative layout shift (CLS)](https://web.dev/articles/cls). `width` and `height` help the browser to reserve space while the image is still loading, so `@sveltejs/enhanced-img` will add a `width` and `height` for you.
* 始终提供良好的 `alt` 文本。如果你不这样做,Svelte 编译器会警告你。
¥Always provide a good `alt` text. The Svelte compiler will warn you if you don't do this.
* 不要在 `sizes` 中使用 `em` 或 `rem`,并更改这些度量的默认大小。当在 `sizes` 或 `@media` 查询中使用时,`em` 和 `rem` 都被定义为用户的默认 `font-size`。对于像 `sizes="(min-width: 768px) min(100vw, 108rem), 64rem"` 这样的 `sizes` 声明,如果 CSS 更改,控制图片在页面上的布局方式的实际 `em` 或 `rem` 可能会有所不同。例如,不要执行类似 `html { font-size: 62.5%; }` 的操作,因为浏览器预加载器保留的插槽现在最终会大于创建 CSS 对象模型后的实际插槽。
¥Do not use `em` or `rem` in `sizes` and change the default size of these measures. When used in `sizes` or `@media` queries, `em` and `rem` are both defined to mean the user's default `font-size`. For a `sizes` declaration like `sizes="(min-width: 768px) min(100vw, 108rem), 64rem"`, the actual `em` or `rem` that controls how the image is laid out on the page can be different if changed by CSS. For example, do not do something like `html { font-size: 62.5%; }` as the slot reserved by the browser preloader will now end up being larger than the actual slot of the CSS object model once it has been created.
# 可访问性
SvelteKit 致力于默认为你的应用提供一个可访问的平台。Svelte 的 [编译时可访问性检查](../svelte/compiler-warnings) 也适用于你构建的任何 SvelteKit 应用。
¥SvelteKit strives to provide an accessible platform for your app by default. Svelte's [compile-time accessibility checks](../svelte/compiler-warnings) will also apply to any SvelteKit application you build.
以下是 SvelteKit 内置辅助功能的工作原理,以及你需要做些什么才能使这些功能尽可能正常工作。请记住,虽然 SvelteKit 提供了可访问的基础,但你仍有责任确保你的应用代码可访问。如果你是无障碍新手,请参阅本指南的 ["怎么会这样?"](accessibility#Further-reading) 部分以获取更多资源。
¥Here's how SvelteKit's built-in accessibility features work and what you need to do to help these features to work as well as possible. Keep in mind that while SvelteKit provides an accessible foundation, you are still responsible for making sure your application code is accessible. If you're new to accessibility, see the ["further reading"](accessibility#Further-reading) section of this guide for additional resources.
我们认识到可访问性可能很难正确实现。如果你想建议改进 SvelteKit 处理可访问性的方式,请使用 [打开 GitHub 问题](https://github.com/sveltejs/kit/issues)。
¥We recognize that accessibility can be hard to get right. If you want to suggest improvements to how SvelteKit handles accessibility, please [open a GitHub issue](https://github.com/sveltejs/kit/issues).
## 路由公告(Route announcements)
¥Route announcements
在传统的服务器渲染应用中,每次导航(例如单击 `` 标签)都会触发整个页面重新加载。当发生这种情况时,屏幕阅读器和其他辅助技术将读出新页面的标题,以便用户了解页面已更改。
¥In traditional server-rendered applications, every navigation (e.g. clicking on an ` ` tag) triggers a full page reload. When this happens, screen readers and other assistive technology will read out the new page's title so that users understand that the page has changed.
由于 SvelteKit 中的页面间导航无需重新加载页面(称为 [客户端路由](glossary#Routing)),因此 SvelteKit 会在页面上注入一个 [实时区域](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions),每次导航后都会读出新的页面名称。这通过检查 `` 元素来确定要宣布的页面名称。
¥Since navigation between pages in SvelteKit happens without reloading the page (known as [client-side routing](glossary#Routing)), SvelteKit injects a [live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) onto the page that will read out the new page name after each navigation. This determines the page name to announce by inspecting the `` element.
由于这种行为,你应用中的每个页面都应该有一个唯一的描述性标题。在 SvelteKit 中,你可以通过在每个页面上放置一个 `` 元素来执行此操作:
¥Because of this behavior, every page in your app should have a unique, descriptive title. In SvelteKit, you can do this by placing a `` element on each page:
```svelte
Todo List
```
这将允许屏幕阅读器和其他辅助技术在导航后识别新页面。提供描述性标题对于 [SEO](seo#Manual-setup-title-and-meta) 也很重要。
¥This will allow screen readers and other assistive technology to identify the new page after a navigation occurs. Providing a descriptive title is also important for [SEO](seo#Manual-setup-title-and-meta).
## 焦点管理(Focus management)
¥Focus management
在传统的服务器渲染应用中,每次导航都会将焦点重置到页面顶部。这可确保使用键盘或屏幕阅读器浏览网页的人从一开始就与页面交互。
¥In traditional server-rendered applications, every navigation will reset focus to the top of the page. This ensures that people browsing the web with a keyboard or screen reader will start interacting with the page from the beginning.
为了在客户端路由期间模拟此行为,SvelteKit 在每次导航和 [增强表单提交](form-actions#Progressive-enhancement) 之后聚焦 `` 元素。有一个例外 - 如果存在具有 [`autofocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) 属性的元素,SvelteKit 将改为关注该元素。使用该属性时,请确保 [考虑对辅助技术的影响](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus#accessibility_considerations)。
¥To simulate this behavior during client-side routing, SvelteKit focuses the `` element after each navigation and [enhanced form submission](form-actions#Progressive-enhancement). There is one exception - if an element with the [`autofocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) attribute is present, SvelteKit will focus that element instead. Make sure to [consider the implications for assistive technology](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus#accessibility_considerations) when using that attribute.
如果你想自定义 SvelteKit 的焦点管理,可以使用 `afterNavigate` 钩子:
¥If you want to customize SvelteKit's focus management, you can use the `afterNavigate` hook:
```js
///
// ---cut---
import { afterNavigate } from '$app/navigation';
afterNavigate(() => {
/** @type {HTMLElement | null} */
const to_focus = document.querySelector('.focus-me');
to_focus?.focus();
});
```
你还可以使用 [`goto`]($app-navigation#goto) 函数以编程方式导航到其他页面。默认情况下,这将具有与单击链接相同的客户端路由行为。但是,`goto` 还接受 `keepFocus` 选项,该选项将保留当前焦点元素而不是重置焦点。如果启用此选项,请确保导航后当前焦点元素仍然存在于页面上。如果元素不再存在,用户的焦点将丢失,从而给辅助技术用户带来混乱的体验。
¥You can also programmatically navigate to a different page using the [`goto`]($app-navigation#goto) function. By default, this will have the same client-side routing behavior as clicking on a link. However, `goto` also accepts a `keepFocus` option that will preserve the currently-focused element instead of resetting focus. If you enable this option, make sure the currently-focused element still exists on the page after navigation. If the element no longer exists, the user's focus will be lost, making for a confusing experience for assistive technology users.
## "lang" 属性(The "lang" attribute)
¥The "lang" attribute
默认情况下,SvelteKit 的页面模板将文档的默认语言设置为英语。如果你的内容不是英文,则应更新 `src/app.html` 中的 `` 元素以具有正确的 [`lang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang#accessibility) 属性。这将确保任何辅助技术读取文档时都使用正确的发音。例如,如果你的内容是德语,则应将 `app.html` 更新为以下内容:
¥By default, SvelteKit's page template sets the default language of the document to English. If your content is not in English, you should update the `` element in `src/app.html` to have the correct [`lang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang#accessibility) attribute. This will ensure that any assistive technology reading the document uses the correct pronunciation. For example, if your content is in German, you should update `app.html` to the following:
```html
/// file: src/app.html
```
如果你的内容有多种语言版本,则应根据当前页面的语言设置 `lang` 属性。你可以使用 SvelteKit 的 [处理钩子](hooks#Server-hooks-handle) 执行此操作:
¥If your content is available in multiple languages, you should set the `lang` attribute based on the language of the current page. You can do this with SvelteKit's [handle hook](hooks#Server-hooks-handle):
```html
/// file: src/app.html
```
```js
/// file: src/hooks.server.js
/**
* @param {import('@sveltejs/kit').RequestEvent} event
*/
function get_lang(event) {
return 'en';
}
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export function handle({ event, resolve }) {
return resolve(event, {
transformPageChunk: ({ html }) => html.replace('%lang%', get_lang(event))
});
}
```
## 进一步阅读(Further reading)
¥Further reading
在大多数情况下,构建可访问的 SvelteKit 应用与构建可访问的 Web 应用相同。你应该能够将来自以下通用可访问性资源的信息应用于你构建的任何 Web 体验:
¥For the most part, building an accessible SvelteKit app is the same as building an accessible web app. You should be able to apply information from the following general accessibility resources to any web experience you build:
* [MDN Web 文档:可访问性](https://developer.mozilla.org/en-US/docs/Learn/Accessibility)
¥[MDN Web Docs: Accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility)
* [A11y 项目](https://www.a11yproject.com/)
¥[The A11y Project](https://www.a11yproject.com/)
* [如何满足 WCAG(快速参考)](https://www.w3.org/WAI/WCAG21/quickref/)
¥[How to Meet WCAG (Quick Reference)](https://www.w3.org/WAI/WCAG21/quickref/)
# SEO
SEO 最重要的方面是创建来自网络的广泛链接的高质量内容。但是,构建排名靠前的网站需要考虑一些技术问题。
¥The most important aspect of SEO is to create high-quality content that is widely linked to from around the web. However, there are a few technical considerations for building sites that rank well.
## 开箱即用(Out of the box)
¥Out of the box
### SSR
虽然近年来搜索引擎在索引使用客户端 JavaScript 渲染的内容方面有所改进,但服务器端渲染的内容被索引的频率和可靠性更高。SvelteKit 默认使用 SSR,虽然你可以在 [`handle`](hooks#Server-hooks-handle) 中禁用它,但除非你有充分的理由不这样做,否则应该将其保留。
¥While search engines have got better in recent years at indexing content that was rendered with client-side JavaScript, server-side rendered content is indexed more frequently and reliably. SvelteKit employs SSR by default, and while you can disable it in [`handle`](hooks#Server-hooks-handle), you should leave it on unless you have a good reason not to.
> [!NOTE] SvelteKit 的渲染高度可配置,你可以根据需要实现 [dynamic rendering](https://developers.google.com/search/docs/advanced/javascript/dynamic-rendering)。通常不建议这样做,因为服务器端渲染 (SSR) 除了 SEO 之外还有其他好处。
### 性能(Performance)
¥Performance
诸如 [核心 Web 要素](https://web.dev/vitals/#core-web-vitals) 之类的信号会影响搜索引擎排名。由于 Svelte 和 SvelteKit 的开销极小,因此它们使构建高性能网站变得更加容易。你可以使用 Google 的 [PageSpeed Insights](https://pagespeed.web.dev/) 或 [Lighthouse](https://developers.google.com/web/tools/lighthouse) 测试你网站的性能。只需几个关键操作,例如使用 SvelteKit 的默认 [混合渲染](glossary#Hybrid-app) 模式和 [优化图片](images),即可显著提升网站速度。阅读 [性能页面](performance) 了解更多详情。
¥Signals such as [Core Web Vitals](https://web.dev/vitals/#core-web-vitals) impact search engine ranking. Because Svelte and SvelteKit introduce minimal overhead, they make it easier to build high performance sites. You can test your site's performance using Google's [PageSpeed Insights](https://pagespeed.web.dev/) or [Lighthouse](https://developers.google.com/web/tools/lighthouse). With just a few key actions like using SvelteKit's default [hybrid rendering](glossary#Hybrid-app) mode and [optimizing your images](images), you can greatly improve your site's speed. Read [the performance page](performance) for more details.
### 规范化 URL(Normalized URLs)
¥Normalized URLs
SvelteKit 将带有尾部斜杠的路径名重定向为不带斜杠的路径名(反之亦然,具体取决于你的 [configuration](page-options#trailingSlash)),因为重复的 URL 对 SEO 不利。
¥SvelteKit redirects pathnames with trailing slashes to ones without (or vice versa depending on your [configuration](page-options#trailingSlash)), as duplicate URLs are bad for SEO.
## 手动设置(Manual setup)
¥Manual setup
### <title> 和 <meta>(\<title> and <meta>)
¥\<title> and <meta>
每个页面都应该在 [``](../svelte/svelte-head) 内有编写良好且独特的 `` 和 ` ` 元素。有关如何编写描述性标题和说明的指南,以及使搜索引擎可以理解内容的其他建议,可在 Google 的 [Lighthouse SEO 审核](https://web.dev/lighthouse-seo/) 文档中找到。
¥Every page should have well-written and unique `` and `