Trying out front-end - Part 1 - Build & Debug
I stumbled across hyperapp recently. It made me realise it’s been a while since I’ve experimented with front end development so I thought I’d have a go at building a TODO app (as is tradition). I plan to do this as a series of posts with the following rules:
- Javascript - I’ve played a bit will elm. But I want to see what’s possible with js right now.
- I’ll do some research but not much. So the tools will hopefully be current but not bleeding edge.
- If I get stuck in weird frontend bug hacks I’m hitting eject.
- I’m not going to css - because reasons.
Getting something to build
So I’d already picked hyperapp as my starting point. I’ve also decided this is a good time to try out jsx. Jsx means I’m going to need to do some transformation. So now we need a build tool to turn my jsx infused javascript in to old javascript.
Hyperapp has instructions for using webpack as a build chain so I’ve now got the following in my stack so far:
- hyperapp
- jsx
- webpack
- babel
So to NPM:
npm init
npm i hyperapp
npm i -D \
webpack \
babel-core \
babel-loader \
babel-preset-env \
babel-plugin-transform-react-jsx
One thing to note is I’ve used babel-preset-env
instead of babel-preset-es2015
.
This means I’ll be able to specify an environment in the future that I want to target.
With the above installed I setup
A .babelrc
for babel:
{
"presets": ["env"],
"plugins": [
[
"transform-react-jsx",
{
"pragma": "h"
}
]
]
}
and webpack.config.js
to get webpack building:
module.exports = {
entry: "./index.js",
output: {
path: __dirname + "/dist",
filename: "bundle.js",
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}]
}
}
Something to actually build
I started off with a bit of tooling but now I want a skeleton app. Hyperapp is based on the elm architecture so an app has the following pieces:
- State - Dealt with as a plain javascript object.
- Actions - Which when triggered take the current state and return the next state.
- View - Takes the current state and renders it as html.
I started with the state. I created a file src/todolist.js
and decided
I’d have a list of todo
s.
In addition a nextTodo
which is the todo currently being written:
const emptyState = {
todos: [],
nextTodo: {text: "", id: 0}
};
With the above state in mind I created two actions. One which takes a string and uses it for the text of the next todo. And a second action which takes the nextTodo pushes it on to the list then prepares a new clean todo.
const actions = {
setNewTitle(state, _actions, newText) {
let nextTodo = state.nextTodo;
nextTodo.text = newText;
return {
nextTodo: nextTodo
}
},
addTodo(state) {
let updatedTodos = state.todos;
updatedTodos.push(state.nextTodo);
return {
todos: updatedTodos,
nextTodo: {text: "", id: state.nextTodo.Id + 1}
}
}
};
Now that I’ve got this basic (and possibly bug ridden - I’m getting to testing in part 2)
logic I’ll create a view. For now this can live in the javascript entry point index.js
:
'use strict';
import { h, app } from "hyperapp";
import {emptyState, actions} from "./src/todolist";
const view = ({todos, nextTodoTitle}, {addTodo, setNewTitle}) => (
<div>
<input type="text" id="new-title" onchange={e => setNewTitle(e.target.value)}/>
<button onclick={e => addTodo()}>add</button>
<ul>
{
todos.map((todo) => (<li>{todo.text}</li>))
}
</ul>
</div>
);
app({state: emptyState, view, actions});
This doesn’t do too much at the moment:
- Imports my initial state and actions from the file earlier.
- Hooks an input onchange up to the action that sets the title.
- Hooks a button up to the action that adds the new todo.
- Maps all the current todos in to a list.
- Creates a hyperapp app with the state, view and actions.
The final step is to wrap this in some html:
<!doctype html>
<html>
<body>
<script src="dist/bundle.js"></script>
</body>
</html>
Notice I’m using dist/bundle.js
as this is what my webpack build is going to output.
Running it.
I’ve created a build script in my package.json
file:
{
// Rest of the file
"scripts": {
"build": "webpack -p",
"test": "echo \"Error: no test specified\" && exit 1"
}
// more stuff
}
so running npm run build
creates everything for me.
I’m using intelij IDEA so I just need to click run index.html
and I’m good to go.
Debugging it
Everything looked fine running it but I wanted to get a better idea of what was actually happening. This is where I’d normally fire up a debugger. Since the code has been transformed and minified I needed to buid a source map.
Adding this to webpack was pretty straight forward. I just need to add
module.exports = {
//stuff
devtool: 'source-map',
//more stuff
};
now when I run the build my dist.js
file gets a map too. I’ve already got an intelij
debug extension installed in chrome so I create a few break points and
click debug index.html
.
Now that this shell is working I’ll stop for now and reflect on what I’ve got so far.
Questions
- Do I need webpack? What if I just target modern browsers?
- Do I need webpack? Are there simpler tools? Better tools?
- I’ve only started quite small and I’ve already got: hyperapp, jsx, webpack and babel. Is this normal?
- I’ve pulled out my initial state & actions. Is this a good organisation?
- What makes sure my view doesn’t try and act on items that don’t exist in the state?
- There’s a little bit of magic on how hyperapp passes actions to the view & how the actions can do partial state updates - Is this going to be confusing later?
Next step(s)
- Testing - I don’t really want to build much more than this without being able to verifying correctness. This is where I’ll bring in some tests.