Use Parcel to Bundle a Hyperapp App & Deploy to GitHub Pages

In a previous post we met Hyperapp, a tiny library that can be used to build dynamic, single-page web apps in a similar way to React or Vue.

In this post we’re going to turn things up a notch. We’re going to create the app locally (we were working on CodePen previously), learn how to bundle it using Parcel (a module bundler similar to webpack or Rollup) and deploy it to the web using GitHub Pages.

Don’t worry if you didn’t complete the project from the first post. All the code is provided here (although I won’t go into detail explaining what it does) and the principles outlined can be applied to most other JavaScript projects.

--ADVERTISEMENT--

If you’d like to see what we’ll be ending up with, you can view the finished project here, or download the code from our GitHub repo.

Basic Setup

In order to follow along, you’ll need to have both Node.js and npm installed (they come packaged together). I’d recommend using a version manager such as nvm to manage your Node installation (here’s how), and if you’d like some help getting to grips with npm, then check out our beginner-friendly npm tutorial.

We’ll be using the terminal commands to create files and folders, but feel free to do it by just pointing and clicking instead if that’s your thing.

To get started, create a new folder called hyperlist:

mkdir hyperlist

Now change to that directory and initialize a new project using npm:

cd hyperlist/
npm init

This will prompt you to answer some questions about the app. It’s fine to just press enter to accept the default for any of these, but feel free to add in your name as the author and to add a description of the app.

This should create a file called package.json inside the hyperlist directory that looks similar to the following:

{
  "name": "hyperlist",
  "version": "1.0.0",
  "description": "A To-do List made with Hyperapp",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "DAZ",
  "license": "MIT"
}

Now we need to install the Hyperapp library. This is done using npm along with the --save flag, which means that the package.json file will be updated to include it as a dependency:

npm install --save hyperapp

This might give some warnings about not having a repository field. Don’t worry about this, as we’ll be fixing it later. It should update the package.json file to include the following entry (there might be a slight difference in version number):

"dependencies": {
  "hyperapp": "^1.2.5"
}

It will also create a directory called node_modules where all the Hyperapp files are stored, as well as a file called package-lock.json. This is used to keep track of the dependency tree for all the packages that have been installed using npm.

Now we’re ready to start creating the app!

Folder Structure

It’s a common convention to put all of your source code into a folder called src. Within this folder, we’re going to put all of our JavaScript files into a directory called js. Let’s create both of those now:

mkdir -p src/js

In the previous post we learned that apps are built in Hyperapp using three main parts: state, actions and view. In the interests of code organization, we’re going to place the code for each part in a separate file, so we need to create these files inside the js directory:

cd src/js
touch state.js actions.js view.js

Don’t worry that they’re all empty. We’ll add the code soon!

Last of all, we’ll go back into the src directory and create our “entry point” files. These are the files that will link to all the others. The first is index.html, which will contain some basic HTML, and the other is index.js, which will link to all our other JavaScript files and also our SCSS files:

cd ..
touch index.html index.js

Now that our folder structure is all in place, we can go ahead and start adding some code and wiring all the files together. Onward!

Some Basic HTML

We’ll start by adding some basic HTML code to the index.html file. Hyperapp takes care of creating the HTML and can render it directly into the <body> tag. This means that we only have to set up the meta information contained in the <head> tag. Except for the <title> tag’s value, you can get away with using the same index.html file for every project. Open up index.html in your favorite text editor and add the following code:

<!doctype html>
<html lang='en'>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>HyperList</title>
  </head>
  <body>
    <script src='index.js'></script>
  </body>
</html>

Now it’s time to add some JavaScript code!

ES6 Modules

Native JavaScript modules were introduced in ES6 (aka ES2015). Unfortunately, browsers have been slow to adopt the use of ES6 modules natively, although things are now starting to improve. Luckily, we can still use them to organize our code, and Parcel will sort out piecing them all together.

Let’s start by adding the code for the initial state inside the state.js file:

const state = {
  items: [],
  input: '',
  placeholder: 'Make a list..'
};

export default state;

This is the same as the object we used in the previous article, but with the export declaration at the end. This will make the object available to any other file that imports it. By making it the default export, we don’t have to explicitly name it when we import it later.

Next we’ll add the actions to actions.js:

const actions = {
  add: () => state => ({
    input: '',
    items: state.items.concat({
      value: state.input,
      completed: false,
      id: Date.now()
    })
  }),
  input: ({ value }) => ({ input: value }),
  toggle: id => state => ({
    items: state.items.map(item => (
      id === item.id ? Object.assign({}, item, { completed: !item.completed }) : item
    ))
  }),
  destroy: id => state => ({
    items: state.items.filter(item => item.id !== id)
  }),
  clearAllCompleted: ({ items }) => ({
    items: items.filter(item => !item.completed)
  })
};

export default actions;

Again, this is the same as the object we used in the previous article, with the addition of the export declaration at the end.

Last of all we’ll add the view code to view.js:

import { h } from 'hyperapp'

const AddItem = ({ add, input, value, placeholder }) => (
  <div class='flex'>
    <input
      type="text"
      onkeyup={e => (e.keyCode === 13 ? add() : null)}
      oninput={e => input({ value: e.target.value })}
      value={value}
      placeholder={placeholder}
    />
    <button onclick={add}>+</button>
  </div>
);

const ListItem = ({ value, id, completed, toggle, destroy }) => (
  <li class={completed && "completed"} id={id} key={id} onclick={e => toggle(id)}>
    {value} <button onclick={ () => destroy(id) }>x</button>
  </li>
);

const view = (state, actions) => (
  <div>
    <h1><strong>Hyper</strong>List</h1>
    <AddItem
      add={actions.add}
      input={actions.input}
      value={state.input}
      placeholder={state.placeholder}
    />
    <ul id='list'>
      {state.items.map(item => (
        <ListItem
          id={item.id}
          value={item.value}
          completed={item.completed}
          toggle={actions.toggle}
          destroy={actions.destroy}
        />
      ))}
    </ul>
    <button onclick={() => actions.clearAllCompleted({ items: state.items }) }>
      Clear completed items
    </button>
  </div>s
);

export default view;

First of all, this file uses the import declaration to import the h module from the Hyperapp library that we installed using npm earlier. This is the function that Hyperapp uses to create the Virtual DOM nodes that make up the view.

This file contains two components: AddItem and ListItem. These are just functions that return JSX code and are used to abstract different parts of the view into separate building blocks. If you find that you’re using a large number of components, it might be worth moving them into a separate components.js file and then importing them into the view.js file.

Notice that only the view function is exported at the end of the file. This means that only this function can be imported by other files, rather than the separate components.

Now we’ve added all our JavaScript code, we just need to piece it all together in the index.js file. This is done using the import directive. Add the following code to index.js:

import { app } from 'hyperapp'

import state from './js/state.js'
import actions from './js/actions.js'
import view from './js/view.js'

const main = app(state, actions, view, document.body);

This imports the app function from the Hyperapp library, then imports the three JavaScript files that we just created. The object or function that was exported from each of these files is assigned to the variables state,actions and view respectively, so they can be referenced in this file.

The last line of code calls the app function, which starts the app running. It uses each of the variables created from our imported files as the first three arguments. The last argument is the HTML element where the app will be rendered — which, by convention, is document.body.

Add Some Style

Before we go on to build our app, we should give it some style. Let’s go to the src directory and create a folder for our SCSS:

mkdir src/scss

Now we’ll create the two files that will contain the SCSS code that we used in part 1:

cd src/scss
touch index.scss _settings.scss

We’re using a file called _settings.scss to store all the Sass variables for the different fonts and colors our app will use. This makes them easier to find if you decide to update any of these values in the future. Open up the _settings.scss file and add the following code:

// fonts
@import url("https://fonts.googleapis.com/css?family=Racing+Sans+One");
$base-fonts: Helvetica Neue, sans-serif;
$heading-font: Racing Sans One, sans-serif;

// colors
$primary-color: #00caff;
$secondary-color: hotpink;
$bg-color: #222;

The app-specific CSS goes in index.scss, but we need to make sure we import the _settings.scss file at the start, as the variables it contains are referenced later on in the file. Open up index.scss and add the following code:

@import 'settings';

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  padding-top: 50px;
  background: $bg-color;
  color: $primary-color;
  display: flex;
  height: 100vh;
  justify-content: center;
  font-family: $base-fonts;
}

h1 {
  color: $secondary-color;
  & strong{ color: $primary-color; }
  font-family: $heading-font;
  font-weight: 100;
  font-size: 4.2em;
  text-align: center;
}

a{ color: $primary-color; }

.flex{
  display: flex;
  align-items: top;
  margin: 20px 0;

  input {
    border: 1px solid $primary-color;
    background-color: $primary-color;
    font-size: 1.5em;
    font-weight: 200;
    width: 50vw;
    height: 62px;
    padding: 15px 20px;
    margin: 0;
    outline: 0;

    &::-webkit-input-placeholder { color: $bg-color; }
    &::-moz-placeholder { color: $bg-color; }
    &::-ms-input-placeholder { color: $bg-color; }
    &:hover, &:focus, &:active { background: $primary-color; }
  }

  button {
    height: 62px;
    font-size: 1.8em;
    padding: 5px 15px;
    margin: 0 3px;
  }
}

ul#list {
  display: flex;
  flex-direction: column;
  padding: 0;
  margin: 1.2em;
  width: 50vw;
  li {
    font-size: 1.8em;
    vertical-align: bottom;
    &.completed{
      color: $secondary-color;
      text-decoration: line-through;
      button{
        color: $primary-color;
      }
    }
    button {
      background: none;
      border: none;
      color: $secondary-color;
      outline: none;
      font-size: 0.8em;
      font-weight: 50;
      padding-top: 0.3em;
      margin-left: 5px;
    }
  }
}

button {
  background: $bg-color;
  border-radius: 0px;
  border: 1px solid $primary-color;
  color: $primary-color;
  font-weight: 100;
  outline: none;
  padding: 5px;
  margin: 0;

  &:hover, &:disabled {
    background: $primary-color;
    color: #111;
  }

  &:active { outline: 2px solid $primary-color; }
  &:focus { border: 1px solid $primary-color; }
}

If your SCSS starts to get more complicated, you can break it up into separate files and then import them all into index.scss.

Now we need to link these files to our app. We don’t actually place the link in our HTML file, as you usually do with CSS. Instead, we place it in the index.js file. This is because we’re using SCSS and it needs to be pre-processed into CSS. Parcel will do this for us and also sort out linking the HTML file to the standard CSS file that it creates.

To import the SCSS files, we just need to update our index.js file to include the following line:

import './scss/index.scss'

Now that all our code’s complete, it’s time to start work on the build process!

Babel

Babel will transpile the modern JavaScript code into code that most browsers can consume. It will also take care of rewriting the JSX code into pure JavaScript.

In order to be able to use Babel with JSX transforms, we need to install it along with the JSX plugin:

npm install --save babel-plugin-transform-react-jsx babel-preset-env

We also need to create a .babel.rc file that’s used to tell Babel to use the h function from Hyperapp when processing the JSX. The following code will create the file with the relevant information:

echo '{ "plugins": [["transform-react-jsx", { "pragma": "h" }]] }' > .babelrc

Note that this is a hidden file, so you might not be able to see it after it’s been created!

Parcel

Unfortunately, our code won’t currently work in all browsers as it stands. We need to use a build process to transpile our ES6+ code to ES5 and merge all our JS files into a single file. Let’s use Parcel to do that.

Parcel is a module bundler, similar to webpack or Rollup, that promises zero-configuration and is blazingly fast. It allows us to write modern JavaScript in separate files, and then bundles them together into a single, minified JavaScript file that most browsers will be able to consume. It also supports multiple CSS, SCSS and PostCSS files out of the box.

First of all, let’s install Parcel:

npm install --save parcel-bundler

Parcel comes with its own built-in server. This means that you can continue to develop and make changes to the app and Parcel will build it in the background, so any changes are shown instantly!

To start the server running, enter the following command:

./node_modules/.bin/parcel src/index.html --out-dir docs

This specifies that the entry point is the index.html file. This is all Parcel needs to know about, as it will follow the link to index.js that’s in this file and then follow the import directives in that file.

It also specifies that a folder called docs be used to output all of the static files to. By default, this is usually called dist — but, as you’ll see later, we need it to be called docs so that we can integrate it with GitHub Pages.

You should also see a message that the app is being built in the terminal window. You might even notice that Parcel installs the npm module node-sass for you as it automatically notices that we’ve been using SCSS files, but also that we don’t have node-sass installed. How cool is that?!

After a few seconds, you should see a message similar to the following:

Server running at http://localhost:1234
✨  Built in 3.15s.

The server is now running, and if you open up your browser and go to http://localhost:1234, you’ll be able to see the app running. This will update on the fly, so any changes you make in your code will be reflected on the page straight away (or after a brief pause to rebuild the code). It also hotloads modules, so it will automatically install any npm modules that are required as they’re needed, like it did with “node-sass”. Awesome!

Once you’re happy with how the site looks, it’s time to build the static site. First of all, stop the server running by holding down Ctrl and c together. Then run the following command in the terminal:

./node_modules/.bin/parcel build src/index.html --out-dir docs --public-url ./

This will build the static files and place them inside the docs folder.

If you take a peak inside the docs folder, you should find a file called index.html. Open this in your browser and you should see the site running, using only the static files in the docs folder. Parcel has bundled all the relevant code together and used Babel to transpile our modern JavaScript into a single JavaScript file and used node-sass to pre-process our SCSS files into a single CSS file. Open them up and you can see that the code has also been minimized!

npm Scripts

npm has a useful feature called scripts that allows you to run specific pieces of code with a single command. We can use this to create a couple of scripts that will speed up our use of Parcel.

Add the following to the “scripts” section of the package.json file:

"start": "parcel src/index.html --out-dir docs",
"build": "parcel build src/index.html --out-dir docs --public-url ./"

Now we can simply run the following commands to start the server:

npm start

And the following command will run the build process:

npm run build

If you’ve never used npm scripts, or would like a refresher, you might like to check out our beginner-friendly tutorial on the subject.

Deploying to GitHub Pages

GitHub is a great place for hosting your code, and it also has a great feature called GitHub Pages that allows you to host static sites on GitHub. To get started, you’ll need to make sure you have a GitHub account and you have git installed on your local machine.

To make sure we don’t commit unnecessary files, let’s add a gitignore file to the hyperlist directory:

touch .gitignore

As the name suggests, this file tells git which files (or patterns) it should ignore. It’s usually used to avoid committing files that aren’t useful to other collaborators (such as the temporary files IDEs create, etc.).

I’d recommend adding the following items to make sure they’re not tracked by git (remember that gitignore is a hidden file!):

# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Dependency directory
node_modules

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# Cache for Parcel
.cache

# Apple stuff
.DS_Store

Now we’re ready to initialize git in the hyperlist directory:

git init

Next, we add all the files we’ve created so far:

git add .

Then we commit those files to version control:

git commit -m 'Initial Commit'

Now that our important files are being tracked by git, we need to create a remote repository on GitHub. Just log into your account and click on the New Repository button and follow the instructions. If you get stuck, you can consult GitHub’s documentation here: Create A Repo.

After you’ve done this, you’ll need to add the URL of your remote GitHub repository on your local machine:

git remote add origin https://github.com/<username>/<repo-name>.git

Be sure to replace <username> and <repo-name> with the correct values. If you’d like to check that you’ve done everything correctly, you can use git remote -v.

And, finally, we need to push our code to GitHub:

git push origin master

This will push all of your code to your GitHub repository, including the static files in the docs directory. GitHub Pages can now be configured to use the files in this directory. To do this, log in to the repository on GitHub and go to the Settings section of the repository and scroll down to the GitHub Pages section. Then under Source, select the option that says “master branch /docs folder”, as can be seen in the screenshot below:

Your GitHub Pages site is currently being built from the /docs folder in the master branch. Learn More

This should mean that you can now access the app at the following address: https://username.github.io/repo-name.

For example, you can see ours at sitepoint-editors.github.io/hyperlist/.

Workflow

From now on, if you make any changes to your app, you can adhere to the following workflow:

  1. start the development server: npm start
  2. make any changes
  3. check that the changes work on the development server
  4. shut the server down by holding down Ctrl + c
  5. rebuild the app: npm run build
  6. stage the changes for commit: git add .
  7. commit all the changes to git: git commit -m 'latest update'
  8. push the changes to GitHub: git push origin master.

We can speed this process up by creating an npm script to take care of the last three steps in one go. Add the following to the “scripts” entry in package.json:

"deploy": "npm run build && git add . && git commit -a -m 'latest build' && git push origin master"

Now all you need to do if you want to deploy your code after making any changes is to run the following command:

npm run deploy

That’s All, Folks!

And that brings us to the end of this tutorial. I used the app we created in part 1 of this tutorial, but the principles remain the same for most JavaScript projects. Hopefully I’ve demonstrated how easy it is to use Parcel to build a static JS site and automatically deploy it to GitHub Pages with just a single command!