Skip to content

Harnessing the Power of Threlte - Building Reactive Three.js Scenes in Svelte

Introduction

Web development has evolved to include immersive 3D experiences through libraries like Three.js. This powerful JavaScript library enables the creation of captivating 3D scenes within browsers.

Three.js: The 3D Powerhouse

Three.js democratizes 3D rendering, allowing developers of all skill levels to craft interactive 3D worlds.

Svelte Ecosystem: Svelte Cubed and Svelthree

The Svelte ecosystem presents solutions like Svelte Cubed and Svelthree, which bridges Svelte with Three.js, offering streamlined reactivity for 3D web experiences.

Introducing Threlte v6: Uniting SvelteKit 1.0, Svelte 4, and TypeScript

Threlte v6 is a rendering and component library for Svelte that seamlessly integrates Three.js. By harnessing TypeScript's types, it provides a robust and delightful coding experience.

In this tutorial, we'll showcase Threlte's capabilities by building an engaging website header: an auto-rotating sphere that changes color on mouse down. Using Threlte v6, SvelteKit 1.0, and Three.js, we're set to create a visually stunning experience. Let's dive in!

Screenshot 2023-08-14 091949

Setting up Threlte

Before building our scene, we need to set up Threlte. We can scaffold a new project using the CLI or manually install Threlte in an existing project.

Option 1: Scaffold a New Threlte Project

Create a new SvelteKit project and install Threlte with:

npm create threlte my-project

Option 2: Manual Installation

For an existing project, select the necessary Threlte packages and install:

npm install three @threlte/core @threlte/extras @threlte/rapier @dimforge/rapier3d-compat @threlte/theatre @theatre/core @theatre/studio @types/three

Configuration adjustments for SvelteKit can be made in the "vite.config.js" file:

const config = {
  // ...
  ssr: {
    noExternal: ['three']
  }
};

With Threlte configured, we're ready to build our interactive sphere. In the next chapter, we'll lay the groundwork for our exciting 3D web experience!

Exploring the Boilerplate of Threlte

Upon scaffolding a new project using npm create threlte, a few essential boilerplate files are generated. In this chapter, we'll examine the code snippets from three of these files: lib/components/scene.svelte, routes/+page.svelte, and lib/components/app.svelte.

  1. lib/components/scene.svelte: This file lays the foundation for our 3D scene. Here's a brief breakdown of its main elements:

    • Perspective Camera: Sets up the camera view with a specific field of view and position, and integrates OrbitControls for auto-rotation and zoom management.
    • Directional and Ambient Lights: Defines the lighting conditions to illuminate the scene.
    • Grid: A grid structure to represent the ground.
    • ContactShadows: Adds shadow effects to enhance realism.
    • Float: Wraps around 3D mesh objects and defines floating properties, including intensity and range. Various geometrical shapes like BoxGeometry, TorusKnotGeometry, and IcosahedronGeometry are included here.
  2. routes/+page.svelte: This file handles the ui of the index page and imports all necessary components we need to bring our vibrant design to life.

  3. lib/components/app.svelte: This file is where you would typically define the main application layout, including styling and embedding other components.

Heading to the Fun Stuff

With the boilerplate components explained, we're now ready to dive into the exciting part of building our interactive 3D web experience. In the next section, we'll begin crafting our auto-rotating sphere, and explore how Threlte's robust features will help us bring it to life.

Creating a Rotating Sphere Scene

In this chapter, we'll walk you through creating an interactive 3D sphere scene using Threlte. We'll cover setting up the scene, the sphere, the camera and lights, and finally the interactivity that includes a scaling effect and color changes.

1. Setting Up the Scene

First, we need to import the required components and utilities from Threlte.

import { T } from '@threlte/core';
import { OrbitControls } from '@threlte/extras';

2. Setting Up the Sphere

We'll create the 3D sphere using Threlte's <T.Mesh> and <T.SphereGeometry> components.

<T.Mesh>
	<T.SphereGeometry args={[1, 32, 32]} />
	<T.MeshStandardMaterial color={`rgb(${sphereColor})`} roughness={0.2} />
</T.Mesh>
  1. <T.Mesh>: This is a component from Threlte that represents a 3D object, which in this case is a sphere. It's the container that holds the geometry and material of the sphere.

  2. <T.SphereGeometry args={[1, 32, 32]} />: This is the geometry of the sphere. It defines the shape and characteristics of the sphere. The args attribute specifies the parameters for the sphere's creation:

    • The first argument (1) is the radius of the sphere.
    • The second argument (32) represents the number of width segments.
    • The third argument (32) represents the number of height segments.
  3. <T.MeshStandardMaterial color={rgb(${sphereColor})} roughness={0.2} />: This is the material applied to the sphere. It determines how the surface of the sphere interacts with light. The color attribute specifies the color of the material. In this case, the color is dynamic and defined by the sphereColor variable, which updates based on user interaction. The roughness attribute controls the surface roughness of the sphere, affecting how it reflects light.

3. Setting Up the Camera and Lights

Next, we'll position the camera and add lights to create a visually appealing scene.

<T.PerspectiveCamera makeDefault position={[-10, 20, 10]} fov={15}>
	<OrbitControls
		enableZoom={false}
		enablePan={false}
		enableDamping
		autoRotate
		autoRotateSpeed={1.5}
	/>
</T.PerspectiveCamera>
<T.DirectionalLight intensity={0.8} position.x={10} position.y={10} />
<T.AmbientLight intensity={0.02} />
  1. <T.PerspectiveCamera>: This component represents the camera in the scene. It provides the viewpoint through which the user sees the 3D objects. The position attribute defines the camera's position in 3D space. In this case, the camera is positioned at (-10, 20, 10). The fov attribute specifies the field of view, which affects how wide the camera's view is.

    • makeDefault: This attribute makes this camera the default camera for rendering the scene.
  2. <OrbitControls>: This component provides controls for easy navigation and interaction with the scene. It allows the user to pan, zoom, and orbit around the objects in the scene. The attributes within the <OrbitControls> component configure its behavior:

    • enableZoom: Disables zooming using the mouse scroll wheel.
    • enablePan: Disables panning the scene.
    • enableDamping: Enables a damping effect that smoothens the camera's movement.
    • autoRotate: Enables automatic rotation of the camera around the scene.
    • autoRotateSpeed: Defines the speed of the auto-rotation.
  3. <T.DirectionalLight>: This component represents a directional light source in the scene. It simulates light coming from a specific direction. The attributes within the <T.DirectionalLight> component configure the light's behavior:

    • intensity: Specifies the intensity of the light.
    • position.x and position.y: Define the position of the light source in the scene.
  4. <T.AmbientLight>: This component represents an ambient light source in the scene. It provides even lighting across all objects in the scene. The intensity attribute controls the strength of the ambient light.

4. Interactivity: Scaling and Color Changes

Now we'll add interactivity to the sphere, allowing it to scale and change color in response to user input.

First, we'll import the required utilities for animation and set up a spring object to manage the scale.

import { spring } from 'svelte/motion';
import { onMount } from 'svelte';
import { interactivity } from '@threlte/extras';

interactivity();

const scale = spring(0, { stiffness: 0.1 });

onMount(() => {
	scale.set(1);
});

We'll update the sphere definition to include scaling:

<T.Mesh scale={$scale} on:pointerenter={() => scale.set(1.1)} on:pointerleave={() => scale.set(1)}>
	<T.SphereGeometry args={[1, 32, 32]} />
	<T.MeshStandardMaterial color={`rgb(${sphereColor})`} roughness={0.2} />
</T.Mesh>

Lastly, we'll add code to update the color of the sphere based on the mouse's position within the window.

let mousedown = false;
let rgb: number[] = [];

function updateSphereColor(e: MouseEvent) {
	if (mousedown) {
		rgb = [
			Math.floor((e.pageX / window.innerWidth) * 255),
			Math.floor((e.pageY / window.innerHeight) * 255),
			150
		];
	}
}
window.addEventListener('mousedown', () => (mousedown = true));
window.addEventListener('mouseup', () => (mousedown = false));
window.addEventListener('mousemove', updateSphereColor);

$: sphereColor = rgb.join(',');

We have successfully created a rotating sphere scene with scaling and color-changing interactivity. By leveraging Threlte's capabilities, we have built a visually engaging 3D experience that responds to user input, providing a dynamic and immersive interface.

Adding Navigation and Scroll Prompt in app.svelte

In this chapter, we'll add a navigation bar and a scroll prompt to our scene. The navigation bar provides links for user navigation, while the scroll prompt encourages the user to interact with the content. Here's a step-by-step breakdown of the code:

1. Importing the Canvas and Scene

The Canvas component from Threlte serves as the container for our 3D scene. We import our custom Scene component to render within the canvas.

import { Canvas } from '@threlte/core';
import Scene from './Scene.svelte';

2. Embedding the 3D Scene

The Canvas component wraps the Scene component to render the 3D content. It is positioned absolutely to cover the full viewport, and the z-index property ensures that it's layered behind the navigation elements.

<div class="sphere-canvas">
	<Canvas>
		<Scene />
	</Canvas>
</div>

3. Adding the Navigation Bar

We use a <nav> element to create a horizontal navigation bar at the top of the page. It contains a home link and two navigation list items. The styling properties ensure that the navigation bar is visually appealing and positioned correctly.

<nav>
	<a href="/">Home</a>
	<ul>
		<li>Explore</li>
		<li>Learn</li>
	</ul>
</nav>

4. Adding the Scroll Prompt

We include a "Give a scroll" prompt with an <h1> element to encourage user interaction. It's positioned near the bottom of the viewport and styled for readability against the background.

<h1>Give a scroll</h1>

5. Styling the Components

Finally, the provided CSS styles control the positioning and appearance of the canvas, navigation bar, and scroll prompt. The CSS classes apply appropriate color, font, and layout properties to create a cohesive and attractive design.

.sphere-canvas {
	width: 100%;
	height: 100%;
	position: absolute;
	top: 0;
	left: 0;
	z-index: 1;
}

nav {
	display: flex;
	color: white;
	z-index: 2;
	position: relative;
	padding: 4rem 8rem;
	justify-content: space-between;
	align-items: center;
}

nav a {
	text-decoration: none;
	color: white;
	font-weight: bold;
}

nav ul {
	display: flex;
	list-style: none;
	gap: 4rem;
}

h1 {
	color: white;
	z-index: 2;
	position: absolute;
	font-size: 3rem;
	left: 50%;
	top: 75%;
	transform: translate(-50%, -75%);
}

Head to the github repo to view the full code.

Check out the result: https://threlte6-spinning-ball.vercel.app/

Conclusion

We've successfully added navigation and a scroll prompt to our Threlte project in the app.svelte file. By layering 2D HTML content with a 3D scene, we've created an interactive user interface that combines traditional web design elements with immersive 3D visuals.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

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

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

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 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 `` 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. `scss ul { list-style-type: circle; li { position: relative; } } ` `sh npm i -D sass ` Then add SCSS support with the svelte-preprocess` package. `js // 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. `sh 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. `ts // 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; ` `ts // 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(null); const page = readable({ 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: `sh 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. `json // 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". `sh #!/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!...

A Deep Dive into SvelteKit Routing with Our Starter.dev GitHub Showcase Example cover image

A Deep Dive into SvelteKit Routing with Our Starter.dev GitHub Showcase Example

Introduction SvelteKit is an excellent framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing. At the heart of SvelteKit is a filesystem-based router. The routes of your app — i.e. the URL paths that users can access — are defined by the directories in your codebase. In this tutorial, we are going to discuss SvelteKit routing with an awesome SvelteKit GitHub showcase built by This Dot Labs. The showcase is built with the SvelteKit starter kit on starter.dev. We are going to tackle: - Filesystem-based router - +page.svelte - +page.server - +layout.svelte - +layout.server - +error.svelte - Advanced Routing - Rest Parameters - (group) layouts - Matching Below is the current routes folder. Prerequisites You will need a development environment running Node.js; this tutorial was tested on Node.js version 16.18.0, and npm version 8.19.2. Filesystem-based router The src/routes` is the root route. You can change `src/routes` to a different directory by editing the project config. `js // svelte.config.js / @type {import('@sveltejs/kit').Config} */ const config = { kit: { routes: "src/routes", // 👈 you can change it here to anything you want }, }; ` Each route directory contains one or more route files, which can be identified by their + prefix. +page.svelte A +page.svelte` component defines a page of your app. By default, pages are rendered both on the server (SSR) for the initial request, and in the browser (CSR) for subsequent navigation. In the below example, we see how to render a simple login page component: ` // src/routes/signin/(auth)/+page.svelte import Auth from '$lib/components/auth/Auth.svelte'; ` +page.ts Often, a page will need to load some data before it can be rendered. For this, we add a +page.js` (or `+page.ts`, if you're TypeScript-inclined) module that exports a load function. +page.server.ts` If your load function can only run on the server— ie, if it needs to fetch data from a database or you need to access private environment variables like API key— then you can rename +page.js` to `+page.server.js`, and change the `PageLoad` type to `PageServerLoad`. To pass top user repository data, and user’s gists to the client-rendered page, we do the following: `ts // src/routes/(authenticated)/(home)/+page.server.ts import type { PageServerLoad } from "./$types"; import { mapUserReposToTopRepos, mapGistsToHomeGists } from "$lib/helpers"; import type { UserGistsApiResponse, UserReposApiResponse, } from "$lib/interfaces"; import { ENV } from "$lib/constants/env"; export const load: PageServerLoad = async ({ fetch, parent }) => { const repoURL = new URL("/user/repos", ENV.GITHUBURL); repoURL.searchParams.append("sort", "updated"); repoURL.searchParams.append("perpage", "20"); const { userInfo } = await parent(); const gistsURL = new URL( /users/${userInfo?.username}/gists`, ENV.GITHUBURL ); try { const reposPromise = await fetch(repoURL); const gistsPromise = await fetch(gistsURL); const [repoRes, gistsRes] = await Promise.all([reposPromise, gistsPromise]); const gists = (await gistsRes.json()) as UserGistsApiResponse; const repos = (await repoRes.json()) as UserReposApiResponse; return { topRepos: mapUserReposToTopRepos(repos), gists: mapGistsToHomeGists(gists), username: userInfo?.username, }; } catch (err) { console.log(err); } }; ` The page.svelte` gets access to the data by using the data variable which is of type `PageServerData`. `html import TopRepositories from '$lib/components/TopRepositories/TopRepositories.svelte'; import Gists from '$lib/components/Gists/Gists.svelte'; import type { PageServerData } from './$types'; export let data: PageServerData; {#if data?.gists} {/if} {#if data?.topRepos} {/if} @use 'src/lib/styles/variables.scss'; .page-container { display: grid; grid-template-columns: 1fr; background: variables.$gray100; @media (min-width: variables.$md) { grid-template-columns: 24rem 1fr; } } aside { background: variables.$white; padding: 2rem; @media (max-width: variables.$md) { order: 2; } } ` +layout.svelte As there are elements that should be visible on every page, such as top-level navigation or a footer. Instead of repeating them in every +page.svelte, we can put them in layouts. The only requirement is that the component includes a ` for the page content. For example, let's add a nav bar: `html import NavBar from '$lib/components/NavBar/NavBar.svelte'; import type { LayoutServerData } from './$types'; export let data: LayoutServerData; // 👈 child routes of the layout page ` +layout.server.ts Just like +page.server.ts`, your `+layout.svelte` component can get data from a load function in `+layout.server.js`, and change the type from `PageServerLoad` type to LayoutServerLoad. `ts // src/routes/(authenticated)/+layout.server.ts import { ENV } from "$lib/constants/env"; import { remapContextUserAsync } from "$lib/helpers/context-user"; import type { LayoutServerLoad } from "./$types"; import { mapUserInfoResponseToUserInfo } from "$lib/helpers/user"; export const load: LayoutServerLoad = async ({ locals, fetch }) => { const getContextUserUrl = new URL("/user", ENV.GITHUBURL); const response = await fetch(getContextUserUrl.toString()); const contextUser = await remapContextUserAsync(response); locals.user = contextUser; return { userInfo: mapUserInfoResponseToUserInfo(locals.user), }; }; ` +error.svelte If an error occurs during load, SvelteKit will render a default error page. You can customize this error page on a per-route basis by adding an +error.svelte file. In the showcase, an error.svelte` page has been added for authenticated view in case of an error. `html import { page } from '$app/stores'; import ErrorMain from '$lib/components/ErrorPage/ErrorMain.svelte'; import ErrorFlash from '$lib/components/ErrorPage/ErrorFlash.svelte'; ` Advanced Routing Rest Parameters If the number of route segments is unknown, you can use spread operator syntax. This is done to implement Github’s file viewer. ` /[org]/[repo]/tree/[branch]/[...file] ` svelte-kit-scss.starter.dev/thisdot/starter.dev/blob/main/starters/svelte-kit-scss/README.md` would result in the following parameters being available to the page: `js { org: ‘thisdot’, repo: 'starter.dev', branch: 'main', file: ‘/starters/svelte-kit-scss/README.md' } ` (group) layouts By default, the layout hierarchy mirrors the route hierarchy. In some cases, that might not be what you want. In the GitHub showcase, we would like an authenticated user to be able to have access to the navigation bar, error page, and user information. This is done by grouping all the relevant pages which an authenticated user can access. Grouping can also be used to tidy your file tree and ‘group’ similar pages together for easy navigation, and understanding of the project. Matching In the Github showcase, we needed to have a page to show issues and pull requests for a single repo. The route src/routes/(authenticated)/[username]/[repo]/[issues]` would match `/thisdot/starter.dev-github-showcases/issues` or `/thisdot/starter.dev-github-showcases/pull-requests` but also `/thisdot/starter.dev-github-showcases/anything` and we don't want that. You can ensure that route parameters are well-formed by adding a matcher— which takes only `issues` or `pull-requests`, and returns true if it is valid– to your params directory. `ts // src/params/issuesearch_type.ts import { IssueSearchPageTypeFiltersMap } from "$lib/constants/matchers"; import type { ParamMatcher } from "@sveltejs/kit"; export const match: ParamMatcher = (param: string): boolean => { return Object.keys(IssueSearchPageTypeFiltersMap).includes( param?.toLowerCase() ); }; ` `ts // src/lib/constants/matchers.ts import { IssueSearchQueryType } from './issues-search-query-filters'; export const IssueSearchPageTypeFiltersMap = { issues: ‘issues’, pulls: ’pull-requests’, }; export type IssueSearchTypePage = keyof typeof IssueSearchPageTypeFiltersMap; ` ...and augmenting your routes: ` [issueSearchType=issuesearch_type] ` If the pathname doesn't match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404. Note: Matchers run both on the server and in the browser.` Conclusion In this article, we learned about basic and advanced routing in SvelteKit by using the SvelteKit showcase example. We looked at how to work with SvelteKit's Filesystem-based router, rest parameters, and (group) layouts. If you want to learn more about SvelteKit, please check out the SvelteKit and SCSS starter kit and the SvelteKit and SCSS GitHub showcase. All the code for our showcase project is open source. If you want to collaborate with us or have suggestions, we're always welcome to new contributions. Thanks for reading! If you have any questions, or run into any trouble, feel free to reach out on Twitter....

Integrating Storybook with SvelteKit, TypeScript, and SCSS cover image

Integrating Storybook with SvelteKit, TypeScript, and SCSS

Introduction Storybook is a great tool for testing, and visualizing your components in different states. Storybook allows teams to collaborate to develop durable UIs in isolation. It allows users to implement reusable components without fussing with data, APIs, or business logic. In this article, we will discuss how to integrate Storybook in a SvelteKit project with TypeScript, and SCSS support. Zero-config set up To get started, run the following in the root of an existing Svelte project: `shell npx sb@next init ` This detects the project type, installs @storybook/svelte, and adds some sample files to demonstrate the basics of Storybook. Running npm run storybook` gives you the following zero-config set up on `http://localhost:6006` Project Structure Our project structure is already set up by SvelteKit, and Storybook initialization has created a .storybook` folder. Still, we need to make some changes to the Storybook file extension, since our project is in TypeScript. This is a snippet of the folders in our project: ├── .storybook ├── main.cjs ├── preview.js └── preview-head.html ├── vite.config.ts └── src Add SCSS support To add SCSS support, we need the @storybook/preset-scss` addon. Install sass`, `@storybook/preset-scss`, and other relevant style loaders. `sh npm i -D sass @storybook/preset-scss css-loader sass-loader style-loader ` Navigate to storybook/main.cjs` Add @storybook/preset-scss` to the addons' array. `js addons: [ '@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions', '@storybook/preset-scss' // add here ], ` Add $lib alias support Navigate to .storybook/main.cjs`. Import mergeConfig` from Vite. This deeply merges two Vite configs. Import path`. The `path` module provides utilities for working with file, and directory paths. Finally, we need to resolve the $lib` to point to `../src/lib` for Storybook. `js async viteFinal(config) { return mergeConfig(config, { resolve: { alias: { $lib: path.resolve(dirname, '../src/lib') } } }); } ` Your .storybook/main.cjs` should contain the following: `js // .storybook/main.cjs const { mergeConfig } = require("vite"); const path = require("path"); module.exports = { stories: ["../src//*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], addons: [ "@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions", "@storybook/preset-scss", ], framework: { name: "@storybook/sveltekit", options: {}, }, docs: { autodocs: "tag", }, async viteFinal(config) { return mergeConfig(config, { resolve: { alias: { $lib: path.resolve(dirname, "../src/lib") }, }, }); }, }; ` Creating Stories We will start by creating a Greeting.svelte` file that receives a message from our server. `html export let message: string; SvelteKit Fetch Data from API Message: {message} .fetch-container { text-align: center; header { margin: 1.25rem auto; width: 40%; h1 { padding: 0.9375rem 0; font-size: 2rem; text-align: center; border-bottom: 5px solid #1d4ed8; } } div { font-size: 1.2rem; display: flex; justify-content: center; } } ` Then a Greeting.stories.ts` with a message argument: `ts import Greeting from './Greeting.svelte'; export default { component: Greeting, title: 'Example/Greeting', excludeStories: /.Data$/, argTypes: { message: 'from Storybook', }, }; const Template = ({ ...args }) => ({ Component: Greeting, props: args, }); export const Default = Template.bind({}); Default.args = { message: 'from This Dot’, }; ` Simply run npm run storybook` to see if your story is running: You can edit the argument to test different messages. Conclusion In this article, we learned how to set up Storybook in a SvelteKit project, and created our first story. If you are looking to bootstrap your next project, check out our starter kit that uses SvelteKit and SCSS. Thanks for reading! If you have any questions or run into any trouble, feel free to reach out on Twitter....

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels cover image

Being a CTO at Any Level: A Discussion with Kathy Keating, Co-Founder of CTO Levels

In this episode of the engineering leadership series, Kathy Keating, co-founder of CTO Levels and CTO Advisor, shares her insights on the role of a CTO and the challenges they face. She begins by discussing her own journey as a technologist and her experience in technology leadership roles, including founding companies and having a recent exit. According to Kathy, the primary responsibility of a CTO is to deliver the technology that aligns with the company's business needs. However, she highlights a concerning statistic that 50% of CTOs have a tenure of less than two years, often due to a lack of understanding and mismatched expectations. She emphasizes the importance of building trust quickly in order to succeed in this role. One of the main challenges CTOs face is transitioning from being a technologist to a leader. Kathy stresses the significance of developing effective communication habits to bridge this gap. She suggests that CTOs create a playbook of best practices to enhance their communication skills and join communities of other CTOs to learn from their experiences. Matching the right CTO to the stage of a company is another crucial aspect discussed in the episode. Kathy explains that different stages of a company require different types of CTOs, and it is essential to find the right fit. To navigate these challenges, Kathy advises CTOs to build a support system of advisors and coaches who can provide guidance and help them overcome obstacles. Additionally, she encourages CTOs to be aware of their own preferences and strengths, as self-awareness can greatly contribute to their success. In conclusion, this podcast episode sheds light on the technical aspects of being a CTO and the challenges they face. Kathy Keating's insights provide valuable guidance for CTOs to build trust, develop effective communication habits, match their skills to the company's stage, and create a support system for their professional growth. By understanding these key technical aspects, CTOs can enhance their leadership skills and contribute to the success of their organizations....