How to Set Up a Svelte App with Rollup
Nick Scialli
February 10, 2021
Svelte is a relatively new, blazing fast front-end UI library. Like some other front-end libraries (e.g., React), svelte code can’t be immediately interpreted and executed by a browser. Instead, you have to add a build step that will take your Svelte code and turn it into the HTML, CSS, and JavaScript that browsers understand.
The post will explore the basics on how to build Svelte apps for development and production using Rollup.
Creating a Basic Svelte App
Let’s create a super simple Svelte app to start with. First, we’ll create our application directory, which we’ll call my-svelte-app
:
mkdir my-svelte-app
Next, let’s navigate into that directory and initialize a new npm project. We’ll use the -y
flag to use default settings.
cd my-svelte-app
npm init -y
Great, we now have a basic package.json
file. Of course, this is a svelte projects, so the first thing we’ll want to do is actually install svelte
as a development dependency.
npm i svelte
By convention, we’ll write our app code in a src
directory. We’ll create that directory, an App.svelte
file for our component code, and a main.js
file, which will instantiate our app and tell it where to be inserted into the DOM.
mkdir src
touch src/App.svelte src/main.js
In our App.svelte
file, we’ll just have a paragraph that outputs “Hello [name]”, where that name is a prop.
App.svelte
<script>
export let name;
</script>
<p>Hello {name}</p>
Next up, we’ll configure main.js
. Here we create a new instance of our App
, plan to load our app in the document’s body (document.body
), and we’ll provide a name
of "Daffodil"
to our component.
main.js
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
name: 'Daffodil',
},
});
export default app;
Perfect! We have finished setting our our Svelte app, but we have no way of running it in development mode or production; it’s just a bunch of Svelte code right now.
As I stated before, we’ll reach for Rollup to convert our Svelte code into browser-readable HTML, CSS, and JavaScript.
Adding Rollup
Rollup is a module bundler for JavaScript applications. It takes modular code, like our Svelte app, and bundles it into files that browsers can readily parse and display to users. This means converting things like our .svelte
file and its various imports, props, etc., into HTML, CSS, and JavaScript files. Webpack is another such module bundler and can also be used for Svelte projects. Today, however, we focus on Rollup.
Getting Started with Rollup
One thing you might notice when you clone down a Svelte template (or a template from another UI library like React) is that the module bundler config files seem complex and unapproachable. The truth is that there is a lot that goes into these files, but if we create them from scratch and incrementally add features, we are able to see that it all makes sense and is very much doable.
That being said, let’s get our hands dirty! The first thing we’ll do is add rollup
as a development dependency for our project.
npm i -D rollup
Next up, we need to add two additional rollup development dependencies:
@rollup/plugin-node-resolve
, which is used to help rollup resolve third-party pluginsrollup-plugin-svelte
a third-party plugin that helps rollup understand how to process Svelte apps
npm i -D @rollup/plugin-node-resolve rollup-plugin-svelte
Keep in mind that we’re using the -D
flag to install these as development dependencies. After all, we only use rollup in development; by the time we’re in production our app has been built into HTML, CSS, and JavaScript.
Creating the Rollup Config File
Let’s create a very simple rollup config file. For now, all it will do is bundle our Svelte app into JavaScript in a public/build
folder.
touch rollup.config.js
In that file, our default export will be the rollup config object.
rollup.config.js
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
export default {
// This `main.js` file we wrote
input: 'src/main.js',
output: {
// The destination for our bundled JavaScript
file: 'public/build/bundle.js',
// Our bundle will be an Immediately-Invoked Function Expression
format: 'iife',
// The IIFE return value will be assigned into a variable called `app`
name: 'app',
},
plugins: [
svelte({
// Tell the svelte plugin where our svelte files are located
include: 'src/**/*.svelte',
}),
// Tell any third-party plugins that we're building for the browser
resolve({ browser: true }),
],
};
Hopefully that’s not too much at once! The input
field tells rollup where the main entrypoint the app is, the output
field specifies information about the bundled result, and the plugins
filed tells rollup how to process the input application.
Adding Rollup to Our NPM scripts
The last thing we need to do before we take our app for a test drive is to make it so we can (a) run rollup with an npm script and (b) serve the content that’s added to the public
folder.
Running rollup with an npm script
To run rollup with an npm script, let’s add a new script to our package.json
file:
package.json
/* other package.json content here */
{
"scripts": {
"dev": "rollup -c -w"
}
}
/* other package.json content here */
The -c
flag indicates we want rollup to use a config file. Since we don’t provide a file location, rollup will assume we have followed a convention, which we did when we named our config file rollup.config.js
. The -w
flag is super handy because it tells rollup to watch our included app files for changes. When there are any changes, rollup will helpfully rebuild our app into public/build/bundle.js
.
Now if we go to the command line and run npm run dev
, we should see that rollup has bundled our app into a new public/build/bunde.js
file. Success!
Serving the content
We have our bundled JavaScript, but the browser isn’t going to know what to do with that without an html file. Therefore, let’s add an index.html
file to our public
folder:
touch public/index.html
Inside that index.html
file, let’s create an HTML file with nothing in the body. We will, however, want to make sure we add a scrpt
tag that loads our bundled JavaScript from /build/bundle.js
.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Svelte App</title>
<script defer src="build/bundle.js"></script>
</head>
<body></body>
</html>
Now, we need a simple web server to serve our index.html
file. We will use a popular npm package called sirv-cli
to do this. Since this is only for development, we will again only be adding sirv-cli
as a dev dependency.
npm i -D sirv-cli
Now let’s add an npm script to serve our app. We’ll put this under the start
script.
/* other package.json content here */
{
"scripts": {
"dev": "rollup -c -w",
"start": "sirv public"
}
}
/* other package.json content here */
Now we should finally be able to build and start our app! For now, we’ll do so by first running the dev
script and then the start
script.
npm run dev && npm run start
You should now be able to navigate to http://localhost:5000 and see your Svelte app in all its glory!
Now if we change the the name
prop in our src/main.js
file to "Nick"
(or your own name), rollup will helpfully rebuild our application. Note that our application won’t refresh itself, we’ll have to go ahead and refresh http://localhost:5000 to see the changes.
Those are the basics
Congrats, those are the basics to using rollup to build and serve your Svelte app! That was a good chunk of information, so it makes sense if you want to stop there. However, there are some enhancements and improvements we can make! If you still have the appetite, forge ahead with me to make our development process a bit more robust.
Enhancements and Improvements
There are quite a few improvements we can make to our project. We’ll tackle two main improvements in this post: having rollup start the dev server for us and adding hot reloading to the project.
Much of the work here is derived from the Svelte starter template located here. Big thanks for the maintainers of that repo!
Having Rollup Start the Server
Running npm run dev && npm run start
is a bit of a pain, we should only have to run npm run dev
to get our dev server going. Therefore, let’s use the flexibility of rollup plugins to create our own serve
plugin.
Our custom serve
plugin can be added to the top of our rollup.config.js
file. It needs to export an object with a writeBundle
key that is a function. We can then call our serve
function in our plugins
array.
rollup.config.json
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
function serve() {
return {
writeBundle() {},
};
}
export default {
input: 'src/main.js',
output: {
file: 'public/build/bundle.js',
format: 'iife',
name: 'app',
},
plugins: [
svelte({
include: 'src/**/*.svelte',
}),
resolve({ browser: true }),
serve(),
],
};
Let’s fill in the serve
function. The following is a completed version of the function with some inline notes. Note that this blog post won’t go into detail about spawning a child process in node as it’s a bit out of scope!
function serve() {
// Keep a reference to a spawned server process
let server;
function toExit() {
// kill the server if it exists
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
// Spawn a child server process
server = require('child_process').spawn(
'npm',
['run', 'start', '--', '--dev'],
{
stdio: ['ignore', 'inherit', 'inherit'],
shell: true,
}
);
// Kill server on process termination or exit
process.on('SIGTERM', toExit);
process.on('exit', toExit);
},
};
}
Now we can go ahead and run npm run dev
in our terminal and we’ll see that our sirv
server is started for us! We can navigate to http://localhost:5000 and we’ll be up and running.
Adding Hot Reloading
You probably noticed earlier on that, when we made changes to our Svelte app, rollup would rebuild our bundle, but we had to refresh the browser in order to see changes. There’s actually a pretty easy way to make that happen without having to manually refresh—there’s a package for it called rollup-plugin-livereload
!
npm i -D rollup-plugin-livereload
Then we simply add it to our rollup config plugins array. It takes a string argument specifying which folder to watch for live reloading. In this case, we wish to watch anything in public
.
rollup.config.js
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
function serve() {
// Keep a reference to a spawned server process
let server;
function toExit() {
// kill the server if it exists
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
// Spawn a child server process
server = require('child_process').spawn(
'npm',
['run', 'start', '--', '--dev'],
{
stdio: ['ignore', 'inherit', 'inherit'],
shell: true,
}
);
// Kill server on process termination or exit
process.on('SIGTERM', toExit);
process.on('exit', toExit);
},
};
}
export default {
input: 'src/main.js',
output: {
file: 'public/build/bundle.js',
format: 'iife',
name: 'app',
},
plugins: [
svelte({
include: 'src/**/*.svelte',
}),
resolve({ browser: true }),
serve(),
livereload('public'),
],
};
Now if we start our application with npm run dev
, we see that our server will hot-reload the application whenever we make changes to our Svelte files. Neat!
Keep Exploring
There’s so much more you can configure (CSS/preprocessor support, various config differences in production versus dev, a wide ecosystem of very cool plugins), so hopefully this post has helped you dip your toe in and actually understand the Svelte/Rollup process enough to keep configuring it to your heart’s desire!
Nick Scialli is a senior UI engineer at Microsoft.