Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Svelte

General capabilities

create a starter svelte project

npx degit sveltejs/template my-svelte-project
cd my-svelte-project
npm install
npm run dev

vscode setting

Command Palette (⇧⌘P) then: Preferences: Configure Language Specific Settings,

{
  "[svelte]": {
    "editor.defaultFormatter": "svelte.svelte-vscode",
    "editor.tabSize": 2
  }
}

Svelte Browser compatibility

Svelte components

Svelte Component exported functions

Comp1.svelte file.

<script>
    import Comp2, {mod_param as C2param} from 'Comp2.svelte';

    console.log(C2param.someText);
    C2param.someFunc("Comp2 function called from Comp1");
</script>

Comp2.svelte file.

<script>
  // This is component instance context
...
</script>

<script context="module">
  // This is component module context
  export const mod_param = {
    someFunc: (text) => {
      console.log(text);
    },

    someText: "hello from Comp2" 
  }
</script>

Exchanging data between components module and instance context is tricky So appropriate handling required in such case. It is best to use REPL sandbox JS output window to check the exact use case.

Component lifecycle

Components,

  1. are instantiated and composed inside parents.
  2. are initialized by parents via parameters. Parameters are passed by value.
  3. display information and interact with user.
  4. manage user interaction via state transitions.
  5. make the user entered information accessible to parents via bindings. Binding keeps parent's variable in sync with components variable. Binding makes the parameter behave like as if they were passed by pointers.
  6. send events to parent so that parent can take actions. Parent uses event handlers.
  7. are destroyed by parents when they are no longer needed.

A parent can get component state both via binding and events. Bindings provide easy reactive update in parents. Events provides an easy way when algorithmic action is required by parent. Using both as appropriate is the best approach.

Check Managing state in Svelte.

Passing parameter to sub-component

Sub-components act like functions javascript function with parameters, pass parameters individually or all the parameters as a dictionary using a spread operator. You can have default values inside the sub-component.

  <Comp1 param1={data} />
  <Comp2 {...selprops} />

Using spread operator is preferred for large number of parameters.

Check Passing parameters to components.

Binding

Binding is exactly same as passing individual parameters, except you have to attach the bind keyword in the association.

  <Comp1 bind:param1={data} />

Binding will keep component parameter param1 and parent variable data always in sync. When components updates param1 it will be immediately reflected in data. Parent can bind data with multiple components and they all will be in sync as well.

There is no spread style binding syntax supported.

There is a short hand syntax available for binding in the case when parameter name and variable name are the same.

  <Comp1 bind:same_name={same_name} />
  <!-- short hand syntax for above -->
  <Comp1 bind:same_name />

Check component bindings.

Events

Parent.svelte source,

<script>
	import Comp1 from './Comp1.svelte';

	function handleMessage(event) {
		alert(event.detail.text);
	}
</script>

<Comp1 on:event_name={handleMessage}/>

Comp1.svelte source,

<script>
	import { createEventDispatcher } from 'svelte';

	const dispatch = createEventDispatcher();

	function sayHello() {
		dispatch('event_name', {
			text: 'Hello!'
		});
	}
</script>

<button on:click={sayHello}>
	Click to say hello
</button>

To bubble up component message parent can forward it further up, see event forwarding.

develop reusable components

using REPL, Github and npm

todo: write up

Generating web components with svelte

Styling

Generated content and global styling in production build

Svelte allows css support of generated content with global styling. However it (with postcss and purgecss) removes any css that is not being used by the static content during production build, if it is loaded from a css file using postcss-include facility even it is a global style. During development build this doesn't happen though since we don't run purgecss in development build. To ensure those styles are retained we need to tell purgecss about those. For instance if we used a highlighter such as highlight.js which prepends highlight classes with hljs- prefix. Then we can retain styling for them by adding a whitelist pattern /hljs-/

in postcss.config.js the same way as it is done for svelte in their official svelte template.

const purgecss = require('@fullhuman/postcss-purgecss')({
  content: ['./src/**/*.svelte', './src/**/*.html'],
  whitelistPatterns: [/svelte-/, /hljs-/],
  defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
})

Transitions, Animations

state management

Contexts vs. stores

Contexts and stores seem similar. They differ in that stores are available to any part of an app, while a context is only available to a component and its descendants. This can be helpful if you want to use several instances of a component without the state of one interfering with the state of the others.

In fact, you might use the two together. Since context is not reactive and not mutable, values that change over time should be represented as stores:

const { these, are, stores } = getContext(...);

Svelte markdown editor

using processing p5.js in svelte

Routing

SPA & Search Engine Optimization (SEO)

Svelte with firebase

Login data passing with context API

Firebase can have has it own login subscription via rxfire. However following articles are good read to understand svelte support data sharing across the app.

Web components

A Svelte component can be compiled into a web component as documented in Custom element API, read this carefully.

At the moment this is an incomplete feature. There are many issues as described in the following parts of this section. If you are not dependent on them (kebab casing and global: css) you can make web components.

There should be two ways we can interact with web component technology,

  • Instantiate web component in a svelte component. Check this as a starting point, https://youtu.be/-6Hy3MHfPhA.
  • instantiate a svelte component in a web component or a existing web page like a web component.

Compile

To convert your existing Svelte app into a web component, you need to,

  • Add <svelte:options tag="component-name"> somewhere in your .svelte file to assign a html tag name.
  • Tell the svelte compiler to create web component by adding customElement: true option. However using this way would turn every svelte file it compiles to a web component. In a typical project however you might one only the top svelte file compiled to be a web component and all other files to integrated in the top level file. This can be done using customElement option with a filter.

Following is a filter example that only makes web component when the file has .wc.svelte extension.

// rollup.config.js
svelte({ customElement: true, include: /\.wc\.svelte$/ }),
svelte({ customElement: false, exclude: /\.wc\.svelte$/ }),

Global CSS file

Using global CSS from a file should be done carefully if they are not scoped properly. They should be avoided inside a component, all styles should be defined inside the Svelte components so that svelte compiler scope them properly. Using class name to scope CSS is the right approach if needed. For web components svelte will inline the CSS. Note that, CSS with global: specifier for generated content is needed since svelte will remove any unused CSS otherwise. But this does not work in Custom element right now.

A workaround proposed is to use webpack ContentReplacePlugin,

plugins: [
	new ContentReplacePlugin({
        	rules: {
		      '**/*.js': content => content.replace(/:global\(([\[\]\(\)\-\.\:\*\w]+)\)/g, '$1')
	          }
	})
]

A side note, CSS doesn't leak thru shadow DOM which is used for web components.

Property name

The property names should be restricted to lower case, camel case and kebab case should not be used as they have complications. However if you must, check https://github.com/sveltejs/svelte/issues/3852. The solution proposed is to create a wrapper,

import MyCustomComponent from './MyCustomComponent.svelte';

class MyCustomComponentWrapper extends MyCustomComponent {
  static get observedAttributes() {
    return (super.observedAttributes || []).map(attr => attr.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase());
  }

  attributeChangedCallback(attrName, oldValue, newValue) {
    attrName = attrName.replace(/-([a-z])/g, (_, up) => up.toUpperCase());
    super.attributeChangedCallback(attrName, oldValue, newValue);
  }
}

customElements.define('my-custom-component', MyCustomComponentWrapper);

MyCustomComponent.svelte

<script>
  export let someDashProperty;
</script>

<svelte:options tag={null} />

{someDashProperty}

Then you can use it in this way:

<my-custom-component some-dash-property="hello"></my-custom-component>

There were variants of this can be used in the bundler to have a automated wrapper injection done at the build time.

If you use esbuild bundler instead of rollup, following would work.

Original Svelte component like this:

<!-- src/components/navbar/navbar.wc.svelte -->

<svelte:options tag="elect-navbar" />

<!-- Svelte Component ... -->

Create a customElements.define

/* src/utils/custom-element.js */

export const customElements = {
  define: (tagName, CustomElement) => {
    class CustomElementWrapper extends CustomElement {
      static get observedAttributes() {
        return (super.observedAttributes || []).map((attr) =>
          attr.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase(),
        );
      }

      attributeChangedCallback(attrName, oldValue, newValue) {
        super.attributeChangedCallback(
          attrName.replace(/-([a-z])/g, (_, up) => up.toUpperCase()),
          oldValue,
          newValue === '' ? true : newValue, // [Tweaked] Value of omitted value attribute will be true
        );
      }
    }

    window.customElements.define(tagName, CustomElementWrapper); // <--- Call the actual customElements.define with our wrapper
  },
};

Then use esbuild inject option to inject the above code to the top of the built file

/* esbuild.js */

import { build } from 'esbuild';
import esbuildSvelte from 'esbuild-svelte';
import sveltePreprocess from 'svelte-preprocess';

// ...
    build({
      entryPoints,
      ourdir,
      bundle: true,
      inject: ['src/utils/custom-element.js'], // <--- Inject our custom elements mock
      plugins: [
        esbuildSvelte({
          preprocess: [sveltePreprocess()],
          compileOptions: { customElement: true },
        }),
      ],
    })
// ...

Will produce web component like this:

// components/navbar.js

(() => {
  // src/utils/custom-element.js
  var customElements = {
    define: (tagName, CustomElement) => {
      // Our mocked customElements.define logic ...
    }
  };

  // Svelte compiled code ...

  customElements.define("elect-navbar", Navbar_wc); // <--- This code compiled by Svelte will called our mocked function instead of actual customElements.define
  var navbar_wc_default = Navbar_wc;
})();



Todo: We will explore this in more details in future.

Events

Read MDN events article. The read-only composed property of the Event interface returns a Boolean which indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM.

// App.svelte
<svelte:options tag="my-component" />
<script>
	import { createEventDispatcher } from 'svelte';
	const dispatch = createEventDispatcher();
</script>

<button on:click="{() => dispatch('foo', {detail: 'bar', composed: true})}">
    click me 
</button>

Develop with Vite

Vite provides very nice response time during development cycle, compile is really fast with vite as it doesn't bundle at development time. For production build it uses rollup as usual with svelte and bundles everything together. It also has a nice plugin for kebab casing support for svelte component property names.

I am not sure if we make a web component with this plugin, the web component will support kebab casing. But using web component with kebab casing in Svelte can be done with this.