Frontend in 2017 by a non front end dev - 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:

  1. Javascript - I’ve played a bit will elm. But I want to see what’s possible with js right now.
  2. I’ll do some research but not much. So the tools will hopefully be current but not bleeding edge.
  3. If I get stuck in weird frontend bug hacks I’m hitting eject.
  4. 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 todos. 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.
comments powered by Disqus