Introduction

Hyperapp - GitHub

Hyperapp is a JavaScript library for building web applications.

HyperappはWebアプリケーションのフロント(主にView)を担うJavaScript用のライブラリです。このライブラリは3つのコンセプトで成り立っています。

  • 外部ライブラリに依存しない、超軽量1KBぐらいのライブラリ
  • ステートレスコンポーネント
  • Elm Architecture に則ったスケーラブル可能な仕組み

今回はHyperappのドキュメントを勝手に意訳1してみようと思います😌💡

Getting Started

Hyperappは器用に作られたもので、新規プロダクトに取り入れることはもちろん、既存のWebアプリケーションに組み込むこともできるよ。

一番簡単な方法は何と言ってもCDNだね。

<script src="https://unpkg.com/hyperapp"></script>

バージョンを指定したいかな? そんなときはこう。

<script src="https://unpkg.com/hyperapp@1.0.1"></script>

こんにちは世界

ではさっそくHyperappを使って「こんにちは世界」を表示させてみよう。index.htmlを用意して以下のコードをコピペしてブラウザで見てみよう😄

HyperappではJSXを使った記法と、ES6のテンプレートリテラルを使ったHyperxという記法の2つが使えるよ。How wonderful!! それぞれのコード例を用意したから好きな方を使ってね。

JSXで書いた場合
<body>
  <script src="https://unpkg.com/hyperapp"></script>
  <script src="https://unpkg.com/babel-standalone"></script>
  <script type="text/babel">

  const { h, app } = hyperapp
  /** @jsx h */

  const state = {
    greeting: "こんにちは世界"
  }
  const actions = {}
  const view = state => <h1 id="title">{state.greeting}</h1>

  app(state, actions, view, document.body)

  </script>
</body>
Hyperxで書いた場合
<body>
  <script src="https://unpkg.com/hyperapp"></script>
  <script src="https://wzrd.in/standalone/hyperx"></script>
  <script>

  const { h, app } = hyperapp
  const html = hyperx(h)

  const state = {
    greeting: "こんにちは世界"
  }
  const actions = {}
  const view = state => html`<h1 id="title">{state.greeting}</h1>`

  app(state, actions, view, document.body)

  </script>
</body>

さて、ブラウザでは何が起きたかな?
ブラウザはHyperxやJSXをCDN経由でダウンロードし、scriptの部分をコンパイルして描画したよ。

この例ではHyperappとは何かを手軽に知ることができたと思うけれど、これだけだとWebアプリケーション開発の例としては少々物足りないよね。わかるよ。

では、次にWebpack、Browserify、Rollupをつかったビルド環境のセットアップの例を見てみよう。

Build Setup

Webアプリケーションの開発環境を用意するには3つの要素が必要なんだ。

なぜこれらが必要かというと、JSX/Hyperxで書かれたソースをコンパイルするためなんだ💪 コンパイルされると、Hyperappのh関数という仮想DOMを生成するための関数になるのだけれど、それについてはまた後で語るからね。

コンパイル前(JSX/Hyperx)

<h1 id="test">Hi.</h1>

コンパイル後

h("h1", { id: "test" }, "Hi.")

こんな具合だよ。いい具合だね。
さぁ次の章ではJSXとHyperx各々のためのビルド環境のセットアップを学ぶよ。ついておいで!

JSXを使う環境の用意

JSXはXMLと同様データの記法のひとつだよ。知ってたかい? これを使うことでHTML(テンプレート)とJavaScript(処理)を一つのファイルに混在させることができるよ。

JSXを使うには上述の通りコンパイルが必要なんだ。JSXをh関数に変換し、ひとつのjsファイルにバンドルし、配信しなければならないからね。ではさっそくビルド環境の用意をしよう。

Browserifyを使う場合

必要なモジュールのインストール
$ npm i -S hyperapp

$ npm i -D babel-plugin-transform-react-jsx babel-preset-es2015 babelify browserify bundle-collapser uglifyify uglifyjs
.babelrcの用意
{
  "presets": ["es2015"],
  "plugins": [
    [
      "transform-react-jsx",
      {
        "pragma": "h"
      }
    ]
  ]
}
ビルドの実行
$(npm bin)/browserify \
  -t babelify \
  -g uglifyify \
  -p bundle-collapser/plugin index.js | uglifyjs > bundle.js

Webpackを使う場合

必要なモジュールのインストール
$ npm i -S hyperapp

$ npm i -D webpack babel-core babel-loader babel-preset-es2015 babel-plugin-transform-react-jsx
.babelrcの用意
{
  "presets": ["es2015"],
  "plugins": [
    [
      "transform-react-jsx",
      {
        "pragma": "h"
      }
    ]
  ]
}
webpack.config.jsの用意
module.exports = {
  entry: "./index.js",
  output: {
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      }
    ]
  }
}
ビルドの実行
$(npm bin)/webpack -p

Rollupを使う場合

必要なモジュールのインストール
$ npm i -S hyperapp

$ npm i -D rollup rollup-plugin-babel rollup-plugin-node-resolve rollup-plugin-uglify babel-preset-es2015-rollup babel-plugin-transform-react-jsx
rollup.config.jsの用意
import babel from "rollup-plugin-babel"
import resolve from "rollup-plugin-node-resolve"
import uglify from "rollup-plugin-uglify"

export default {
  plugins: [
    babel({
      babelrc: false,
      presets: ["es2015-rollup"],
      plugins: [["transform-react-jsx", { pragma: "h" }]]
    }),
    resolve({
      jsnext: true
    }),
    uglify()
  ]
}
ビルドの実行
$(npm bin)/rollup -cf iife -i index.js -o bundle.js

Hyperxを使う環境の用意

JSXのときとインストールするファイルが少々変わるだけなので割愛するよ 😌

API Reference

ここではHyperappのモジュールや関数の使い方について、サンプルと併せて紹介するよ。

Hyperappは大きく分けて2つの仕組みを提供しているんだ。

  • h 関数 … 仮想DOMを生成する関数
  • app 関数 … Hyperappを利用したApplicationを実行する関数

h

仮想DOMを返す関数だよ。ここで言う仮想DOMとは、ネストされたDOMのツリーをJavaScriptのオブジェクトとして表現しているものだよ🌴

Syntax

h(name, props, children)
  • name {String} … 「div」など、HTML上でのタグ名
  • props {Object} … Elementに挿入されるattributes
  • children {String | Array} … 子要素
h("a", { href: "#" }, "next page")

// return object
// {
//   name: 'a',
//   props: {
//     href: '#'
//   },
//   children: 'next page'
// }

app

HyperappによるWebアプリケーションを起動するよ。これを呼び出すことで全てが始まる。オプションを添えられるよ。

app(state, actions, view, container)

State

アプリケーションのStateを管理するオブジェクトだよ。Objectでないとダメだよ!

View

仮想DOMを返す関数だよ。引数にstateactionsをとるよ。

const state = {
  on: true
}

const actions = {
  toggle: () => state => ({ on: !state.on })
}

const view = (state, actions) => (
  <button onclick={actions.toggle}>{state.on.toString()}</button>
)

app(state, actions, view, document.body)

Actions

Webアプリケーションの振る舞いを定義する関数のコレクションだよ。actionsは一般的にstateを更新するために利用されるよ。そのため、返り値が新しいstateであることがしばしば。

const actions = {
  setValue: value => ({ value }),
  addValue: value => state => ({ value: state.value + value }),
  addValueLater: value => (state, actions) => {
    setTimeout(() => {
      actions.addValue(value)
    }, 1000)
  }
}
  • data … actionの処理(モデルの更新)に必要な情報
  • state … 現在のstate
  • actions … アプリケーションが持つ大元のactions
const state = { count: 0 }
const actions = {
  sub: () => state => state - 1,
  add: () => state => state + 1
}

const view = (state, actions) => (
  <div>
    <h1>{state.count}</h1>
    <button onclick={actions.sub} disabled={state.count <= 0}>
      -
    </button>
    <button onclick={actions.add}>+</button>
  </div>
)

app(state, actions, view, document.body)

Global Events

app関数が返すオブジェクトにstateと繋がっている元のactionsがある。viewに渡されるactionsと同じで、actionを呼び出す時に、stateが更新される!

const state = { x: 0, y: 0 }
const actions = {
  move: () => ({ x, y }) => ({ x, y })
}
const view = state => <h1>{state.x + ", " + state.y}</h1>

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

addEventListener("mousemove", e =>
  MyApp.move({
    x: e.clientX,
    y: e.clientY
  })
)

Lifecycle Methods

仮想DOMのライフサイクルにまつわるイベントをハンドリングできるよ。

  • oncreate … ElementがDOMとして構築されたとき
  • onupdate … Elementの要素が更新されたとき
  • onremove … ElementがDOMから消える直前
  • ondestroy … ElementがDOMから消える直後
const node = document.createElement("div")
const editor = CodeMirror(node)

const Editor = options => {
  const setOptions = options =>
    Object.keys(options).forEach(key => editor.setOption(key, options[key]))

  const oncreate = elm => {
    setOptions(options)
    elm.appendChild(node)
  }

  const onupdate = _ => setOptions(options)

  return <div oncreate={oncreate} onupdate={onupdate} />
}

Afterword

良さそうなところ

  • 小さな開発用ツールなどをつくるときには高速で実装できるし、実行速度的にも◎
  • 特にReactでJSX使ってた人はとっつきやすいかも
  • 小さなライブラリなので、初めてのOSSコードリーディングにはもってこい
  • 同じく、初めてのOSSコントリビュートにはもってこい(?)

ちょっと考えものなところ

  • テンプレートファイルを外出しできないとマークアップエンジニアとの分業がしにくいかも
  • もう少し細かい単位でライフサイクルのイベントハンドリングをしたくなるかも
  • viewの共通部品の定義(Abstract的なもの)とか欲しくなりそう

一部訳すのをさぼりました。すみません😵

初めて翻訳(の真似事)をしたのですが、自然と海外ドキュメンタリーの吹き替えみたいな声が脳内再生されました。「私ゃ失敗こいちまってさ」的なやつです。ともあれ、とても楽しかったですしHyperappを好きになれて満足しました。

間違いや指摘箇所があれば@aloerina_までご連絡ください。

  1. 英語が得意でない者が雰囲気で訳したものです。本家Hyperappとは無関係の個人的な記事です。