TypeOfNaN

How to Set Up a Svelte App with Rollup

Nick Scialli February 10, 2021🚀🚀🚀 15 minute read

svelte and rollup

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 plugins
  • rollup-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!

hello daffodil

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 software engineer at the U.S. Digital Service.