超軽量Viewライブラリ「Hyperapp」の日本語ドキュメント風の何か
Introduction
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に挿入されるattributeschildren {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を返す関数だよ。引数にstate
とactions
をとるよ。
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
… 現在のstateactions
… アプリケーションが持つ大元の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_までご連絡ください。
-
英語が得意でない者が雰囲気で訳したものです。本家Hyperappとは無関係の個人的な記事です。 ↩