Building a project with Sapper, a JavaScript app framework

Is there a bomb?

If someone read my last blog post about how Svelte can help in JavaScript projects and now cannot resist of building an app using this new JavaScript framework (instead of React) but at the same time really needs server-side rendering, probably will also need Svelte app maker aka Sapper.
Sapper is totally inspired by Next.js, providing all the advantages we already know like:

  • server-rendered pages by default,
  • offline support,
  • automatic code splitting for faster page loads,
  • simple, files based client-side routing.

But of course there are some differences that bring further advantages:

  • Each page of the application is a Svelte component, so the application should be faster and smaller as Svelte compiles component to highly optimized vanilla JS and brings us the promise of building extremely high-performance web apps.
  • As using route masking was so common practice in Next.js, Sapper introduced encoding route parameters in filenames. It seems that on so many occasions the Svelte/Sapper team faced “commonly used pattern to solve some problem” and provided a nice shorthand for that.
  • There is no need to use additional framework-specific (like <Link>) components for navigation. Links are just <a> elements, that works with the router as expected.

How to start building JavaScript app with Sapper?

The easiest way to start new Sapper application is to use a scaffold available on github (you will need of course node.js already installed). In a terminal just type:

npx degit sveltejs/sapper-template#rollup my-app

or

npx degit sveltejs/sapper-template#webpack my-app.

You can change my-app to whatever name you want. Then go to my-app directory, run npm install to install dependencies and start the development server (npm run dev) that will be available at localhost:3000.

What is inside?

The structure of the project looks rather simple. There are only package.json and [rollup/webpack].config.js files and static and src directories. The first one will contain static files (like images and global styles) for your app and later one is where the core of your app exists. When you run Sapper for the first time, it will also add __sapper__ directory that will contain all generated files.
In the src directory, among the others, there is template.html file that contains document’s template for pages served from the server. Sapper will inject e.g. component-specific <head>, <style> and <script>  using following placeholders:

  • %sapper.base%
  • %sapper.styles%
  • %sapper.head%
  • %sapper.html%
  • %sapper.scripts%.

Leaving the configuration and entry points files just like they are we can focus on editing files in routes and components directories and check how our application will change.

Let’s build a simple JavaScript app with Sapper

During one of our DevCollege workshops, we build an application that shows how to use React with Redux and next.js. The idea of this simple project would be also used here to get familiarized with Sapper and Svelte and to compare Sapper with next.js.
The application consists of three views:

  • a first view is a form for defining search parameters,
  • a second view sends a request to an external API (there a plenty of them you can choose, we’ve used this one) and displays the search results as a list of clickable elements,
  • click on an element will display the third view with element’s details.

The project’s structure looks like this:

├ package.json
├ src
│ ├ components
│ │ ├ Button.svelte
│ │ ├ Card.svelte
│ │ ├ Header.svelte
│ ├ routes
│ │ ├ _error.svelte
│ │ ├ _layout.svelte
│ │ ├ [id].svelte
│ │ ├ index.svelte
│ │ └ list.svelte
│ ├ client.js
│ ├ server.js
│ ├ service-worker.js
│ └ template.html
├ static
│ ├ global.css
└ webpack.config.js

Similar like in Next.js we need to add a .svelte file for each page to the src/routes folder. Those pages are Svelte components that will be, as fast as possible, server-rendered when a user's first visit to the app. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel. 
Filename determines the page’s route - src/routes/index.svelte is the root of our site and src/routes/list.svelte [or src/routes/list/index.svelte if you prefer this style] will be served on /list route. Sapper brings us the possibility to encode routing parameters in filenames by using [brackets]. So we can do both - either name the file for the third view details.svelte and use it with not really nice /details?id=... route or name the file [id].svelte and the page will be served on /:id route with no route masking neither on the side of the server nor the client. To achieve the same behavior with next.js we needed to make additional router configuration in e.g. express to handle HTTP requests in the way described above.
You can also create files and directories with a leading underscore in the name and Sapper will not create routes for them. You can notice two such files in src/routes - two special pages  _error.svelte and _layout.svelte. The first one is shown when an error occurs while rendering a page and has the same role as _error.js file in Next.js. In Next.js we can create _app.js page to, among the others, persist layout between page changes. The good practice is to create some kind of <Layout> component to hold a structure of the page and put in _app.js. _layout.svelte is another example when a good practice was introduced to the standard. This component is meant to hold repeatable elements and it contains <slot> element that acts as a placeholder for changing content dependent on routes.
The components directory contains all reusable components we would like to use in our project, not related to a specific route.

Page example

Let’s take a closer look at the content of [id].svelte file. First, you can notice <script> tag with context property. Regular <script> block in Svelte component runs when each component instance is initialized. The one with context=”module” will run code outside of any instance before the component is created. That is why it is a perfect place to make some initial data fetch requests and call preload function which can remind you getInitialProps from Next.js.

<script context="module">
export async function preload(page) {
const { id } = page.params;

const res = await this.fetch(`https://api.scryfall.com/cards/${id}`);
const data = await res.json();
if (res.status === 404) {
this.error(404, 'Not found');
}
return { data };
}
</script>

The page argument of the preload function is { path, params, query } object:

  • path - the URL’s pathname,
  • params - derived from path and filename route, there we can find id param
  • query - an object of values from a query string

The returned object will be passed to the component as props.

<script>
import Card from '../components/Card.svelte';

export let data;

const colors = {
B: 'Black',
G: 'Green',
R: 'Red',
U: 'Blue',
W: 'White',
};
</script>

In the regular <script> tag you can notice a little bit strange export let data statement, which is an example of component’s props declaration. Every variable that is declared or imported on the top level in <script> tag is available from the template. The one that is declared with export keyword is also exposed outside the component and can be set from the outside.

<style>
img {
display: block;
width: 100%;
height: auto;
border-radius: .3rem .3rem 0 0;
}
</style>

<style> tag contains styles encapsulated for a particular component so the styles defined here for img will affect only details page and no other img that can be found in your app. Unless you use :global(img) selector. 

<svelte:head>
<title>Card details</title>
</svelte:head>

<svelte:head> is a place to define all meta elements for the page, like title, description and all the other SEO important stuff. All those elements will be injected into template.html file mentioned before.

And finally, there is some HTML code with Svelte specific directives and bindings.

{#if data}
<Card>
<div slot="image">
{#if data.image_uris}
<img src={data.image_uris.art_crop} alt="card art" />
{:else}
<img src="https://via.placeholder.com/200" alt="art placeholder"/>
{/if}
</div>
<h5 slot="header">{data.name}</h5>
<p slot="meta">{`Released at ${data.released_at}`}</p>
<div slot="description">
<p>{data.oracle_text}</p>
{#if data.mana_cost}
<p>
{`Mana cost: ${data.mana_cost}`}
</p>
{/if}
{#if data.eur}
<p>
{`Price: ${data.eur}`}
</p>
{/if}
{#each data.color_identity as color}
<p>{colors[color]}</p>
{/each}
</div>
</Card>
{/if}

In the _layout.svelte file you could notice <slot> tag, that Svelte use to indicate the place, where children should be rendered. Svelte provides also a mechanism to distribute children over component layout using named slots. In the <Card> component there are several <slot> tags with defined property “name”. Please notice the slot=”...” properties added to some of the tags in the HTML above, that correspond to named <slot> elements in <Card> component definition.

The navigation on the client side is done with the regular <a> tag. However, Sapper adds to that optional prefetch that will be triggered when a user hovers on the link. To call preload function sooner than user clicks the link and visits the page, you need to add rel=prefetch attribute to the link. 

Example from list.svelte where the list of clickable elements is rendered:

{#each data as card (card.id)}
<a rel=prefetch href={`/${card.id}`}>
{ card.name }
</a>
{/each}

And if you need to make some navigation outside the template you should use provided by the framework goto(href) method, that programmatically navigates to the specified href. For destinations being Sapper routes, the navigation will be handled by the framework and for external destinations, the navigation will proceed as page reload to the new address. Check the example from index.svelte:

<script>
import * as sapper from '@sapper/app';
import Button from '../components/Button.svelte';

let searchTerm = '';

function handleSubmit() {
sapper.goto(`/list?query=${searchTerm}`);
}
</script>

<form on:submit|preventDefault={handleSubmit}>
<input
type="text"
bind:value={searchTerm}
/>
<Button disabled={!searchTerm} type="submit">
Submit
</Button>
</form>

Do we need Sapper?

This is in fact question Do we need Svelte? As long as we are going to use React we have Next.js - both well documented, with a lot of examples, wide community support and ready to go UI components libraries like react-semantic-ui.
But when we decide to switch to Svelte than of course, Sapper should be our choice. With the release of Svelte 3 we can now consider it as a more adult JavaScript framework, but Supper is now available in 0.26 version so it is still in an early state of the project. Most of the energy was lately put into developing Svelte 3 and Sapper is right now build with the power of new Svelte but I think we just need to wait a bit for stable full 1.0 version.

Do we need Svelte?

You could read lately on our blog that Svelte brings, among the others, the ‘write less code’ promise, that allows build components with no boilerplate using just HTML, CSS and JS. Look at this custom Button component example. 

<script>
export let disabled = false, type;
</script>

<style>
button {
/* custom styles */
}
</style>

<button {disabled} {type} on:click>
<slot></slot>
</button>

This code looks almost like an initial sketch. But it works, it contains all the necessary elements:

  • JS part containing only props declarations,
  • CSS part that in this case can be the biggest one, depending on how fancy your button should be,
  • HTML part with property and event bindings reduced to a minimum
  • {disabled} stands for disabled={disabled}
  • although component events don’t bubbly by default, on:click without any assignment automatically forwards this event to the parent and there is no need to define and dispatch any additional events
  •  <slot> element as a placeholder for children.

This is only one of the Svelte advantages and as a whole, it looks very promising so I’m quite sure sooner or later we are going to use Svelte together with Sapper more often.

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .