Skip to main content

从 Sapper 迁移

SvelteKit 是 Sapper 的继任者,它们共享许多设计元素。

如果您有一个现有的 Sapper 应用想要迁移到 SvelteKit,您需要进行一些更改。在迁移过程中,查看一些示例可能会有所帮助。

package.json

type: “module”

在您的 package.json 中添加 "type": "module"。如果您使用的是 Sapper 0.29.3 或更新版本,您可以将此步骤作为渐进式迁移的一部分单独进行。

dependencies

如果您使用了 polkaexpress,请移除它们,以及任何中间件,如 sirvcompression

devDependencies

从您的 devDependencies 中移除 sapper,并替换为 @sveltejs/kit 和您计划使用的适配器(参见下一节)。

scripts

任何引用 sapper 的脚本都应该更新:

  • sapper build 应改为使用 Node 适配器vite build
  • sapper export 应改为使用静态适配器vite build
  • sapper dev 应改为 vite dev
  • node __sapper__/build 应改为 node build

项目文件

您的应用程序的主体部分,即 src/routes 中的内容可以保持原样,但需要移动或更新几个项目文件。

配置

您的 webpack.config.jsrollup.config.js 应该替换为 svelte.config.js,如此处所述。Svelte 预处理器选项应移至 config.preprocess

您需要添加一个适配器sapper build 大致相当于 adapter-node,而 sapper export 大致相当于 adapter-static,不过您可能更倾向于使用专门为您部署的平台设计的适配器。

如果您之前使用的插件用于处理 Vite 不会自动处理的文件类型,您需要找到 Vite 的等效插件并将它们添加到 Vite 配置中。

src/client.js

此文件在 SvelteKit 中没有对应的文件。任何自定义逻辑(除了 sapper.start(...))都应该在您的 +layout.svelte 文件中的 onMount 回调内表达。

src/server.js

当使用 adapter-node 时,对应的是自定义服务端。否则,此文件没有直接对应的文件,因为 SvelteKit 应用可以在无服务端环境中运行。

src/service-worker.js

大多数从 @sapper/service-worker 导入的内容在 $service-worker 中都有对应项:

  • files 保持不变
  • routes 已被移除
  • shell 现在是 build
  • timestamp 现在是 version

src/template.html

src/template.html 文件应重命名为 src/app.html

移除 %sapper.base%%sapper.scripts%%sapper.styles%。将 %sapper.head% 替换为 %sveltekit.head%,将 %sapper.html% 替换为 %sveltekit.body%<div id="sapper"> 不再需要。

src/node_modules

Sapper 应用中的一个常见模式是将内部库放在 src/node_modules 目录中。这在 Vite 中不起作用,所以我们改用 src/lib

页面和布局

重命名文件

现在路由仅由文件夹名称构成以消除歧义,通向 +page.svelte 的文件夹名称对应于路由。查看路由文档了解概述。以下是旧文件和新文件的对比:

routes/about/index.svelte routes/about/+page.svelte
routes/about.svelte routes/about/+page.svelte

您的自定义错误页面组件应从 _error.svelte 重命名为 +error.svelte。任何 _layout.svelte 文件同样应重命名为 +layout.svelte其他文件将被忽略

引入

@sapper/app 导入的 gotoprefetchprefetchRoutes 应分别替换为从 $app/navigation 导入的 gotopreloadDatapreloadCode

@sapper/appstores 导入应被替换 — 请参阅下面的Stores (存储)部分。

之前从 src/node_modules 目录中导入的任何文件现在需要用 $lib 导入替换。

Preload

如之前一样,页面和布局可以导出一个函数,该函数允许在渲染发生之前加载数据。

此函数名称从 preload 重命名为 load,现在它位于紧邻其 +page.svelte(或 +layout.svelte)的 +page.js(或 +layout.js)中,且其 API 已更改。不再是两个参数 — pagesession — 而是一个单一的 event 参数。

不再有 this 对象,因此也没有 this.fetchthis.errorthis.redirect。取而代之的是,您可以从输入方法中获取 fetcherrorredirect 现在通过抛出实现。

存储 (Stores)

在 Sapper 中,您可以这样获取提供的存储的引用:

import { module "@sapper/app"stores } from '@sapper/app';
const { const preloading: anypreloading, const page: anypage, const session: anysession } = module "@sapper/app"stores();

page 存储仍然存在;preloading 被替换为具有 fromto 属性的 navigating 存储。page 现在有 urlparams 属性,但没有 pathquery

在 SvelteKit 中访问它们的方式有所不同。stores 现在是 getStores,但在大多数情况下,这是不必要的,因为您可以直接从 $app/stores 导入 navigatingpage。如果您使用的是 Svelte 5 和 SvelteKit 2.12 或更高版本,请考虑使用 $app/state

路由 (Routing)

不再支持正则路由。取而代之的是使用高级路由匹配

段 (Segments)

以前,布局组件会接收一个 segment 属性,指示子段。这已被移除;您应该使用更灵活的 $page.url.pathname(或 page.url.pathname)值来派生您感兴趣的段。

URL

在 Sapper 中,所有相对 URL 都是相对于基本 URL 解析的 — 通常是 /,除非使用了 basepath 选项 — 而不是相对于当前页面。

这导致了一些问题,现在在 SvelteKit 中不再这样了。相对 URL 是相对于当前页面解析的(对于 load 函数中的 fetch URL,解析为目标页面)。在大多数情况下,使用根相对 URL(即以 / 开头)更简单,因为它们的意义不依赖于上下文。

<a> 属性

  • sapper:prefetch 现在是 data-sveltekit-preload-data
  • sapper:noscroll 现在是 data-sveltekit-noscroll

端点 (Endpoints)

在 Sapper 中,服务端路由 接收由 Node 的 http 模块暴露的 reqres 对象(或由框架如 Polka 和 Express 提供增强的版本)。

SvelteKit 被设计成可以与应用运行的环境无关 — 它可以运行在 Node 服务端,也可以运行在无服务端平台或 Cloudflare Worker 中。因此,您不能再直接与 reqres 交互。您的端点需要更新以符合新签名。

为了支持这种与环境无关的行为,fetch 现在在全局上下文中可用,因此不需要引入 node-fetchcross-fetch 或类似的服务端 fetch 实现来使用它。

集成 (Integrations)

参阅集成文档了解集成的详细信息。

HTML 压缩器

Sapper 默认包含 html-minifier。SvelteKit 没有包含此功能,但您可以将其添加为生产依赖,然后通过钩子使用:

import { module "html-minifier"minify } from 'html-minifier';
import { const building: boolean

SvelteKit analyses your app during the build step by running it. During this process, building is true. This also applies during prerendering.

building
} from '$app/environment';
const
const minification_options: {
    collapseBooleanAttributes: boolean;
    collapseWhitespace: boolean;
    conservativeCollapse: boolean;
    decodeEntities: boolean;
    html5: boolean;
    ignoreCustomComments: RegExp[];
    minifyCSS: boolean;
    ... 8 more ...;
    sortClassName: boolean;
}
minification_options
= {
collapseBooleanAttributes: booleancollapseBooleanAttributes: true, collapseWhitespace: booleancollapseWhitespace: true, conservativeCollapse: booleanconservativeCollapse: true, decodeEntities: booleandecodeEntities: true, html5: booleanhtml5: true, ignoreCustomComments: RegExp[]ignoreCustomComments: [/^#/], minifyCSS: booleanminifyCSS: true, minifyJS: booleanminifyJS: false, removeAttributeQuotes: booleanremoveAttributeQuotes: true, removeComments: booleanremoveComments: false, // 一些 hydration 代码需要注释,所以保留它们 removeOptionalTags: booleanremoveOptionalTags: true, removeRedundantAttributes: booleanremoveRedundantAttributes: true, removeScriptTypeAttributes: booleanremoveScriptTypeAttributes: true, removeStyleLinkTypeAttributes: booleanremoveStyleLinkTypeAttributes: true, sortAttributes: booleansortAttributes: true, sortClassName: booleansortClassName: true }; /** @type {import('@sveltejs/kit').Handle} */ export async function
function handle(input: {
    event: RequestEvent;
    resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}): MaybePromise<...>
@type{import('@sveltejs/kit').Handle}
handle
({ event: RequestEvent<Partial<Record<string, string>>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) {
let let page: stringpage = ''; return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>event, {
ResolveOptions.transformPageChunk?(input: {
    html: string;
    done: boolean;
}): MaybePromise<string | undefined>

Applies custom transforms to HTML. If done is true, it’s the final chunk. Chunks are not guaranteed to be well-formed HTML (they could include an element’s opening tag but not its closing tag, for example) but they will always be split at sensible boundaries such as %sveltekit.head% or layout/page components.

@paraminput the html chunk and the info if this is the last chunk
transformPageChunk
: ({ html: stringhtml, done: booleandone }) => {
let page: stringpage += html: stringhtml; if (done: booleandone) { return const building: boolean

SvelteKit analyses your app during the build step by running it. During this process, building is true. This also applies during prerendering.

building
? module "html-minifier"minify(let page: stringpage,
const minification_options: {
    collapseBooleanAttributes: boolean;
    collapseWhitespace: boolean;
    conservativeCollapse: boolean;
    decodeEntities: boolean;
    html5: boolean;
    ignoreCustomComments: RegExp[];
    minifyCSS: boolean;
    ... 8 more ...;
    sortClassName: boolean;
}
minification_options
) : let page: stringpage;
} } }); }

请注意,当使用 vite preview 测试生产构建的站点时,prerenderingfalse,因此要验证压缩效果,您需要直接检查生成的 HTML 文件。

在 GitHub 编辑此页面