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.
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.
Key Takeaways
- Utilize Parcel, a zero-configuration module bundler, to efficiently bundle a Hyperapp application, simplifying the development process and deployment.
- Deploy the bundled Hyperapp application to GitHub Pages by configuring the build output directory to `docs`, enabling easy hosting and access via a custom URL.
- Manage application state and dynamic content using Hyperapp’s minimalistic framework, which supports a virtual DOM and state management out of the box.
- Write modern JavaScript with ES6 modules and JSX, which Parcel will automatically transpile and bundle for broad browser compatibility.
- Leverage npm scripts within the `package.json` file to automate common tasks like starting the server, building the app, and deploying to GitHub Pages.
- Enhance the application’s styling using SCSS, which Parcel will process and include automatically, ensuring a polished and responsive user interface.
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:
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:
- start the development server:
npm start
- make any changes
- check that the changes work on the development server
- shut the server down by holding down Ctrl + c
- rebuild the app:
npm run build
- stage the changes for commit:
git add .
- commit all the changes to git:
git commit -m 'latest update'
- 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!
Frequently Asked Questions (FAQs) about Parcel, Hyperapp, and Github Pages
What is the main difference between Parcel and other bundlers?
Parcel is a web application bundler that offers a seamless and efficient experience for developers. Unlike other bundlers such as Webpack and Browserify, Parcel requires zero configuration to get started. It has a built-in development server and automatically transforms modules using Babel, PostCSS, and PostHTML when needed. It also supports code splitting out of the box, making it a powerful tool for building complex web applications.
How does Hyperapp compare to other JavaScript frameworks?
Hyperapp is a minimalist JavaScript framework for building web applications. It stands out for its simplicity and small size, with the entire library being just 1KB. Despite its size, Hyperapp provides a robust set of features, including state management and a virtual DOM for efficient rendering. It’s a great choice for developers who want to keep their projects lightweight and manageable.
How can I deploy my Parcel and Hyperapp project on Github Pages?
Github Pages is a platform that allows you to host your web applications directly from your Github repository. To deploy your Parcel and Hyperapp project, you need to build your project using Parcel, commit the build files to your repository, and then configure Github Pages to serve the build files. You can automate this process using scripts in your package.json file.
Can I use Parcel with other JavaScript frameworks?
Yes, Parcel is a versatile bundler that can be used with a variety of JavaScript frameworks, including React, Vue, and Angular. It automatically handles the transformation and bundling of your code, allowing you to focus on writing your application.
What are the benefits of using Github Pages for hosting my web application?
Github Pages offers a simple and free solution for hosting your web applications. It integrates directly with your Github repository, allowing you to easily deploy updates by pushing to your repository. It also supports custom domains and SSL encryption, making it a robust platform for hosting both personal and professional projects.
How does Parcel handle assets?
Parcel has out-of-the-box support for a wide range of assets, including CSS, images, and fonts. It automatically processes these assets and includes them in your bundle. You can also import assets directly in your JavaScript code, and Parcel will handle them appropriately.
How can I handle state in Hyperapp?
Hyperapp includes a built-in state management solution. The state of your application is stored in a single state object, and you can update the state using actions. Actions are pure functions that take the current state and an action payload, and return the new state.
Can I use TypeScript with Parcel and Hyperapp?
Yes, Parcel has built-in support for TypeScript, a statically typed superset of JavaScript. You can write your Hyperapp code in TypeScript and Parcel will automatically compile it to JavaScript.
How can I optimize my Parcel bundle for production?
Parcel automatically optimizes your bundle when you build for production. It minifies your code, extracts and minifies CSS, and optimizes images. You can also use the --experimental-scope-hoisting
flag to enable scope hoisting, which can result in smaller bundle sizes.
Can I use server-side rendering with Hyperapp?
While Hyperapp does not natively support server-side rendering, you can use it in combination with a Node.js server and a library like Hyperapp Render to achieve server-side rendering. This can improve the performance and SEO of your application.
Darren loves building web apps and coding in JavaScript, Haskell and Ruby. He is the author of Learn to Code using JavaScript, JavaScript: Novice to Ninja and Jump Start Sinatra.He is also the creator of Nanny State, a tiny alternative to React. He can be found on Twitter @daz4126.