Skip to content

Building a Simple Static Site with Parcel and Stimulus

I’ve been using parcel and stimulusjs recently while experimenting with a JVM-based monolith where I didn’t want to give up the developer experience I get in node-land. I’ll write more about my experience there later, but figured it was worth writing up a simple use case with these tools in isolation before piling on more complexity.

Both of these tools come with superbly good documentation so if you’re someone who learns well from RTFM-ing then I recommend doing so wholeheartedly. On the other hand if you’d like to read through a crappy toy application build then read on!

The what and why of parcel

I’m not really a front-end developer. While I work on front-end features about 40% of the time, it’s a means to an end. Furthermore, I’m not terribly interested in the front-end build environment. This is a roundabout way of saying I suck at figuring out how to get webpack to do what I want. It was this bit of self-reflection that led to my interest in parcel: a build tool that claims to be a “zero configuration web application bundler.”

The dev team gets incredibly close to this lofty goal. There are some rough bits (for example, there’s no way to explicitly state the base directory that your entrypoint should work relative to) but it’s very solid all round. It’s worth mentioning that v2 is in alpha and the developers seems to be offering a better escape hatch for when the automatic configuration doesn’t get things quite right, so even these minor pain points might be alleviated.

The what and why of stimulus

I’ve recently realized that single page apps are a bad fit for a lot of the work I’ve been doing in recent years. I’ll write more about this another time, but for now, I think it’s enough to say that this realization led me down a rabbit hole that ended in jQuery. While I’m not totally opposed to jQuery, I’ve found it hard to maintain a coherent structure when a project grows.

In particular, it’s really hard to figure out what JS might impact the part of the markup I’m working in without searching the entire codebase, or enforcing my own convention on naming/selectors. I’m normally leery of good intentions like this so I started searching for a library that manages this convention for me. That’s how I found Stimulus.

Stimulus is a javascript framework made by Basecamp, the same folks that made Rails and Turbolinks. While I’d recommend reading their blog to find out more, the long and short of their web philosophy is that’s it’s more productive to focus on server-rendered markup while using javascript to add “sprinkles” of functionality.

Stimulus resolves the issues I have with jQuery while getting enough done that I’m not missing much. It works by allowing you to write a JS controller class that uses augmented markup to add some dynamic functionality. There are three main concepts in Stimulus:

  • Controllers: JS classes that hold the custom JS you’re writing;
  • Targets: DOM elements that are tagged with data-target attributes to allow an easy reference from the controller; and
  • Actions: a way to connect native browser events to custom JS in your controller by adding the data-action attribute to an element in markup.

We’ll go into the syntax more below. It was a little weird to work in this way at first (after a few years of Vue), but after a couple of hours I was really impressed by how versatile it is.

What are we building?

I’m going to build a very simple little app. It lets you type in your name and prints “Hello ${name}!” much like many of the SPA framework examples around. I’ll also throw in some styling with SASS to show off how easy it is to build up your build toolchain with parcel.


We’re going to set-up an empty node project. So on my linux machine I run:

mkdir parcel-stimulus-example && cd parcel-stimulus-example && npm init

Fill out the prompts and then we’re ready to rock. First I install parcel by running npm i -D parcel-bundler. I’m going to be using npx to run parcel throughout this example but you could set up some scripts in your package.json if you’d like. They’ll be pretty much the same.

Next, I’m going to create an src directory and inside that directory create an index.html file.

mkdir src && touch src/index.html

And I’m going to write a basic outline of an HTML doc:

<!doctype html>

<html lang="en">
  <meta charset="utf-8">

  <title>Parcel & Stimulus</title>
  <meta name="description" content="Say hello to parcel and stimulusjs.">
  <h1>Hello World</h1>

Then run parcel in serve mode:

npx parcel serve src/index.html

You should get some output like this:

Open this link and you should see something like this:

Hey! This is easy! It turns out that serving a static HTML file in 2020 isn’t so bad… Let’s get this doing something a little more interesting.

Now to make it more stimulating

First we’ll add stimulus by running npm i -S stimulus

Now that stimulus is installed, we can start writing our javascript. Let’s create a js directory in our src folder and create an app.js file.

mkdir src/js && touch src/js/app.js

Now let’s create our first stimulus controller. We create a controllers directory and create a hello_controller.js file inside it.

mkdir src/js/controllers 
&& touch src/js/controllers/hello_controller.js

In that hello_controller.js file let’s add the following:

import { Controller } from 'stimulus'

export default class HelloController extends Controller {
    connect() {
        console.log('Hello there!')

So here we have our class HelloController that extends the stimulus Controller class. We’re using a connect method here which stimulus will call whenever the class connects to a DOM element.

Now in app.js we’re going to write:

import { Application } from 'stimulus'
import HelloController from "./controllers/hello_controller";

const application = Application.start()
application.register('hello', HelloController)

This starts our Application instance and registers our controller. Stimulus ships with a default webpack helper that’s not available to us here so we have to register the controller manually. There is a utility available that allows you to load controllers asynchronously with explicitly importing them, but I’ve not used it before and can’t speak to how well it works.

To use this we’ll add a script tag to head in our index.html:

<script src="./js/app.js" defer></script>

And that’s it! Open the page again and…. no dice. Huh. It looks like we forgot to attach that controller to anything in the application. Let’s fix it now. We add an empty div with a stimulus data-controller attribute. This tells the library to create a new instance of HelloController and attach it to the DOM element:

<div data-controller="hello"></div>

This results in:

Cool! Let’s looks at something else for a minute here. If you open dist/index.html you’ll notice that parcel has made a change to our application. The script tag we added before is now:

  <script src="/app.c3f9f951.js" defer=""></script>

Parcel has hashed the file for us added that hash as a cache buster automatically. We can now serve this file up from a CDN with an indefinite cache without worrying about how we’d invalidate the browser cache on an update. Pretty neat!

Let’s do it in style

Now let’s add a little bit of style to further highlight how easy life is with Parcel. Create a new directory in src called styles and create a file in there called index.scss

mkdir src/styles && touch src/styles/index.scss

Note that we’re creating an scss file here.

Now open the file up and enter some styling:

.app {
  .title {
    font-size: 3rem;

If you’re not familiar with SASS, one of the nice things it lets you do is nest styles in a way that’s more intuitive (in my opinion) than the default CSS way of doing things. This example exists solely to convince you that I’m not writing CSS with a weird filename.

To use this style, add the following line to the head of our index.html file.

  <link rel="stylesheet" href="./styles/index.scss">

While we’re in here, let’s change the body to reflect the styles:

<div class="app" data-controller="hello">
  <h1 class="title">Hello World</h1>

Reload the page in the browser and behold! A slightly larger title! Inspecting the created index.html in our dist directory, we see that the style has been added:

  <link rel="stylesheet" href="/styles.434540e1.css">
  <script src="/styles.434540e1.js"></script>

We’ll ignore the JS for now: it’s essentially some parcel functionality around loading the assets. As we can see the styles have been compiled down to CSS and have a unique hash added to the filename again. As a reminder, we’ve not touched anything about our build manually. I did not sneak off and install a SASS compiler.

Now let’s get back to the task at hand. We’re trying to add an input where we can type our name and be greeted with a wonderful message. Let’s update our index.html so that the body contains the following:

<div class="app" data-controller="hello">
    <h1 class="title">Hello World</h1>
            Your name:
            <input type="text" data-target="hello.input"
        <p data-target="hello.greeting"></p>

There’s a little bit more to unpack here. We have two data-target attributes, one for the input and one for the greeting. This gives us an easy way to refer to the target in the controller.

We also have a data-action defined. This says on input, pass the native browser input event to the HelloController‘s updateMessage function.

Let’s write that function now. We’ll also have to add our targets to the Controller class:

import { Controller } from 'stimulus'

export default class HelloController extends Controller {
    static get targets() {
        return ['input', 'greeting'];

    connect() {
        console.log('Hello there!')

    updateMessage() {
        const name = this.inputTarget.value

        this.greetingTarget.innerHTML = `And hello to you ${name}!`

There we have it! We had to add the targets list so that Stimulus knows what target elements are available in the document. We’ve also added the updateMessage method that we need to use. We’ll go back to the browser and see that it’s working:

It works!

It’s just not my type (yet)

Now, let’s get down to my favorite bit. I don’t really love dynamic languages so normally try to use Typescript in place of javascript when I can. Let’s change the ending of our hello_controller.js class to ts. We’ll also have to change the file ending in app.js:

import HelloController from "./controllers/hello_controller.ts";

Now head back on over to the browser and… it works. It just does. It’s wonderful. This is the easiest time I’ve ever had adding typescript to a project.

You’ll notice that we’ve yet to change our controller. It’s just working because typescript is a superset of javascript, but we’re not really getting any benefit yet. Let’s fix that:

import { Controller } from 'stimulus'

export default class HelloController extends Controller {
    static targets =  ['input', 'greeting'];

    inputTarget!: HTMLInputElement;
    greetingTarget!: HTMLElement;

    connect() {
        console.log('Hello there!')

    updateMessage() {
        const name = this.inputTarget.value

        this.greetingTarget.innerHTML = `And hello to you ${name}!`

While we’re not doing anything too involved here, I find the autocomplete that comes with telling the editor about types is worthwhile. We’ve had to add an exclamation mark to the end of our field names so that typescript doesn’t get too upset about them not being explicitly initialized, but – other than that – everything works as expected.


And that is that. We’ve added Stimulus to a project and built it with Parcel. So what? Well, we also added Typescript and SASS without blinking. Did you see me run another npm command? No, parcel did it for me. This is terrific for developers like me who aren’t focused on the front end. Parcel can auto-configure Vue, React and Angular too. It will make you smile.

In terms of scripting a web application, StimulusJS is a breath of fresh air for us. It’s nice to be able to use a simpler kind of front-end tool without worrying about it becoming a big ball of mud. If you’re interested in what’s possible with Stimulus, I’d checkout the email service from Basecamp – Hey. They’ve included the sourcemaps at so you can have a dig around with dev tools open.

I’ve posted the completed example to GitHub here. I’ll be writing a follow up to this where I add cypress testing and dark mode to this project. If there’s anything else that would be good to include then let me know.

Published inTechnical

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Creative Commons License
Except where otherwise noted, Happy Valley IO Blog by Happy Valley IO is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
You are welcome to buy a license for any post on this site by contacting