We recently had the opportunity to build a relatively small prototype application with the front-end JavaScript framework Svelte. Along the way, we took note of some issues we came across as Svelte beginners to help other developers that might come across similar issues.
Svelte Applications vs. Components
One of the features that drew us to using Svelte for our POC was that the Svelte framework is used at compile-time, not run time. The result of compiled Svelte apps are lightweight, optimized JavaScript not interpreted at runtime. Once compiled, all we need to run the application is the compiled JavaScript code itself, no other libraries. This encapsulated, dependency-free output makes sharing our Svelte application or component with other web pages, or larger micro front ends incredibly easy.
A quick note about Svelte Applications vs. Components: conceptually, one could think of a component as a set of code specific to one responsibility. You could, for example, have an authentication component responsible for all thing’s user authentication. On the other hand, we can think of applications as the glue that holds together a component set. For example, a Point of Sale Application might use our authentication component in conjunction with many other components to allow a sales associate to login and complete a transaction. How you differentiate between an application and component is really up to you.
Svelte application and a Svelte component
Technically speaking, there is no difference between a Svelte application and a Svelte component until you bundle your application. The de facto Svelte bundler is rollup.js. The rollup.js library works with the Svelte compiler to compile your app and add a warper to import the code into your project.
When we first started our POC, we found this blog, which explains how quickly get started with Svelte by using an application project template. After following the steps, we noticed that our application was immediately placing itself on the window and DOM and began executing once we loaded our compiled Svelte code with a script tag. This model is great for cases where your svelte app is running as a single page application. However, if you are looking to use Svelte as part of a micro front-end that interacts with other components on the page, you have no control over the start-up process. What does this mean exactly? Let’s look at an example:
Example Initialization Scenario
Let’s say we comprise our web page of several loosely based subcomponents, one of which is our Svelte application. Let’s also say the Svelte application needs initialization data before it can start up correctly. However, that data is provided by another subcomponent, componentA, which is asynchronous in nature. Because of the way Svelte apps are bundled, there is no way for us to delay the Svelte application from executing until componentA has returned its data. Instead, our Svelte app will boot up right away without its initialization data.
You can solve this problem by bundling your app as a component instead of a Svelte app. Doing so will add the component as a class to a namespace, which you can specify via a configuration instruction, on the window.
Once you are ready, you can create a new instance of the component and provide it with the necessary start-up data. The only real change needed (depending on your project setup) is a small rollup configuration change.
output: {
…
format: 'iife',
…
}
To:
output: {
…
format: 'umd',
…
}
Now that we have configured the bundler to bundle the application as a UMD module, it will appear on the window as a “class,” which you can initialize when it’s time.
A couple of call outs
https://github.com/sveltejs/template/ is a template project for Svelte applications. https://github.com/sveltejs/component-template/ is a template project for Svelte modules.
Rollup.js supports several output formats aside from UMD and IFFE. You can find the complete list of output formats supported by Rollup.js here.
Working with your Svelte Component
When working with a component, you will generally want to do three things with your component.
- Initialize the component
- Provide data to the component
- Listen for events from the component.
Svelte allows you to do all three. I’ve created a sample application to illustrate all three Svelte component techniques for you to review.
The slash-line app is an HTML page that uses two components (one Svelte, one web component) to display a batter’s slash line as it compares to the league average.
PlayerList
The web component PlayerList is responsible for, given a set of player data, displaying a list of player names. It is also responsible for calling a provided callback when a user clicks a player’s name.
slash-line-component
The Svelte Component slash-line-component is responsible for displaying the difference between a player’s slash line and the leagues’ average slash line for a specific set of player and league data. Slash line stats below the league average appear with a blue background, and stats above the league average are decorated with a red background. The Svelte component dispatches an event when a user clicks one of the stats.
The event will contain the player’s name, the clicked stat, and the difference between the player’s stat and the league average for that stat.
slash-line-app
The slash-line-app’s index.html is the glue that holds the components together. It:
- Provides the player data and click callbacks to the PlayerLists
- Passes the correct player information to the slash-line-component when a user clicks on a player in the PlayerList
- Displays an alert with the player/ league stat difference when the slash-line-component dispatches an event indicating a user clicked a stat
Initializing a component
One of the first things we want to do when we have a new component is to perform initialization. In our slash-line-app we do this first by loading our Svelte component with a script tag and providing a callback.
<script type="text/javascript" src="./components/slash-line/SlashLine.js" onload="onSlashLineComponentLoaded()"></script>
After the script is loaded, the lifecycle process calls the onSlashLineComponentLoaded method. It is here that we initialize our component.
let mySlashLineComponent = new window.SlashLine({
// initial set of props
props: {
baseSlashLine: league
},
// the target that the slash line component will be attached to.
target: document.getElementById("slashLine")
});
Notice the namespace of our SlashLine Component, window.SlashLine. As mentioned before, this namespace is specified as the name parameter here:
{ file: pkg.main, 'format': 'umd', name }
Our rollup config pulls this name from the package.json name field. The exact steps and options are very customizable and can be changed to fit your needs.
Now that the SlashLineComponent exists on the window, we can initialize it with the new keyword. We can also pass in some configuration values. In our case, the league averages will stay the same throughout the application. We can pass that data as an initial prop. We must also specify the element that this component will attach to using the target property.
At this point, our SlashLineComponent has is initialized, so how do we interact with it?
Passing data to our component at runtime.
Without any player data, our component has nothing to compare the league data with, so we will need to provide some data when a user clicks on a player. We do so here:
mySlashLineComponent.$$set({comparatorSlashLine: player});
The $$set method allows you to update props at runtime. Notice we provide the comparatorSlashline, and not the baseSlashLine.
$$set will only update the props that you provide and leave any other props the same. baseSlashLine will remain intact.
Now we have a way to provide data to the Svelte component. You may ask, how do we react to events dispatched from the Svelte component?
Listening to Svelte Events
While our SlashLine component’s primary focus is to display information about how a player stacks up to the league average, it also emits that information as an event when the user clicks a specific stat.
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
….
$:toClickHandler = (prop) => () => dispatch('statClicked', {name, stat:prop, difference: comparatorSlashLine[prop] - baseSlashLine[prop] })
Our slash-line-app listens for this by registering a callback with the $on method:
mySlashLineComponent.$on('statClicked', ({ detail })=> {
/*
In this case, we will listen to the statClicked event dispatched from our SlashLine when a user clicks on one of the stats displayed in the stat line component. When the event is dispatched, we display an alert showing the difference between the stat and league average for that stat.
*/
const { name, stat, difference } = detail;
const comparisonMessage =
difference >= 0 ? 'better than league average': 'worse than league average';
alert(`${name} is ${formatPercentage(difference)} ${comparisonMessage} ${stat}`);
});
In this example, we destructure the name, stat, and difference from the event’s details prop and display the information as an alert from the application level, not the Svelte component level.
Summary
To summarize, Svelte applications are handy as single-page applications. There are times when you don’t want a full application, but instead, a component that works as part of a broader architecture. In those cases, you can bundle your project as a Svelte component. This technique gives you control over when and how you initialize your project.
Svelte also allows you, as a developer, to interact with your application or component with many built-in methods. $set will enable you to update your app/component’s props, and $on allows you to register event handlers to respond to event dispatched from your app/component.