Svelte 与 SvelteKit 的初尝试

· 技术

为什么是 Svelte 与 SvelteKit?

我很喜欢 Vue,同时对 React 也有一定的知识储备,按理来说二选一就可以了。但是,当我第一次了解到 Svelte 这个前端框架的时候,它还是成功钓起了我的好奇心。

Svelte 与 React 和 Vue 不同的是,它在构建过程中进行了优化,最终构建产物要比两者更小、更快。Evan You 曾经做过一个 Svelte 和 Vue 的对比测试:在组件较少的情况下,因为 Svelte 不具有 Runtime(运行时),所以其构建产物的体积要比 Vue 的小很多;而如果组件数量来到 19 个、或是 SSR 模式下来到 13 个组件,Svelte 和 Vue 可谓不相上下。

当然,今天要写的 Sliver Complex 登陆页并不会有这么多的组件。于是,轻量的项目就用轻量的框架写,我决定用 Svelte 来重写,顺便试一下它的元框架 SvelteKit。

实践之路

bun create svelte@latest

由于 pnpm 装一次依赖、在依赖安装期间我的硬盘负载率特别高,于是我使用了 bun 作为 JavaScript 打包器 / Node.js 包管理器。所以,我直接用 bun 初始化了一个 SvelteKit 项目,而事实证明 bun 和 SvelteKit 的兼容性还是可以的。

配置 UnoCSS

安装

SvelteKit 默认使用 Vite 作为打包器,并且它贴心的给你保留了 vite.config.ts 配置文件。因此,我们可以直接使用 UnoCSS 的 Vite 集成,那就先来安装一下吧:

bun add -D unocss

接着来动一下 vite.config.ts

import { sveltekit } from "@sveltejs/kit/vite"
import { defineConfig } from "vite"
import UnoCSS from "unocss/vite"

export default defineConfig({
    plugins: [UnoCSS(), sveltekit()]
})

UnoCSS 的文档 贴心地告诉你 要将 UnoCSS 插件放在 @sveltejs/vite-plugin-svelte 前面,SvelteKit 同理。

如果你想要让 UnoCSS 的规则集也用上 SvelteKit 的 class:fooclass:foo={bar} 用法的话,那就多加一个 extractor 吧:

import { sveltekit } from "@sveltejs/kit/vite"
import UnoCSS from "unocss/vite"
import extractorSvelte from "@unocss/extractor-svelte"

/** @type {import('vite').UserConfig} */
const config = {
    plugins: [
        UnoCSS({
            extractors: [extractorSvelte()]
        }),
        sveltekit()
    ]
}

引入 uno.css

UnoCSS 并没有告诉我们要在 Svelte 或 SvelteKit 的哪个地方引入 uno.css (或者说人家根本没这个义务)。而 Svelte 每一个组件的 <style></style> 样式块中的样式都不会溢出这个组件,导入到特定一个组件是不可能的。

不过,我们可以创建一个 Layout(布局)、并在那里引入 uno.css,这样可以绕开上面提到的特性。让我们在 src/routes 下创建一个 +layout.svelte 组件:

<script lang="ts">
    import "uno.css"
</script>

<slot />

做完之后就可以开始在 SvelteKit 里尽情使用 UnoCSS 啦。

深浅色模式:svelte-theme-select

在用 Nuxt 重构我的博客时,我使用了 @nuxt/color-mode 来接管网站的深浅色模式。但现在是在 SvelteKit,自己写一套深浅色切换的代码是绝对不可能的,而且很大可能导致浏览器加载完毕才切换主题、导致用户看到一瞬间的闪烁。不过,SvelteKit 的插件也不少,这里我们就用 svelte-theme-select 来接管深浅色模式即可。

bun add svelte-theme-select

要引入 svelte-theme-select 的地方同样是 Layout 组件。由于我只需要其跟随用户偏好(一般是系统设置)切换深浅色主题,所以我只需要简单插入几行代码即可。

<script lang="ts">
    import "uno.css";
    import { createThemeSwitcher, Theme } from 'svelte-theme-select'; // 导入需要的内容
    createThemeSwitcher(); // 执行函数
</script>

<slot />

<Theme /> // 插入相关的主题切换 JavaScript 代码

同时,因为我使用了 UnoCSS 的默认预设,其包含了 TailwindCSS 的大部分规则集,所以可以按照 TailwindCSS 编写深色主题时的方法来即可:

<div class="bg-white dark:bg-black">Background</div>

数据获取与遍历:显示 GitHub 组织下所有人可见的仓库

在设计这个登陆页的时候,我突发奇想:要是能显示 GitHub 组织下所有人可见的仓库,那该多好!

事实是可以做到,通过 GitHub API 获取、在 SvelteKit 上 map 一下需要的信息即可。这里先贴上代码:

<script lang="ts">
    import { onMount } from 'svelte';

    interface Repo {
        name: string;
        html_url: string;
        description: string;
    }

    let repos: Repo[] = [];
    let isLoading = true;

    onMount(async () => {
        const response = await fetch('https://api.github.com/users/s-complex/repos');
        const data: Repo[] = await response.json();
        repos = data.map((repo) => ({
            name: repo.name,
            html_url: repo.html_url,
            description: repo.description
        }));
        isLoading = false;
    });
</script>

<hgroup>
    <h1>Project</h1>
    <p>List of projects that are visible to everyone in this lab.</p>
</hgroup>

{#if isLoading}
    <p>Loading...</p>
{:else}
    <div class=":uno: grid grid-cols-1 sm:grid-cols-2 gap-4">
        {#each repos as repo (repo.name)}
            <a href={repo.html_url}>
                <div class=":uno: px-4 py-2 shadow-md hover:shadow-lg rounded">
                    <h2>{repo.name}</h2>
                    <p>{repo.description}</p>
                </div>
            </a>
        {/each}
    </div>
{/if}

为了让访客能够知道数据获取的状态,我设定了一个 isLoading 值、默认是 true,它将在数据获取之后改为 false。在判断和数据遍历这方面和 Vue 将 if 判断和 for 遍历直接写在 HTML 代码或 <template></template> 模板块上不同,Svelte 是以 {#if}{#each} 包裹住需要的代码来运行。

Svelte Head

虽然 src 下有一个 app.html,但你打开之后会发现 <head></head> 这里不太寻常:

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  %sveltekit.head%
</head>

这是因为 Svelte 支持用 <svelte:head></svelte:head> 在各个组件定义头信息。我是直接简单粗暴的定义了一下:

<svelte:head>
    <title>Sliver Complex</title>
    <meta name="description" content="The landing site of Sliver Complex.">
    <link rel="icon" href="https://library.gxres.net/images/icons/favicon.webp">
</svelte:head>

这个登陆页暂时不需要动态标题,所以就不在各个页组件定义了。

预渲染(SSG)

因为各个托管平台有着不同的部署方式,所以 SvelteKit 一一做了对应的「适配器(Adapter)」。因为我只喜欢预渲染(SSG),而 SvelteKit 自带的 Auto Adapter 我不知道能不能 SSG,干脆就自己装一个了,反正也不麻烦。

bun add -D @sveltejs/adapter-static

接下来,我们要在 svelte.config.js 将原本的 Adapter 换成 SSG 的:

import adapter from "@sveltejs/adapter-static" // 换掉这一行就好了
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"

/** @type {import('@sveltejs/kit').Config} */
const config = {
    // Consult https://kit.svelte.dev/docs/integrations#preprocessors
    // for more information about preprocessors
    preprocess: vitePreprocess(),

    kit: {
        // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
        // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
        // See https://kit.svelte.dev/docs/adapters for more information about adapters.
        adapter: adapter()
    }
}

export default config

最后,在 src/routes 下添加一个 +layout.ts,并在里面写一行:

export const prerender = true

如果你需要 Fallback(比如 SPA),你可以改为 false

小结

最终的成品确实在我的期待之中。重点是:我又多学了一门前端框架(x

如果你需要一个轻量的框架、给一个轻量的项目使用,快来用 Svelte 吧非常好用.jpg

Loading Artalk...