Joshua's Docs - Svelte JS - Cheatsheet and Misc Notes

Resources

What & Link Type
Svelte - Official Docs Official Docs
SvelteKit - Official Docs Official (SvelteKit) Docs
Svelte - Official online REPL Interactive REPL
SvelteLab - Unofficial advanced online REPL Interactive REPL
Svelte - Official Tutorials
- Use menu to quickly switch between
- I've published a single-page compilation
Tutorials
Svelte - Quick Start Guide Guide
Svelte - v2 vs v3 Cheatsheet Cheatsheet
Twitter thread - SvelteJS differences, compared to ReactJS Twitter Thread / Cheatsheet
Flavio Copes: Free Svelte Handbook
- Online Version
- PDF Download
Guide / eBook
Joy of Code (posts, videos) Blog posts, guides, videos
My blog post: What Makes Svelte So Great – Features and Comparisons Blog Post

Basic Component Syntax

See Docs: "Svelte Components".

<script>
// Imports, exports, logic, assignments, reactivity, etc.
</script>

<!-- Markup (any number of elements, components, etc.) -->

<style>
/* Styles 🎨! */
</style>

Svelte is very flexible in its SFC approach; none of the sections are mandatory, and you can even have multiple top-level elements. No fragments necessary!

Svelte for React Devs

If you are coming from React (or Preact), you might find it helpful to see how methods and features you used in React align with Svelte built-ins (and are often, IMHO, much easier to use 😊):

What React Svelte
Using JS Values in Render

JSX uses curly braces for JavaScript expressions that should be evaluated (docs). Example:

const name = 'Joshua';
const nameTag = <p>Hello, my name is {name}</p>

This can also be used for attributes:

const name = 'Joshua';
const userPic = <img src="./profile.jpg" alt={`user ${name}`} />;

Svelte uses the same curly brace syntax as React / JSX:

<script>
	let name = 'Joshua';
</script>

<h1>Hello {name}!</h1>

However, many might prefer the shortcuts and added flexibility that Svelte offers around this syntax in comparison with React. For example, if your variable name is the same as the attribute you are assigning it to, you can skip writing attr={attr}, and just write {attr}:

<script>
	let src = 'https://via.placeholder.com/50';
</script>

<img alt="Placeholder" {src} />

Running code when a local state variable changes

Various different ways:

  • useEffect() hook
  • Relying on re-renders caused by state change
  • Using setState callback
  • Using class lifecycle hooks (componentWillUpdate, componentDidUpdate)
  • Using useMemo, if this is for a computed value

For local variables, Reactive Statements are what you probably want

$: {
	console.log(`name has been changed to ${name}`);
}

For store changes, you can use derived callbacks.

Computed properties or variables, derived from stateful sources

Similar to the options for running code when a local state variable changes, there are multiple ways to do this in React.

  • For an optimized hooks-based approach, you can use the useMemo hook to recomputed the derived value only when the specific inputs that the value is derived from change
  • For both hooks and class based approaches, you can recompute the value during every render() call. This might have a negative performance impact, especially if computing the value requires a complex calculation, so useMemo is preferred in those cases.

Rather then requiring special hooks or a separate API for derived values, in Svelte you can use the same reactive syntax for variables that are derived from changing values as you do for reactive statements in general (see section "Using Reactivity") - regardless of the number of variables it is derived from.

For example:

$: canTakeTheBus = userHasFare && busHasOpenSeat && userIsNearStop;
Getting reference to an element inside the component
  • Create a ref var
  • Bind / attach to element in your render section: <MyElement ref={myRef}/>

Also, see: Docs: "Refs and the DOM"

Use bind:this pattern on component. E.g:

<Bus bind:this={busComponent} >

Warning: The value of a variable that is bound to a component will actually be undefined until the component mounts, so you will need to wrap in onMount() if you are trying to use the variable early on.

Using Fragments (empty wrappers that don't output DOM nodes)

<React.Fragment>, or, simply <></>

Docs: React Fragments

Not necessary!

Unlike React, Svelte does not require a single parent node in each component; you can have multiple root-level nodes in Svelte, or even a completely empty component!

Conditional Rendering

Lots of different approaches.

Very common tricks:

// `&&` operator
{isLoggedOut && <p>Please Log In</p>}

// Ternary Operator
{isLoggedOut ? <p>Please Log In</p> : <p>Welcome back!</p>}

There are other approaches, but easiest is to wrap in a if block:

{#if isLoggedOut}
	<p>Please Log In</p>
{/if}

If you want to emulate the React ternary operator approach, just add an {:else} or {:else if} block, before the {/if} end statement.

Rending Arrays

Format your data as an array of jsx, and use it as {jsxArr} within render()

However, you also have to make sure that each component within the JSX array also has a unique key attribute - see docs.

Use the {#each} block syntax, such as {#each teamMembers as member}.

Similar to React, you should pass a key to allow for data updates.

Children and Slots

In React, children are automatically injected and passed via props.children. In JSX, you can then render the children like so:

const MyComponent = (props) => (
	<div>
	{props.children}
	</div>
);

To pass separate children to the same component, instead of using the automatic children prop, you can just use any named custom property - JSX can be passed via property without issue. E.g.:

<MyComponent
	title={<h1>My Blog Post</h1>}
	callToAction={<button>Subscribe!</button>}
/>

For passing children, Svelte uses the <slot> API. There is an automatic default slot, through which children can be passed and rendered.

To pass separate children, you can use named slots, or pass via props. For full details, see my section - "Passing Components Around"

Dynamically Rendering Components

If you are dynamically returning components as variables from the script section of a Svelte SFC, and want to dynamically render them, you cannot simply render them with brace syntax (like {myComponent}).

The way to do this is with the <svelte:component> element. (Tutorial).

The syntax is:

<svelte:component this={myComponentVar} />

Working with Images

I have a blog post on working with SVGs in Svelte, which also applies to general usage of images in Svelte.

The short summary is that your options are:

  • Put images in /public and then reference them by path
    • or
  • Put images in /src, and use a bundler

Using Reactivity

Svelte reactivity and state is very powerful and pleasant to use, but the syntax can take a little getting used to at first.

I'll summarize below, but the docs and tutorials really do the best job of explaining it.

My summary:

  • You get out-of-the-box reactivity with top-level variables that are:
    • Changed directly via assignment
  • For computed / derived properties, use $: to mark variables or entire statements as being reactive

Here is an example that shows both top-level reactivity, and the more advanced derived reactivity:

Show / Hide Example
<script>
	// This is *automatically* reactive!
	let count = 0;
	// Since the value of this variable is derived on another,
	// and not updated through assignment, we need to mark it
	// with the `$:` prefix to mark it as reactive
	$: isEven = count % 2 === 0;

	// If we have a whole bunch of logic that is derived
	// on value changes, we can carry it all out within a
	// single reactive statement block!
	let digits = 0;
	let divisibleByFour = false;
	let hex = `0x0`;
	$: {
		console.log(`count has changed`, count);
		digits = count.toString().length;
		divisibleByFour = count % 4 === 0;
		hex = `0x${count.toString(16)}`;
	}
</script>

<button on:click={() => count++}>Click Me!
</button>

<p>Button clicked {count} times.</p>

<p>Count is {isEven ? `` : `not `} even.</p>

<p>Length = {digits}, divisibleByFour = {divisibleByFour}, hex = {hex}</p>

Working with CSS - Styling and Class Names

CSS - Passing Through Class Names

This is a littler tricker than it may seem, due to Svelte's style scoping and class optimization. You can use any of:

  • In the child, use:

    <script>
    	let classStr = '';
    	export {classStr as class};
    </script>
    
    <div class={classStr}></div>
    • Problem: If you have styles in the parent that are scoped to the class (e.g. myClass passed via classStr (as class prop) they will be optimized out since the compiler thinks the class is unused within the parent
      • Trick: You can use :global(.myClass) {} in the parent to preserve myClass. The downside is that it "pollutes" that component tree; if that class is used in any sub-components, the styles will also be applied...
  • In the child, use class="{$$props.class || ''}"

    • This is not recommended due to optimization issues with $$props
  • For more tips and discussion, see Issue #2870

Dynamically Composing CSS

Instead of using a CSS-in-JS library, you might find that Svelte's templating system actually lets you do everything you need to do with CSS, including dynamically generating and applying styles.

There are a few tricks I can share as to more advanced CSS usage in Svelte:

  • Use expression syntax (braces) to build an inline CSS string
    • Example: <div style="background-color: {msgType == 'error' ? 'red' : 'green'}" >{msg}</div>
  • Build the CSS string in the <script></script> section, then pass to template:
    • Example: <div style={myStyle} ></div>
  • Attach a dynamic attribute to HTML, and then target that in your stylesheet
    <div data-msg-type={msgType} >{msg}</div>
    
    <style>
    [data-msg-type="error"] {
    	background-color: red;
    }
    [data-msg-type="success"] {
    	background-color: green;
    }
    </style>

Passing Components Around

The main ways to have a component accept other components, dynamically, is via either the Slot API or via props, with Slots being generally preferred.

Passing Components via Slots

The <slot> API is generally the easiest way to pass components (or elements) through, from parent to child.

E.g.:

<!-- Parent -->
<MyChild>
	<!-- Anything put here will get passed through as the default slot -->
	<p>Hello!</p>
</MyChild>


<!-- MyChild.svelte -->
<div>
	<slot>
</div>

There is a lot more to slots than just the default slot and above use-case. You can have named slots, a dynamic slot object ($$slots), and even bi-directional data-passing, with <slot let:name={value}> to pass values back to the parent.

Slots - Conditional Rendering

In the child component, if you want to render a fallback value if a slot value was not provided, the easiest approach is to just stick the value directly inside the slot:

<slot name="something">
	<p>This will only render if something is not passed in</p>
</slot>

However, you can also use $$slots.YOUR_SLOT_NAME to check for slot values. So this also works:

{#if $$slots.YOUR_SLOT_NAME}
	<!-- Anything you want -->
{/if}

The above can be very useful if you need to use conditional rendering in multiple spots, based on a single slot value, without actually rendering the contents of that slot multiple times.

Passing Components via Props

Once you have imported a Svelte component, you can actually pass it around just like any other value, including the use-case of passing it to another component via props.

I've put together a minimal but fully-functional example, within the Svelte REPL.

For a very verbose approach, I suppose you could pass actual DOM nodes (e.g. export let children: HTMLDivElement[];)

If you are using TypeScript, you will find some handy component types exported from the base of the main Svelte package. E.g. import type {SvelteComponent, SvelteComponentTyped} from 'svelte'. And remember to use typeof with them - e.g. type Component = typeof SvelteComponentTyped;

Passing Data Around

When passing data around your application, you typically have 4 main built-in options, plus browser globals

  • Svelte Built-Ins
  • Browser Built-Ins: global variables (window.myVar), persistence APIs (IndexedDB, localStorage, cookies), etc.

Props are fairly easy to understand, especially if you are coming from another framework like React or Vue, so I won't cover them in detail here (just use the Svelte docs, they are great!). However, context and store are a little more advanced.

Context vs Store

There are quite a few differences between context and store, although at a first glance they might not be clear. I've just started learning Svelte, so take this with a grain of salt, but I think this is a fair comparison:

👇 Context Store
Observable? No Yes
Reactive? No* Yes
Can exchange data with ___ components Data is sent from parent, downwards
(must be in same tree)
Any!

Summarizing another way, the main use of context is to share data downwards, in a component tree. Since it is not reactive, often it is used to pass something that does not change, such as a theme object set by a parent component.

On the other hand, store can be used to share data across your entire app (even outside of components!), and you can use it reactively to have components respond to changes, or observe mutations. Although the data being global is usually a benefit, sometimes context might be desirable if you want data isolated to a specific component tree.

* = 💡 Another way to use context and store is actually to use them... together! By passing a store object through context, it can be shared among a component tree by a parent and provide reactivity to child components!

💡 Don't forget that you can prefix store variables with $ to use them reactively without needing to set up a subscription / observer. You can even use this feature with input bindings or on: event bindings!

If you are looking for more info on context and store, here are some more resources:

Reloading and Hot Module Replacement (HMR)

To understand the difference between full reloads and Hot Module Replacement (HMR), I have a short explainer on my JS Bundler page.

The short version is that HMR only reloads the part of your app that has actually changed, and leaves everything else alone; this often means that app state is preserved, whereas in a full reload it is always destroyed.

Setting up HMR with Svelte used to be somewhat involved, but now with SvelteKit, it is provided out of the box! 🎉

I highly recommend just sticking with SvelteKit, but my old notes on the non-SvelteKit approaches are still below.

Non-SvelteKit / Legacy HMR Instructions

Prior to SvelteKit, we mainly had the the official Svelte template to work off of, which offered live-reloading but not HMR out-of-the-box.

To get HMR working with Svelte, without SvelteKit / legacy Svelte projects:

  • rixo/svelte-hmr (community plugin) + Bundler
    • You need to use this with a Svelte-enabled bundler; the README does a great job of breaking out how to do this for each bundler (Rollup, Webpack, Vite, Snowpack, etc.)
    • There is also svite, which is a pre-built wrapper around the combination of Vite + rixo/svelte-hmr

HMR with TypeScript

Using TypeScript with HMR is another layer of complexity on top of systems that are already kind of tricky. The best places to look for more info are:

Due to the complexities of setting up TypeScript + HMR + Dev Server + Bundling, most users might prefer to stick with Vite or Svite, both of which reduce boilerplate configs and streamline the whole process into one wrapper tool. I would recommend Svite if you are looking for the easiest way to quickly get HMR + bundling setup, with minimal configuration (example).

Vite now has an "out-of-the-box" / "create-app" template for Svelte + TypeScript! You can use npm init @vitejs/app my-svelte-app --template svelte-ts, see docs for details.

Misc / How Do I...

  • Pass through arbitrary attributes on a custom component, from parent to child?
    • You could use {...$$restProps} in the child to spread the props received, but not declared with export (however, this is recommended against, for optimization reasons)
  • Pass through class names?
  • Use CSS variables in style sections?
    • Go for it! You can totally use CSS variables in style blocks. But... keep in mind that Svelte's "scoping" will not magically scope your variables if you do something like :root { --myVar: ... }
    • It is often safer to dynamically build the CSS and deliver it through an inline style="" attribute, or the the style:prop={val} binding
      • You can even inline CSS variable declarations!
  • Have an HTML attribute only output in the DOM if the JS variable being assigned to it is == true (i.e. do not output my-attribute="false")
    • For example, for data-selected={selected}, you might want the data-selected to only show up in the DOM if selected === true - if it is false, the attribute should just not be there. However, this is not the default behavior of Svelte, unless the attribute you are using is A) a native HTML attribute it knows about and B) it knows is a boolean attribute
    • The docs call this out; you need to use a nullish instead of a falsy value if you want the attribute to not show
      • Easy approach: my-attr={myVar || null} (or my-attr={!!myVar || null}, if you want to force boolean coercion).
  • How to pass methods up through the component tree? (aka expose and call a function in a child component)
    • Best practice is to export the method in the child via export function myFunction(){} or export const myFunc = () => {}, and then use bind:this={variableToExportsFromChild} in the parent (component binding)
      • See stackoverflow.com/a/61334528/
      • âš  You will not be able to access the component variable until the component is mounted / rendered, so you might want to wrap in onMount() if trying to use immediately.
    • You could use dispatched events as an alternative, although that requires more boilerplate. However, dispatched events can also travel downwards in addition to upwards.
    • If you are just trying to pass data upwards, and not actual methods, you can always use prop binding in the parent
  • How to make a prop optional
    • Assign a default value when exporting it (e.g. export let myProp = 5)
  • How to accept and render an array of elements via a prop
  • How to move components between places (e.g. from one parent to another), with animation in-between?
    • Svelte actually makes this so easy compared to a lot of other systems! You can use the send and receive transitions, combined with the crossfade function
    • Details:
  • Use static assets (images, svgs, etc.)?
    • One way is to simply place those assets in /public, and then reference them via relative (or absolute without domain) paths / URLs
      • This will not catch typos, or optimize unused assets out of production
    • Another way is to use the processing power of Rollup, which is being used as the default bundler anyways. You can use any number of plugins to accomplish various import tasks, such as letting you import svg files as strings to use directly in code
      • See github.com/rollup/plugins
      • This is better than placing in /public, because Rollup can make sure only imported assets are included in the final bundle, and it should also catch typos / incorrect imports
      • For more advanced parts, see separate section below: "Working with Images"
  • How to directly inject HTML strings, equivalent to React's dangerouslySetInnerHTML, etc.
    • Instead of {myHtmlStr}, use {@html myHtmlStr}
    • See Docs: "HTML Tags"
  • Extend classes, use base template component, reuse component structure, etc.
    • The easiest way is to usually have your base component use the special slot API to accept children, and then using events, exported methods, etc., to pass the necessary things back up
  • Bind a numerical input to a state or store variable?
  • Bind a checkbox input to a boolean variable / store?
    • Use bind:checked={myVar} (details)

General Troubleshooting

  • UI is not updating after an array or object property is updated, which is used in template
    • Svelte's reactive binding is based on assignments; modifying arrays (or object properties) in-place will not trigger updates
  • My UI is not updating / re-rendering, when it should be!
    • For arrays and object properties, see above (those are special, since Svelte's reactivity is based on assignment)
    • Other than making sure that variables are getting updated by assignment, other things to look for are:
      • Incorrectly setting up computed properties
        • If you have a computed value that you want refactored into a top-level function (e.g. getImagePath), when using it inside the template having something like <img src={getImagePath()} /> will actually result in stale values if props change.
        • The correct way is usually to use reactive declarations, with the $: prefix syntax. For a complicated computed value, you can use braces to replace a function
      • Incorrect declaration of a reactive variable / computed property - e.g. using let myVar = $myStore.prop instead of $ myVar = $myStore.prop
  • My on:{evt} is not working on a custom component!
    • To attach event bindings to a custom component, you must use event forwarding
    • For on:click, and other DOM events, this is as easy as putting an empty on:{evt} on the element you want to forward from in the child (demo)
  • My component binding is not working (e.g. bind:this={component_instance})
    • component_instance will be undefined until the component is mounted / rendered. You can wrap with the onMount() lifecycle hook if you are trying to access during initialization
  • Entrance transitions are not working
    • Entrance transitions (whether explicit, or implicit with something like transition:fly) rely on the element's existence actually changing. If the element is always there, from the moment the component mounts, there is no entrance to animate.
      • Try adding a mounted = false variable, change to true in onMount(), and use {#if mounted} to wrap your animated element
  • Transitions that rely on coordinate position changes (e.g. transition:fly) don't seem to be working correctly
    • Some of the transitions can clash with various combinations of CSS positioning declared outside the transition. Instead of using a single transition:{transitionAnimation}, try switching to explicitly declaring each part (in:{transitionAnimation}, out:{transitionAnimation}), as well as explicitly declaring the in and out coordinates (x and y)
  • Transitions seem to be overlapping, and two elements are occupying the same space at the same time when one is entering and the other is exiting
    • This is tricky;
    • https://stackoverflow.com/q/59882179/11447682
    • https://stackoverflow.com/a/62398246/11447682
    • Trick: If you know there should only be one child, than you could use some fancy CSS selectors to hide anything after the first element, which should hide the entering or exiting element until it is in place as the only element
    • Trick: Force the transitioned element to take up 100% of width and height, with an opaque background, so it hides the overlapping entering element
  • Svelte Store, subscriptions, and/or Svelte lifecycle hooks are not working in some of my dependencies / libraries!
    • If you are using Vite / vite-plugin-svelte, you need to pay special attention to the optimizeDeps.exclude array in vite.config.js. Any dependencies that import from the Svelte library need to go in that array.
  • TypeError: Cannot read property '$$' of undefined
    • I've gotten this when something has gone wrong with node_modules and versions of things got mixed
  • Reactive statement is stuck in an infinite loop
    • Be really careful about top level variables / variable scoping inside reactive blocks, especially if they reference variables that are scoped to the SFC / file. For example, I have often found the following helps to get out of an infinite loop issue (probably related to this):
      // Instead of this...
      $: if (dependencyA && dependencyB) {
      	myGlobal = dependencyA;
      	doWork(dependencyB);
      }
      
      // Try this:
      $: if (dependencyA && dependencyB) {
      	handleUpdate(dependencyA, dependencyB);
      }
      
      function handleUpdate(dependencyA, dependencyB) {
      	myGlobal = dependencyA;
      	doWork(dependencyB);
      }

VSCode / IDE Support

  • Prettier is not playing nice with my .svelte files!
    • If you are using the recommended VSCode extension (Svelte for VSCode), try putting your prettier config in .prettierrc, instead of any other method (for example, in .vscode/settings.json)
      • You can use svelteSortOrder to control tag order
    • There is also a dedicated Prettier plugin - prettier-plugin-svelte
      • This comes bundled with the language server / VSCode plugin
    • Here is how the VSCode extension resolves Prettier formatting
    • After making or updating .prettierrc file, run Svelte: restart language server)
  • Props are not being hinted correctly / intellisense is not working

TypeScript

Make sure you follow the official guide on how to get setup with TS (mainly just running the TypeScript setup scripts / ejector).

You will probably also want to install and use @tsconfig/svelte

📘 Also important reading: Language Tools: TypeScript Support

Typing for component bindings

When using bind:this={componentRef} with TypeScript, you can strongly-type the componentRef variable by annotating it as the imported component. For example:

<script lang="ts">
	import MyComponent from './MyComponent.svelte';

	let componentRef: undefined | MyComponent;
</script>

<MyComponent bind:this={componentRef} />

TypeScript - Exporting and Importing Types from a Svelte SFC

Rather than using a shared type file (e.g. types.d.ts, or something like that), you might want to export a type that is created inside of a Svelte SFC (.svelte file).

If you try this within the normal script block, you will get this error:

Modifiers cannot appear here. If this is a declare statement, move it into <script context="module">..</script> ts(1184)

Here is how to use the context=module block:

BlueOrRedButton.svelte:

<script lang="ts" context="module">
	export type ColorOption = 'blue' | 'red';
</script>

<script lang="ts">
	export let color: ColorOption;
</script>

<button style="background-color: {color};"> Click Me! </button>

For our component that imports the above component and its type, normally you could combine both imports into one statement, like:

import RedOrBlueButton, { ColorOption } from './RedOrBlueButton.svelte';

Unfortunately, the above might cause this error:

Uncaught SyntaxError: The requested module '/src/RedOrBlueButton.svelte?import' does not provide an export named 'ColorOption'

And if you try to separate out the imports, you might get a new error: This import is never used as a value and must use 'import type' because 'importsNotUsedAsValues' is set to 'error'.. The least error-prone approach appears to be:

import type { ColorOption } from './RedOrBlueButton.svelte';
import RedOrBlueButton from './RedOrBlueButton.svelte';

TypeScript - Sharing Prop Types

TypeScript - Prop Types - via Common Shared Type Files

If you are OK with declaring your types outside of the SFC where they are being used, then a common shared type file is fairly manageable approach. You could setup ambient types (e.g. *.d.ts), which require no explicit imports and are magically available anywhere (but clutter the global type system), or use explicit import and export statements.

TypeScript - Extracting Prop Types via $$prop_def

Rather than exporting interfaces, and/or using global type declarations, there is actually a way to extract the types of a components props when you import it, for the purposes of static type checking. You can use the exposed $$prop_def property on the imported component instance.

For example, assume we have FancyButton.svelte, which has properties of icon, confirmText and palette, which we want to re-use within our wrapping component that is importing it, DeleteButton.svelte:

DeleteButton.svelte:

<script lang="ts">
	import FancyButton from './FancyButton.svelte';

	// We can reuse a prop of FancyButton as our *own* prop!
	export let icon: FancyButton['$$prop_def']['icon'];

	// We could also extract and use internally, or really do anything that we
	// can do with regular types
	type ConfirmTextWithFontStyle = FancyButton['$$prop_def']['confirmText'] & {
		fontStyle: CSSStyleDeclaration['fontStyle'];
	};

	// Exposing our combined type as a new prop
	export let deleteConfirmText: ConfirmTextWithFontStyle;
</script>

<FancyButton {icon} style="font-style: {deleteConfirmText.fontStyle};" confirmText={deleteConfirmText} />

Note: This relies on the internals of Svelte language tools, and should be used for type-checking only. This approach is also not the best for every situation.

TypeScript - Explicit Importing / Exporting of Prop Types

As an alternative to using $$prop_def, or a shared common types file, you can also export non-properties in a Svelte SFC, by using the <script context="module"> block.

In a TypeScript SFC, for sharing a prop type, that might look something like:

LaptopDisplay.svelte:

<script lang="ts" context="module">
	export interface Laptop {
		year: number;
		model: string;
		brand: string;
	}
</script>

<script lang="ts">
	export let laptop: Laptop;
</script>

<p>
	You have selected a {laptop.model},
	by {laptop.brand},
	manufactured in {laptop.year}.
</p>

For how to import the type, and how to use separate type imports / exports in general with Svelte SFCs, see the section above: "Exporting and Importing Types from a Svelte SFC".

TypeScript - Typing Custom Transition Functions

At the time of writing this, my preferred way of typing a custom transition function is like so:

import type { TransitionConfig } from "svelte/transition";

const myTransition = (node: HTMLElement, params: TransitionConfig): TransitionConfig => {
	return {
		...params,
		css: (t: number, u: number) => {
			// return CSS string
		}
		// or,
		// tick: (t: number, u: number) => { // do something }
	}
}

Make sure to review the docs: "Custom transition functions"

TypeScript - Interfaces as Attributes

If a component has a lot of different attributes, and those attributes are shared across a common interface, you might want to make your code more succinct by adopting a interface type and reusing it:

// Shared interface
interface ChatMessage {
	sender: string;
	receiver: string;
	sentAt: number;
	readAt?: number;
	message: string;
}
<script lang="ts">
	import { ChatMessage } from './types.ts';

	export let sender: ChatMessage['sender'];
	export let receiver: ChatMessage['receiver'];
	export let sentAt: ChatMessage['sentAt'];
	export let readAt: ChatMessage['readAt'];
	export let message: ChatMessage['message'];
</script>

However, as you can see above, this still involves a decent amount of repetitive code.

One easy and quick way to avoid all this boilerplate is to export an attribute that accepts the entire interface as an object, instead of breaking each property out by itself:

<script lang="ts">
	import { ChatMessage } from './types.ts';

	export let messageObj: ChatMessage;
	// You could optionally destructure immediately for easier use inside the SFC
	// E.g. `const {sender, message} = messageObj;`
	// ^ Warning: This is non-reactive
</script>

However, if you still want your component to have separated out attributes while reusing the shared interface, it gets a little tricker. For example consider the following:

<script lang="ts">
	import { ChatMessage } from './types.ts';

	export let {sender, receiver, sentAt, readAt, message}: ChatMessage = {} as ChatMessage;
</script>

On the surface, this seems to get us close, but unfortunately has two main issues

  • Error: ValidationError: Cannot declare props in destructured declaration
  • it makes every attribute accept undefined as an option, turning sender: string into sender?: string | undefined and making every attribute optional. This has to do with how Svelte treats default prop / attribute values.

I'm not sure if there is a current way to get around these issues, but I'll update this if I find one.

TypeScript - Generics

🚨 Experimental feature

Quick example:

<script lang="ts" generics="StrOptions extends readonly string[]">
	export let options: StrOptions;
</script>

Your options are:

<ul>
{#each options as option}
	<li>{option}</li>
{/each}
</ul>

TypeScript - Issues

💡 Tip: When something simple seems like it should be working, and it isn't, try running Svelte: restart language server. This often fixes things

  • Error: Type annotations can only be used in TypeScript files.
    • Make sure you don't have an explicit file association for *.svelte files in any (local or global) vscode settings.json files.
      • If you find one, remove it, then reload the entire window
    • Try restarting the language sever with the cmd: Svelte: restart language server
  • Svelte files not picking up global types (e.g. declared through ambient *.d.ts files)
    • If the file was just created, try restarting the language server
    • Make sure you have actually saved updates to the file, not just modified it. Unlike VSCode's JS/TS type checking system, which will use "dirty" files (unsaved modifications), the Svelte extension requires the file to be saved.
  • Error: (plugin typescript) Error: Could not load ___.ts (imported by ___.svelte): Debug Failure. False expression: Expected fileName to be present in command line
    • This is actually an error from rollup, which is the default bundler shipped with Svelte. Usually this means that the file you are trying to reference was not noticed by the "watcher" (tends to happen with brand new files that were created after the watcher was started)
    • If you are running in watch mode, just restart (kill & then run dev)
  • Error: JSX element type '___' does not have any construct or call signatures.
    • Did you miss providing a default export ___ which exports the SvelteComponent?

SvelteKit

SvelteKit Docs

Many SvelteKit issues are often issues with the underlying bundler (Vite). I have a few notes written up for Vite in my JS Bundlers page

Migrating to SvelteKit

There is already a detailed guide on migrating from Sapper to SvelteKit.

If you are migrating from a non-Sapper setup - e.g. an older plain Svelte project with or without Svite - here is what I would recommend doing. Follow the SvelteKit docs for scaffolding a new project from scratch, and do so in a separate directory from your project. Then, work to get your project directory to resemble the scaffolded one, in terms of structure, config, etc.

Additionally, here are some of the manual steps you will likely find yourself taking to migrate to SvelteKit from a non-Sapper Svelte project:

  • Add all the necessary dependencies, config files, lint files, commands, etc.
  • If you have been using a vite.config.js file, remove it, and merge the settings in it into a scaffolded svelte.config.js file (see "configuration" docs)
  • Rename /public to /static
    • Alternatively, you can keep it as-is, or set it to whatever you want, by customizing the kit.files.assets value in the Svelte config file (svelte.config.js)
  • Move /index.html (root template for Svite) to /src/app.html and fixup
    • At a bare minimum, this needs %svelte.head% in <head> and %svelte.body% in <body>
  • If you were using a src/main.js or src/main.ts to bootstrap the app (e.g. with new App({ target: document.body })), know that this doesn't do anything anymore - this paradigm doesn't work with the route-based setup
    • If you had initialization logic here (setup stores, restore from localStorage, etc.) - you could put this in the onMount of each route. Or, if you want it applied to every page, put it in the special src/routes/+layout.svelte file

Running code on page change within routes

One aspect of route components that tends to catch developers by surprise with SvelteKit is that route components don't fire re-mounting events on page change (e.g., when the URL / href changes). So, you can't use onMount for running code in a route whenever the user switches back to the page.

Instead, you can use global stores that track routing changes, such as page:

<script>
	import { page } from '$app/stores';

	window.pageLoadCount = window.pageLoadCount || 0;

	$: page && window.pageLoadCount++;
</script>

<p>Page loaded {window.pageLoadCount} times.</p>

This might change in the future - see the community discussion around "Resetting components on navigation".

🚨 Warning: page is reactive to Svelte routing changes, but not to pseudo page changes created by history.pushState().

If you want to subscribe to browser navigation, the popstate event is usually what you want

Upgrading SvelteKit

As a newer framework, SvelteKit has been going through lots of changes. These usually greatly improve DX, but unfortunately also might make upgrades slightly more complicated. I have found it is best to consult a few different places when upgrading to make sure things go smoothly, mostly focusing on which config files and primary APIs have changed:

Markdown Source Last Updated:
Sun Aug 11 2024 19:38:30 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Wed Sep 30 2020 03:51:18 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback