Skip to content

Introducing the All New SvelteKit and SCSS Kit for starter.dev

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Introduction

At This Dot Labs, we love Svelte. We've even created a starter.dev kit for SvelteKit that you can use to scaffold your next frontend project using SCSS, TypeScript, Vitest and Storybook.

What is starter.dev?

Starter.dev helps developers get started building web apps with a variety of frameworks, showcasing how various libraries can fit together to solve similar problems. To do that, This Dot Labs has built a series of showcase apps that recreate the experience of using GitHub.

What is SvelteKit? How is it unique?

SvelteKit is a full-stack framework that gives you the best of both worlds: the page is server-side rendered on your first visit, but when you navigate to other pages, they are client-side rendered. SvelteKit gives you levers for your pages to use SSR (server-side rendering), CSR (client-side rendering), SSG (static site generation), SPA (single page application) & MPA (multi page application). The core of SvelteKit provides a highly configurable rendering engine.

Why SvelteKit and not Svelte?

SvelteKit isn’t built on top of Svelte, but it’s a backend web framework where Svelte is used as the view layer. In theory, you could rip it out and replace it with another component framework that supports server-side rendering, and the same is true for other web frameworks.

This allows us to deploy everything as a Node server, or even use Vercel and serverless functions.

Other reasons to use SvelteKit include:

  1. Pages (file based routing)
  2. Endpoints (API routes)
  3. Nested layouts (way more powerful than just nesting files because the segment of the URL maps to your component hierarchy)
  4. Hot module replacement (instant updates in the browser when you make a change preserving application state)
  5. Preprocessing (TypeScript, SCSS, and Pug among others)
  6. Building component libraries (creating and publishing npm packages)
  7. Deployment options (adapters for any platform)

Building a SvelteKit showcase presented several challenges given its uniqueness and different approach to building web apps. This blog post details what we chose to include in our SvelteKit GitHub clone, and how we integrated them.

Project Structures and Naming Conventions

SvelteKit-SCSS Project Structure

SvelteKit has unique conventions in its project structure and naming conventions.

Project Files

src

This is the meat of the project:

  • lib contains your library code, which can be imported via the $lib alias, or packaged up for distribution using svelte-package. It can be imported by using the $lib/* alias.
    • server contains your server-only library code. SvelteKit will prevent you from importing these in client code.
    • components contain single responsibility components that are imported in our routes. They typically contain three files: .spec.ts for unit tests, .svelte and .stories.ts for storybook stories. Barrel files were not necessary here.
    • images contains images that can be imported, and used in the project.
    • stores contains state that needs to be accessed by multiple unrelated components, or by a regular JS module.
    • styles contains styles that can be imported in our Svelte applications.
  • params contains any param matchers your app needs
  • routes contains the routes of your application
  • app.html is your page template — an HTML document containing the following placeholders:
    • %sveltekit.head% — link and script elements needed by the app, plus any head content
    • %sveltekit.body% — the markup for a rendered page. This should live inside a <div> or other element, rather than directly inside the body element, to prevent bugs caused by browser extensions injecting elements that are then destroyed by the hydration process. SvelteKit will warn you in development if this is not the case
    • %sveltekit.assets% — either paths.assets, if specified, or a relative path to paths.base
    • %sveltekit.nonce% — a CSP nonce for manually included links and scripts, if used
  • hooks.server.ts contains your application's hooks

static

Any static assets that should be served as-is, like robots.txt or favicon.png, go in here.

svelte.config.js

This file contains your Svelte and SvelteKit configuration.

tsconfig.json

Since SvelteKit relies on certain configuration being set a specific way, it generates its own .svelte-kit/tsconfig.json file, which your own config extends.

vite.config.js

A SvelteKit project is really just a Vite project that uses the @sveltejs/kit/vite plugin, along with any other Vite configuration.

Routing

To get a deep dive of how routing works with SvelteKit, please check out this article.

SCSS

SvelteKit supports a number of CSS preprocessors.

For people who are new to Svelte or SvelteKit, the syntax for using SCSS or SASS is simple, just need to add the lang="sass" attribute to the style tag.

    <style lang="scss">
        ul {
            list-style-type: circle;
            li {
                position: relative;
            }
        }
    </style>
    npm i -D sass

Then add SCSS support with the svelte-preprocess package.

// svelte.config.js

import adapter from "@sveltejs/adapter-auto";
import preprocess from "svelte-preprocess";

/** @type {import('@sveltejs/kit').Config} */
const config = {
  // Consult https://github.com/sveltejs/svelte-preprocess
  // for more information about preprocessors
  preprocess: preprocess({ typescript: true, scss: true }),

  kit: {
    adapter: adapter(),
  },
};

export default config;

Why SCSS and not Tailwind?

Tailwind CSS has been used by several other starter kits. We otherwise decided to go with SCSS as the syntax is simple and easily understood by even beginners.

Vitest

Test Driven Development (TDD) is one of the best ways to ensure your code works like it's supposed to work. It can also help you create reliable builds during continuous deployments.

Vitest is an up-and-coming testing framework which has similar functionality to Jest.

Since we are using Vite as our build tool for Svelte in this kit, Vitest has very good integration with Vite, and offers a similar testing environment without needing extra configuration.

To test Svelte components that seemed to be hard to test. Such as two-way bindings, name slots, Context API, etc., we need to add more configuration.

We added @testing-library/svelte, jsdom and @testing-library/jest-dom that allow for similar functionality as Jest.

npm i -D @testing-library/svelte @testing-library/jest-dom jsdom

We ensured the $lib alias is supported in our tests by resolving the alias in our vite.config.ts. We also added a setupTest.ts to add @testing-library/jest-dom matchers & mocks of SvelteKit modules.

// vite.config.ts

import { sveltekit } from "@sveltejs/kit/vite";
import type { UserConfig } from "vite";
import { configDefaults, type UserConfig as VitestConfig } from "vitest/config";
import path from "path";

const config: UserConfig & { test: VitestConfig["test"] } = {
  plugins: [sveltekit()],
  define: {
    // Eliminate in-source test code
    "import.meta.vitest": "undefined",
  },
  test: {
    // jest like globals
    globals: true,
    environment: "jsdom",
    // in-source testing
    includeSource: ["src/**/*.{js,ts,svelte}"],
    // Add @testing-library/jest-dom matchers & mocks of SvelteKit modules
    setupFiles: ["./setupTests.ts"],
    // Exclude files in c8
    coverage: {
      exclude: ["setupTest.ts"],
    },
    deps: {
      // Put Svelte component here, e.g., inline: [/svelte-multiselect/, /msw/]
      inline: [],
    },
    // Exclude playwright tests folder
    exclude: [...configDefaults.exclude, "tests"],
  },

  // Ensure the $lib alias is supported in our tests
  resolve: {
    alias: {
      $lib: path.resolve(__dirname, "./src/lib"),
    },
  },
};

export default config;
// setupTest.ts

/* eslint-disable @typescript-eslint/no-empty-function */
import matchers from "@testing-library/jest-dom/matchers";
import { expect, vi } from "vitest";
import type { Navigation, Page } from "@sveltejs/kit";
import { readable } from "svelte/store";
import * as environment from "$app/environment";
import * as navigation from "$app/navigation";
import * as stores from "$app/stores";

// Add custom jest matchers
expect.extend(matchers);

// Mock SvelteKit runtime module $app/environment
vi.mock("$app/environment", (): typeof environment => ({
  browser: false,
  dev: true,
  building: false,
  version: "any",
}));

// Mock SvelteKit runtime module $app/navigation
vi.mock("$app/navigation", (): typeof navigation => ({
  afterNavigate: () => {},
  beforeNavigate: () => {},
  disableScrollHandling: () => {},
  goto: () => Promise.resolve(),
  invalidate: () => Promise.resolve(),
  invalidateAll: () => Promise.resolve(),
  preloadData: () => Promise.resolve(),
  preloadCode: () => Promise.resolve(),
}));

// Mock SvelteKit runtime module $app/stores
vi.mock("$app/stores", (): typeof stores => {
  const getStores: typeof stores.getStores = () => {
    const navigating = readable<Navigation | null>(null);
    const page = readable<Page>({
      url: new URL("http://localhost"),
      params: {},
      route: {
        id: null,
      },
      status: 200,
      error: null,
      data: {},
      form: undefined,
    });
    const updated = {
      subscribe: readable(false).subscribe,
      check: () => false,
    };

    return { navigating, page, updated };
  };

  const page: typeof stores.page = {
    subscribe(fn) {
      return getStores().page.subscribe(fn);
    },
  };
  const navigating: typeof stores.navigating = {
    subscribe(fn) {
      return getStores().navigating.subscribe(fn);
    },
  };
  const updated: typeof stores.updated = {
    subscribe(fn) {
      return getStores().updated.subscribe(fn);
    },
    check: () => false,
  };

  return {
    getStores,
    navigating,
    page,
    updated,
  };
});

If you want to see some test recipes you can use on your SvelteKit projects, check out our SvelteKit-SCSS Github showcase.

Storybook

Like many of the other starter.dev kits, the SvelteKit starter uses Storybook to interactively view and build components in isolation.

For more information on Storybook and SvelteKit visit the article.

Linting

ESLint and Prettier are useful tools for keeping the project neat and consistent among multiple contributors.

To quickly format your project, run:

    npm run format

Running ESLint and Prettier as part of your git workflow is important because it helps you fail fast. This helps us, as contributors, to have a more consistent production codebase.

We achieved this in our SvelteKit-SCSS Github showcase with the help of Husky, lint-staged and Prettier.

How does lint-staged work? It's specifically designed to work on "staged" files, which are files you've changed or created, but haven't yet committed to your project. Working on staged files limits the number of files you need to lint at any given time, and makes the workflow faster.

We configured lint-staged in our package.json. This runs Prettier pre-commit, and ensures the code is up to our ESLint standards.

    // package.json
    "lint-staged": {
        "src/**/*{.js,.ts,.html,.css,.svelte}": [
            "prettier --plugin-search-dir . --write ."
        ],
        "src/**/*{.js,.ts,.svelte}": "eslint ."
    },

The commands you configure will run "pre-commit". As you're attempting to commit files to your project you'll see ESLint run in your terminal. Once it's done you may have successfully committed, or find yourself with linting errors you need to fix before you're able to commit the code.

This works hand-in-hand with husky. Husky uses distinct bash files with filenames that match the workflow step they correspond to, e.g. "pre-commit".

    #!/bin/sh
    . "$(dirname "$0")/_/husky.sh"

    cd svelte-kit-scss
    npx lint-staged

Running ESLint, or Prettier as part of your git workflow is important because it helps you fail fast, which helps contributors have a more consistent production codebase.

Conclusion

SvelteKit is a relatively new and novel framework. The structure represents our best judgment of how a basic SvelteKit application should be. We welcome everyone to take a look, and contribute back to the SvelteKit-SCSS and our SvelteKit-SCSS Github showcase if you have any improvements that you would like to propose!

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co