Building Hangman with Hyperapp - Part 1
Adam Dawkins
Posted on January 21, 2020
Building Hangman with Hyperapp
Building interactive UIs on the modern web can be a daunting task. If you're a junior developer, making the jump from jQuery to a ‘full’ Javascript UI is massive. For seasoned developers, keeping your application architecture error free and simple is hard-work, and there are as many different best practices for React, Angular and Vue as I've had hot dinners.
Things don’t have to be this way.
Let’s build a version of Hangman in Hyperapp, and see another way user interfaces can be build in Javascript. Even if you don’t want to switch your applications over to Hyperapp, you’ll learn something that will make you a better Javascript UI developer in your existing framework of choice.
Why Hyperapp?
Putting the “Hype” into Hyperapp
Hyperapp is a Javascript framework for building user interfaces. It uses a ‘Virtual DOM’ to update the page, as you might be familiar with in other Javascript UI Frameworks, such as React, Vue, Angular, etc. The Virtual DOM is the method by which these frameworks update the page, and Hyperapp has it's own VDOM, written from the ground up for speed and size.
So what’s the difference?
For my money, Hyperapp has a few key advantages over React, Angular & Co.
- It’s 1KB. That’s under 1/20th the size of React, Vue, and Angular, and a third of the size of Preact, which is a “fast 3KB alternative to React”.
- A functional paradigm. Some big words here, but the way Hyperapp conceptualizes your UI and application is a delight to work with. It keeps things very simple and clear and allows you to create powerful interfaces in a way that keeps them robust and manageable in size. More on that later.
- This is a bonus feature, if you like Elm, you’ll love Hyperapp. For me, Hyperapp is essentially Elm-architecture without the different language. If you don’t know what Elm is, don’t worry, you don’t need to - it's a language and framework for building front-end UIs, just like React, Vue and Angular. I've always enjoyed it for the way it conceptualies your app as a big function, but was put off of writing in a language that only compiled to Javascript - it's a learning curve for me, and anyone on my team.
Let’s dive in.
To get you started with Hyperapp, we’re going to build a version of the popular word game /Hangman/, in Hyperapp.
Our game has the following spec:
- The Computer picks a random word for us to guess
- The Player inputs letters to guess the word
- Like the paper version, correct letters get inserted into the word, incorrect letters get listed elsewhere
- 8 incorrect guesses and the Player loses
- If the Player fills in the word correctly, they win.
We may add to this spec as we go on, but this is the nuts and bolts of a game of hangman.
Minimum Viable Hangman
We’re going to build our Hangman game up in layers. We’ll start with something that barely counts as hitting our spec - so that we can play Hangman, and then we’ll improve and refine the user experience.
What you’ll need:
- Node.js
- A node package manager (either
npm
oryarn
). - A code editor
- A browser.
Installation
Let’s dive in, get hyperapp installed and get working on our game then.
Create a new folder called hyperapp-hangman
, and add a Javascript and HTML file:
mkdir hyperapp-hangman
touch hyperapp-hangman/index{.js,.html}
Let’s add a package manager, you can use npm
or yarn
, whatever floats your boat:
cd hyperapp-hangman
npm init . -y
# OR
yarn init . -y
This allows us to add Javascript packages to our application from the inter web, so let’s go ahead and add hyperapp. After all, /that’s why we’re here!/
yarn add hyperapp@beta
OR
npm install hyperapp@beta —save
Great, now we need some HTML.
Our HTML is really simple, we have a div#app
for putting all of our UI in, and we import our Javascript file:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div id="app"></div>
<script src="./index.js"></script>
</body>
</html>
To the Javascript
We're going to start by importing two things from Hyperapp. h
, and app
:
import {h, app} from 'hyperapp';
Let me quickly explain both.
app
is a function that's going to basically 'start' Hyperapp. We're going to pass it an object in a minute, with three keys: init
, view
and node
:
-
init
- this is an object that describes the starting state of our application. We'll put the word we need to guess here. -
view
- the function we give this will take the state and return our UI. It'll end up as HTML on the page. -
node
- the part of the DOM we want to insert our application into, we could write that now if we wanted:
app({
init: {}, // ? - We don't know yet, maybe our guessable word
view: () => {}, // ? - We don't know how to write views in Hyperapp yet.
node: document.getElementById('app'), // our app div in index.html
});
Views
In Hyperapp, what we're actually doing is creating virtual DOM nodes. This is a virtual representation of the UI we want to see in the browser. Hyperapp compares this virtual representation under the hood to the real DOM and makes any necessary changes.
But how do we write it? Enter h
.
Updating the DOM
To explain what h
does, it's worth thinking about how you'd add something to the DOM in Javascript. Let's try and add the following HTML to app
using pure Javascript:
<div class="pure-javascript">
<h1>This was added with pure Javascript, check me out!</h1>
</div>
To create our div, we do this:
const div = document.createElement('div');
div.classList = 'pure-javascript';
and we append it to our #app
div like so:
document.addEventListener('DOMContentLoaded', () => {
// wait for the DOM to load
const div = document.createElement('div');
div.classList = 'pure-javascript';
const app = document.getElementById('app');
app.appendChild(div);
});
})
Now, we need to add our <h1>
to our div.
const h1 = document.createElement('h1');
h1.appendChild(
document.createTextNode('This was added with pure Javascript, check me out!'),
);
div.appendChild(h1);
Success! We have inserted some DOM with Javascript. This is a perfectly fine way of building a UI in Javascript, but it is ugly to work with.
To counter this, Javascript wizards invented 'hyperscript', an h
function that takes the name of an element and some children.
A basic version, without attributes looks like this:
const h = (tagName, children) => {
const element = document.createElement(tagName);
if (typeof children === 'string') {
element.appendChild(document.createTextNode(children));
} else {
children.forEach(child => {
element.appendChild(child);
});
}
return element;
};
You don't need to understand exactly what this is doing, but it allows us to use it like this:
const container = h('div', [
h('h1', 'Hello, World!'),
h('p', 'This is a paragraph about the app'),
]);
app.appendChild(container);
We pass our child elements in to h
as an array, and textNodes
in as text. Lovely.
We could extend this to include an object of attributes to add to our elements, so we could use it like this.
h('div', {class: 'pure-javascript'}, [
h('h1', {}, 'This was added with pure Javascript, check me out!'),
]);
And there we have it.
This is actually exactly what the syntax of hyperapp's h
looks like, except, under the hood, in creates a Virtual DOM rather than editting the real DOM directly.
Let's do the same thing with hyperapp:
import {h, app} from 'hyperapp';
app({
init: {}, // we don't have a state here
view: () =>
h('div', {class: 'pure-javascript'}, [
h('h1', {}, 'This was added with pure Javascript, check me out!'),
]),
node: document.getElementbyId('app'),
});
Running Hyperapp
With our basic Javascript examples, we could just open index.html
to view our code, but hyperapp
is install as a node module, so we need to start things a bit differently.
The easiest way, is with Parcel JS
After installing the package, we can run parcel
to start a server:
parcel index.html
Now open your browser at http://localhost:1234
Improving on h()
We have a couple more options available to tidy up this view:
1) @hyperapp/html
You may have noticed another pattern available to us. h('div',...)
is always going to be the same for every div
. We could create a div
function to handle this.
const div = (attributes, children) => h('div', attributes, children);
We could do the same for h1
, but it'd be time consuming.
That's what hyperapp/html
has already done, We can use it to tidy up this view:
yarn add @hyperapp/html
import {app} from 'hyperapp';
import {div, h1} from '@hyperapp/html';
app({
init: {}, // we don't have a state here
view: () =>
div({class: 'pure-javascript'}, [
h1({}, 'This was added with pure Javascript, check me out!'),
]),
node: document.getElementById('app'),
});
Our other option is to use JSX, which allows you to write your view in a way that looks like HTML. You may have seen this in React.
There is a small piece of extra configuration requried if you choose to use JSX, and your view will now look like this:
app({
init: {}, // we don't have a state here
view: () => (
<div className="pure-javascript">
<h1>This was added with pure Javascript, check me out!
</div>
),
node: document.getElementById('app'),
});
There are some obvious benefits to JSX. If you already have written some HTML, it makes the conversion to VDOM painless. It also represents what you're actually adding to the DOM.
However, it is important to remember that underneath, both of our enhancements here are calling h()
under the hood.
They're both doing:
{
view: () =>
h('div', {class: 'pure-javascript'}, [
h('h1', {}, 'This was added with pure Javascript, check me out!'),
]),
};
If you're familiar with JSX, by all means get it setup and follow along. If you're new to JS UI Frameworks, stick with h()
and @hyperapp/html
for this tutorial. It'll help you remember you're not writing HTML, you're writing functions that create a virtual DOM.
Now that we know a bit of what's going on, we'll get stuck into building Hangman in Part 2.
This tutorial was originally posted on adamdawkins.uk on 7th October 2019
Posted on January 21, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.