Lab 4: Svelte (Templating & Control Flow)
In this lab, we will learn:
- What is npm?
- What are JS frameworks and why are they useful?
- What is Svelte and how does it compare to other frameworks?
- First steps with Svelte: templating and control flow
Table of contents
- Lab 4: Svelte (Templating & Control Flow)
- Check-off
- Lab 4 Rubric
- Prerequisites
- Slides
- What to Expect When You’re Svelting
- Step 1: Setting up
- Step 2: Porting your previous website to Svelte
- Step 3: Publishing our new website to GitHub Pages
- Step 4: Templating projects from a data file
- Step 5: Displaying the first 3 projects on the home page
- To be continued…
- Resources
Check-off
You need to come to TA Office Hours to get checked off for this lab (any of them, no appointment needed), OR submit your work asynchronously by filling out this form.
If you choose to submit your work asynchronously and have an incorrect or incomplete part of the lab, you will not receive any credit for the lab (we do not offer partial credit on labs). You may not resubmit this form nor ask for a synchronous check off for the same lab.
Lab 4 Rubric
To successfully complete this lab check-off, ensure your work meets all of the following requirements:
- Same functionality from Labs 1-3, just ported to SvelteKit excluding the theme switcher.
- Succesfully deployed to GitHub Pages.
- Correct file directory (pages are named correctly and placed in the correct folders).
- Home page includes a subset of projects with corrected heading sizes.
Prerequisites
- You should have completed all the steps in Lab 0, i.e. that you have Node.js and npm installed. You will not need the local server from Lab 0, as SvelteKit will provide its own.
- This lab assumes you have already completed Lab 1, Lab 2, Lab 3 as we will use the same website as a starting point.
Slides
What to Expect When You’re Svelting
Unlike the previous labs, this lab will not involve dramatic changes to the end-user experience of our website. In terms of user-facing changes, we will only be adding a section of the 3 selected projects to the home page, and displaying a count of projects.
However, we will be completely re-architecting its internals to make it much easier to make changes and evolve it over time.
Step 1: Setting up
Step 1.1: Creating a new blank Svelte/SvelteKit project
In this lab, we will be creating a new repository for our website, and then gradually importing our existing website into it. Decide on a name for your new repository (I called it my-portfolio
below).
Open your website folder with VS Code. Open the integrated terminal, and type cd ..
to go to its parent folder.
In this class, we will only be using Svelte 4. Be careful when installing or working with Svelte, as the latest Svelte 5 update will not be compatible with the instructions of this lab. It’s imperative to follow the instructions for terminal commands exactly in this lab, so you are only installing Svelte 4 and its compatible dependencies. Later on, you can check what version you are using by running npm list svelte
in your command line.
Now, run the following in the terminal:
npm create svelte@4 my-portfolio
The npm create
command creates a new project using a template. Note that my-portfolio
is the name of the new folder that will be created, so you may want to tweak it accordingly (just make sure to pick a different name than that of the folder containing your existing website!).
You may initially be prompted to install Svelte 4 if you haven’t installed it previously. You can just select y here to indicate you would like to install it.
Then, for the questions shown below, use the up and down arrows on your keyboard to navigate. You will want to select Skeleton project, No, and then press Enter without selecting anything for the last question. You can follow the video shown below for this step!
You should now have a new folder called my-portfolio
that is right next to the folder containing your old website.
Step 1.2: Creating a new repository for our new project
Using GitHub Desktop, select File → Add Local Repository… create a new repository for this folder by selecting it from the Choose
option.
GitHub Desktop will warn you that there is no repository in the folder and ask if you want to create one. You should say yes.
Then, press “Publish repository” and publish it to GitHub.
Step 1.3: Installing dependencies
Then open your new project in VS Code (Repository → Open in Visual Studio Code).
Open the VS Code terminal and run:
npm install && npm install -D svelte@4 && npm install -D @sveltejs/adapter-static@2
This will install all the dependencies for your new Svelte project. If &&
is not valid on your machine, feel free to run each npm install
command separately. Be patient, it can take a while!
What does this command do? Let’s break it down:
&&
separates different terminal commands, so this is actually three separate commands, each of which will be run in sequence:
npm install
reads dependencies frompackage.json
and installs the packages listed there.npm install -D svelte@4
will replace the Svelte version already installed with the Svelte 4 (which we need for modern CSS support and to adapt from the differences of the latest version of Svelte 5).npm install -D @sveltejs/adapter-static@2
will install the static adapter for SvelteKit v1, which we will use to deploy our website to GitHub Pages and remains compatible with Svelte 4.
Once npm install
finishes, run:
npm run dev -- --open
This will start a local server on port 5173
and open http://localhost:5173/
in your default browser.
You should see something like this:
As a bonus, we don’t need to refresh the page to see changes anymore! Vite (used by SvelteKit under the hood) implements hot reloading, i.e. it will automatically reload the page when we save a file. You could even arrange VS Code and the browser side by side and see your changes in real time!
Step 2: Porting your previous website to Svelte
Now we will start porting the website we creted in Labs 1-3 to Svelte, piece by piece.
Step 2.0: Moving your assets
First, copy your images/
folder as well as style.css
and global.js
to static/
. You can do this in your own File Explorer or in VS Code!
Step 2.1: Skeleton HTML
Open src/app.html
. This contains skeleton HTML for every page on your website. Notice some expressions in %...%
. These are special variables that Svelte will replace with the actual content of your pages at build time. Do not remove them!
Edit the <head>
in src/app.html
to add:
- A default
<title>
- Your existing CSS via
<link rel="stylesheet" href="%sveltekit.assets%/style.css" />
- Your existing JS via
<script src="%sveltekit.assets%/global.js" type="module"></script>
Step 2.2: Porting your pages to routes
“Routing” is the process of determining what content to display based on the URL. To do that, SvelteKit uses a routes
directory, with +page.svelte
files for each page. These are actually components, so everything you know about component syntax applies to them. However their special name tells SvelteKit that they are meant to be used as pages.
Open the routes
directory. You will see a single file called +page.svelte
. This is your home page.
Svelte pages represent a route and defines the structure, logic, and styling for a page. Each consists of three main parts in chronological order:
- A
<script>
element for Javascript logic and data. - Its HTML to display content.
- A
<style>
element for CSS that will only apply to that page.
Replace its contents with the contents of your homepage from your old site, i.e. the contents of the <body>
element in your root index.html
.
If you get an error like <tag_name> is a void element and cannot have children, or a closing tag
, this just means that in Svelte, you don’t need a closing tag for <tag_name>
elements. You can resolve this error by deleting the closing tag </tag_name>
. With this fix, the website should now compile!
View your website and verify this worked, then do the same for your other pages:
projects/index.html
→routes/projects/+page.svelte
resume/index.html
→routes/resume/+page.svelte
contact/index.html
→routes/contact/+page.svelte
Again, for each of them only copy the HTML between <body>
…</body>
, excluding the <body>
opening and closing tags.
View your website. It should largely look the same as before, but it is now a SvelteKit app!
Step 2.3: Adding titles
Notice that while our website looks largely the same, the title displayed on the browser tab is the default for every page.
Svelte allows us to provide elements in a <svelte:head>
element that will be used to insert elements into the <head>
of the page at build time. This is placed after the <script>
element (if present) and at the top of the HTML portion of the file.
For example in the Contact page it could look like this:
<svelte:head>
<title>Contact me</title>
</svelte:head>
Add titles to your all your pages this way.
Step 2.4: Adjusting navigation bar URLs
Note that our links work without us having to handle the home page in any special way. Why is that? This has to do with how Svelte’s server works: it does not let us add /
at the end of URLs (note that if you try to go to e.g. http://localhost:5173/projects/
you will be redirected to http://localhost:5173/projects
) so every relative URL is interpreted as relative to the same folder.
The browser doesn’t know anything about your directory structure, so it doesn’t know that projects/
is a directory. It figures out what is a file and what is a directory entirely from the URL. So when you visit something like https://username.github.io/portfolio/projects
, it will just treat projects
as a file in the same directory — a file with no extension. This means that if we did want to add subpages (e.g. projects/viz/
), we would have to do some work to handle this, but let’s cross that bridge when we get to it.
To ensure the correct link is marked as .current
, you should delete the trailing slash at the end of your relative URLs in global.js
.
You should also change the URL of the homepage to .
or ./
, as it should not be empty: an empty URL is interpreted as equal to the current page URL.
While you’re at it, you may as well delete the code that checks whether we are in the home page and adjusts URLs.
This is referring to the code you added in Step 3.1 of Lab 3 in global.js
involving the variable ARE_WE_HOME
.
Why does my current
page class not update when I navigate to another page? We will fix this bug with layouts in Svelte, which we learn in Lab 5 next week!
Step 3: Publishing our new website to GitHub Pages
Now that we have a website that functions like our old one, it’s a good time to publish it to the Web.
Deploying to GitHub Pages is a little more complicated than in the previous labs, because we now have a build process. As we edit our website, SvelteKit generates a bunch of files from our source code and stores them in a .svelte-kit
folder. It is generally considered a bad practice to commit automatically generated files (“build artifacts”) to a repository, as makes every edit correspond to several other edits we didn’t make, complicating and bloating the commit history.
Instead, we want to ignore these files when committing to our repo (that’s why .svelte-kit
is already in our .gitignore
file) and tell GitHub to generate those files again on its side.
To run custom logic before our site is deployed, GitHub provides a feature called GitHub Actions, which we will use.
If you are working out of an MIT GitHub Enterprise account, GitHub Actions is not included in this. Make sure to port your repo to a personal account!
Before you proceed, commit and push your changes, then on github.com, enable GitHub Pages on your repo selecting “Github Actions” as the source (Repo settings → Pages → Source: GitHub Actions).
SvelteKit has a detailed guide on how to deploy to GitHub Pages. Copy the code linked below for the following files:
.github/workflows/deploy.yml
(you will need to create a.github
folder in your root directory — note the dot — and aworkflows
folder inside it)svelte.config.js
(this will replace the Svelte config file already in your project).
You can also click on the file paths above to download the files, if that’s more convenient for you.
Commit and push these changes to your repo.
If all goes well, your app should be deployed to YOUR_USERNAME.github.io/REPO_NAME
. To see our GitHub Action deployed, navigate to the “Actions” tab on your GitHub repo. You’ll be able to see both actions that are currently running, as well as past ones.
Step 4: Templating projects from a data file
In the previous labs, we were using a hardcoded blob of HTML to display our projects. This is not ideal: if we want to change the HTML for our projects, we have to do it N times, where N is the number of projects we have. Now, it is true that if we design our HTML well, we should be able to change its style without changing its structure, but there are many changes we may want to make that would require changing the structure of the HTML. And even the most well written HTML is no help when we want to display the same data in multiple ways. For example, what if we wanted to display our projects on the homepage as well? Or provide a data file for others to use? Or draw a visualization of them? Maintaining our data together with its presentation tends to become painful, fast.
Step 4.1: Creating a JSON file with our project data
We will use the browser console to extract the data from our HTML to JSON so that if you have edited your HTML to contain your actual projects, you don’t lose your data. The following code assumes you have used the same structure for your projects as what was given in the previous labs, where the list of projects was within a <div class="projects>
and each project looked like this:
<article>
<h2>Project title</h2>
<img src="path/to/image.png" alt="" />
<p>Project description</p>
</article>
Load your Projects page in the browser and open the Dev Tools Console. Paste the following code into it and hit Enter:
$$(".projects > article").map((a) => ({
title: $("h2", a).textContent,
image: $("img", a).getAttribute("src"),
description: $("p", a).textContent,
}));
Inspect the array returned by the code and make sure it looks like what you expect.
If you’re happy with it, right click on it and select “Copy object”.
Create a new file in src/lib/
called projects.json
and paste the JSON there.
Having trouble?
If you’re having trouble with the above steps, you can use thisprojects.json
file as a starting point. Step 4.2: Importing our project data into our Projects page
Create a <script>
element at the top of src/routes/projects/+page.svelte
and import the JSON file:
<script>
import projects from "$lib/projects.json";
</script>
No need for type="module"
, Svelte processes these <script>
elements in a special way anyway.
Print it out on the page to make sure everything worked by adding <pre>{ JSON.stringify(projects, null, "\t") }</pre>
anywhere outside the <script>
element, e.g. under our heading. If it worked, you should see something like this:
Delete this debug code, and let’s use the data to display our projects in a more …presentable way.
Step 4.3: Templating our project data
First, delete or comment out all your <article>
elements inside the <div class="projects">
element except the first one.
Then, add an {#each}
block block around it to iterate over the projects.
<div class="projects">
{#each projects as p}
<article>
<h2>Lorem ipsum dolor sit.</h2>
<img src="https://vis-society.github.io/labs/2/images/empty.svg" alt="" />
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Magnam dolor quos,
quod assumenda explicabo odio, nobis ipsa laudantium quas eum veritatis
ullam sint porro minima modi molestias doloribus cumque odit.
</p>
</article>
{/each}
</div>
If you view your website at this point, you should see the same project repeated as many times as there are projects in your JSON file.
Now replace its title with {p.title}
, its image’s src
with {p.image}
, and its description with {p.description}
and view your website.
Note that we don’t need quotes to use an expression as an attribute value in Svelte, i.e. we can do src={p.image}
, we don’t need to do src="{p.image}"
, though the latter can be helpful for combining expressions with static text, e.g. href="/{ p.url }"
. Read Svelte’s full docs on attributes and props to learn more.
It should look the same as before, but now your projects are templated from a JSON file! Try making an edit to your JSON file and see if it reflects on your website.
Step 4.4: Counting projects
A big bonus of this approach is that we can use code to compute things from the data, and have it update automatically when the data changes. Try it: add a count of projects at the top of the page by adding { projects.length }
in the <h1>
element.
Step 5: Displaying the first 3 projects on the home page
We will now display the first 3 projects on the home page. We could do this by copying the project template from the Projects page and pasting it into the home page. However, this means that if we want to change it (e.g. add a date), we’d need to change it in two places.
That’s precisely what components are for!
Components encapsulate an independent piece of UI, and can be reused across your app. Each component lives in a single .svelte
file and consists of three parts:
- Its JavaScript (placed inside a
<script>
element) - Its HTML (placed directly inside the file). As we have seen, this is not plain HTML, but it has superpowers: it can contain expressions and logic.
- Its CSS (placed inside a
<style>
element). Any CSS you write here is transformed to only apply to that component, even if you use very generic selectors.
The +page.svelte
files you created in Step 2 are also components!
Step 5.1: Creating a <Project>
component
We will create a <Project>
component that will take the project data as input so that we can use it anywhere we want like this:
<Project data={project} />
Standard Svelte naming conventions include PascalCase (capitalize first letters) for Components and +lowercase for Pages.
Start by copying the <article>
element and its contents into a Project.svelte
file in src/lib
.
In larger projects, components are placed in a lib/components
directory to distinguish them from other files in lib
. However, we’ll stick to a shallow directory structure for now to keep things simple.
Then add a <script>
element to the top of the same file with:
export let data = {};
Prefacing our variable with export
will allow us to recognize data
in other files. let
is used instead of const
here because it allows for the values to be set from outside, creating dynamic updates and allowing the variable to be reupdated later.
This defines what prop name other files use to pass data to our component and what the default value is (in this case an empty object). Props are values passed to components to then be utilized in those components.
We should now change all expressions in our template to use data
instead of p
(e.g. { p.title }
becomes { data.title }
).
Step 5.2: Using the <Project>
component
Now that we’ve created our component, let’s use it!
First, we’ll use it on the Projects page routes/projects/+page.svelte
. To make it available to the page, we need to import it in the <script>
element at the top of the file:
import Project from "$lib/Project.svelte";
Then, we can replace the <article>
element and its contents with:
<Project data={p} />
Step 5.3: Using the <Project>
component on the home page
Now that we’ve used the Project
component on the Projects page, we can use it on the home page as well.
Copy the <script>
element and its contents from the Projects page and paste it at the top of the home page, since we’ll need exactly the same imports: the project data and the Project
component.
Then to display the first 3 items we can use an {#each}...{/each}
block very similar to that of the Projects page, just using projects.slice(0, 3)
instead of projects
.
{#each projects.slice(0, 3) as p}
<Project data={p} />
{/each}
You should also add a heading of a suitable level (e.g. “Latest projects”) and wrap the three projects in a <div class="projects">
element so that they get the same styling (you may want to add another class too, e.g. <div class="projects highlights">
to style them a little differently there).
Want to display selected projects rather than the first three? You can use a new array like [projects[0], projects[3], projects[7]]
instead.
Step 5.4: Customizing heading levels
Notice that we used an <h2>
element for the heading of our projects on the home page, but we are also using <h2>
for the title of each project. This works well for the projects page, but not on the home page, where it completely breaks the information hierarchy. What could we do?
One way to fix this is to add another prop to the Project.svelte
component that allows us to specify the heading level.
You can read more about props here.
Just like data
, we specify a prop by using export
in the <script>
element of Project.svelte
, and setting its value to our desired default value. You can call it anything you want (I called it hLevel
) but it’s important to give it a default value so that it only needs to be specified when it differs from the default. The <script>
block should look like the following:
<script>
export let data = {};
export let hLevel = 2;
</script>
Then in the HTML of the file, we want to apply hLevel
to the heading tag for the title. There is a special element, <svelte:element>
that is used for this exact scenario: when we want to specify the type of the element conditionally. We can replace
<h2>{data.title}</h2>
with
<svelte:element this={"h" + hLevel}>{data.title}</svelte:element>
<svelte:element>
is a dynamic element that allows you to create an HTML tag based on changing input rather than hardcoded data. The this
attribute will specify which HTML element to render.
Lastly, we need to set the hLevel
prop value for our Project
component used in our Home page routes/+page.svelte
:
<Project data={p} />
becomes
<Project data={p} hLevel="3" />
Note that we also need to adjust our CSS to account for <h3>
elements styling.
Step 5.5: Moving Project-specific CSS to the Project component
The idea of component-driven design is that any code that is specific to a component should live in that component. You previously wrote code in the global CSS file style.css
that is styling specific to the Project component, so let’s move this code to Project.svelte
inside a <style>
element at the bottom of the file.
This could include the CSS styling you created for article {...}
, img {...}
, etc.
Svelte automatically rewrites this CSS to ensure it never applies to anything that is not part of the component, so you can use define styling for simple selectors like article
and not have to worry that you’ll this styling will bleed into those elements in other components.
To be continued…
In the next lab, we will learn about Svelte’s reactivity and how to use it to create interactive components. Among other things we will…
- Fix the current page bug in our nav bar
- Port the color scheme switcher code to Svelte and greatly simplify it in the process
- Load data from an API and display it on our website