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:
This can also be used for attributes:
|
Svelte uses the same curly brace syntax as React / JSX:
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
|
Running code when a local state variable changes |
Various different ways:
|
For local variables, Reactive Statements are what you probably want
For |
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.
|
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:
|
Getting reference to an element inside the component |
Also, see: Docs: "Refs and the DOM" |
Use
|
Using Fragments (empty wrappers that don't output DOM nodes) |
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:
|
There are other approaches, but easiest is to wrap in a if block:
If you want to emulate the React ternary operator approach, just add an |
Rending Arrays |
Format your data as an array of However, you also have to make sure that each component within the JSX array also has a unique |
Use the Similar to React, you should pass a |
Children and Slots |
In React, children are automatically injected and passed via
To pass separate children to the same component, instead of using the automatic
|
For passing children, Svelte uses the 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 viaclassStr
(asclass
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 preservemyClass
. The downside is that it "pollutes" that component tree; if that class is used in any sub-components, the styles will also be applied...
- Trick: You can use
- Problem: If you have styles in the parent that are scoped to the class (e.g.
-
In the child, use
class="{$$props.class || ''}"
- This is not recommended due to optimization issues with
$$props
- This is not recommended due to optimization issues with
-
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>
- Example:
- Build the CSS string in the
<script></script>
section, then pass to template:- Example:
<div style={myStyle} ></div>
- Example:
- 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 usetypeof
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
- Props
- Although most common use is for passing data down, don't forget that Svelte also offers bi-directional property binding!
- Event Dispatching
- Context
- Store
- Props
- 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
andstore
is actually to use them... together! By passing astore
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 oron:
event bindings!
If you are looking for more info on context and store, here are some more resources:
- Flavio Copes: Cross-Component State Management
- Daniel Imfeld: How and When to Use Component Context in Svelte
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
- Now at svitejs/svite
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:
- Excellent template / demo repo: codechips/svelte-typescript-setups
- Make sure to check out their linked blog posts
- Svite's TypeScript examples
- Phaser 3 (TS + Vite)
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 withexport
(however, this is recommended against, for optimization reasons)
- You could use
- Pass through class names?
- See subsection of this doc - "Passing 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 thestyle:prop={val}
binding- You can even inline CSS variable declarations!
- 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
- Have an HTML attribute only output in the DOM if the JS variable being assigned to it is
== true
(i.e. do not outputmy-attribute="false"
)- For example, for
data-selected={selected}
, you might want thedata-selected
to only show up in the DOM ifselected === 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}
(ormy-attr={!!myVar || null}
, if you want to force boolean coercion).
- Easy approach:
- For example, for
- 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(){}
orexport const myFunc = () => {}
, and then usebind: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
- Best practice is to export the method in the child via
- How to make a prop optional
- Assign a default value when exporting it (e.g.
export let myProp = 5
)
- Assign a default value when exporting it (e.g.
- How to accept and render an array of elements via a prop
- See subsection: "Passing Components Around"
- 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
andreceive
transitions, combined with thecrossfade
function - Details:
- Svelte actually makes this so easy compared to a lot of other systems! You can use the
- 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"
- One way is to simply place those assets in
- How to directly inject HTML strings, equivalent to React's
dangerouslySetInnerHTML
, etc.- Instead of
{myHtmlStr}
, use{@html myHtmlStr}
- See Docs: "HTML Tags"
- Instead of
- 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
- The easiest way is to usually have your base component use the special
- Bind a numerical input to a state or store variable?
- Works out of the box - Svelte will handle the translation for you.
- Bind a checkbox input to a boolean variable / store?
- Use
bind:checked={myVar}
(details)
- Use
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
- You can re-assign to self to trigger
- See Docs: "Updating Arrays and Objects"
- 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
- If you have a computed value that you want refactored into a top-level function (e.g.
- Incorrect declaration of a reactive variable / computed property - e.g. using
let myVar = $myStore.prop
instead of$ myVar = $myStore.prop
- Incorrectly setting up computed properties
- 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 emptyon:{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 theonMount()
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 totrue
inonMount()
, and use{#if mounted}
to wrap your animated element
- Try adding a
- Entrance transitions (whether explicit, or implicit with something like
- 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
andy
)
- Some of the transitions can clash with various combinations of CSS positioning declared outside the transition. Instead of using a single
- 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 theoptimizeDeps.exclude
array invite.config.js
. Any dependencies that import from the Svelte library need to go in that array.
- If you are using Vite /
TypeError: Cannot read property '$$' of undefined
- I've gotten this when something has gone wrong with
node_modules
and versions of things got mixed
- I've gotten this when something has gone wrong with
- 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); }
- 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):
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
- You can use
- 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, runSvelte: restart language server
)
- If you are using the recommended VSCode extension (Svelte for VSCode), try putting your prettier config in
- Props are not being hinted correctly / intellisense is not working
- Install Svelte Intellisense extension for VSCode
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, turningsender: string
intosender?: 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
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
- Make sure you don't have an explicit file association for
- 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
)
- This is actually an error from
- Error:
JSX element type '___' does not have any construct or call signatures.
- Did you miss providing a
default export ___
which exports theSvelteComponent
?
- Did you miss providing a
SvelteKit
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 scaffoldedsvelte.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
)
- Alternatively, you can keep it as-is, or set it to whatever you want, by customizing the
- 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>
- At a bare minimum, this needs
- If you were using a
src/main.js
orsrc/main.ts
to bootstrap the app (e.g. withnew 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 specialsrc/routes/+layout.svelte
file
- If you had initialization logic here (setup stores, restore from localStorage, etc.) - you could put this in the
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 byhistory.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:
- The template files used for
npm create svelte@latest
- Can be worth even doing a
diff
on this directory between release commits - Also see
/shared
folder
- Can be worth even doing a
- Docs: Project Files
- SvelteKit CHANGELOG
- Rich Harris's Twitter 😅