Hyperappでhead内のタグを書き換えるライブラリ、Hyperapp Helmetをリリースしました!

はじめに

Hyperapp Helmetという、Hyperappフレームワーク用のライブラリをリリースしました。

Helmetとは、React Helmetが有名/はしりですが、<head>タグの中を書き換えるコンポーネントのことを言います。

昨今の検索エンジンbotJavaScriptを解釈しますので、SSRはOGP/Twitter Card対応のためにあると言っても過言ではないと思います。その時に、ページ内容に合わせた<head>内タグの書き換えが必要になりますが、Hyperappにはそれに対応するライブラリがありませんでした。これで最後のピースが埋まった!

特長

Hyperapp Helmetは以下の特長を持ちます。

  • @hyperapp/render対応。renderToString()だけじゃなく、renderToStream()SSRする時も使えます。
  • @hyperapp/router対応。JSDOMと併用することにより、サーバサイドでも、ルーティングに合った<head>内タグの書き換えができます。
  • ネスト対応。親・子・孫……コンポーネントそれぞれで定義した<Helmet>が全て<head>内タグに反映されます。
  • TypeScript対応。

インストール

$ npm install hyperapp hyperapp-helmet
or
$ yarn add hyperapp hyperapp-helmet

使い方

公開メソッドは2つしかないので、README.md読めばわかると思いますが、軽く紹介しておきます。

<Helmet>

任意のコンポーネントで使える。<Helmet>タグで囲まれた部分が<head>タグの中に挿入される。keyアトリビュートが必須。

import { h } from 'hyperapp'
import { Link } from '@hyperapp/router'
import { App as A } from '../App'
import { Helmet } from 'hyperapp-helmet'

export const Counter = (): any => (state: A.State, actions: A.Actions) => (
  <div key="counter">
    <Helmet key="counter-helmet">
      <title>Counter: {state.count}</title>
      <meta name="description" content="Number Counting Page" />
    </Helmet>
    <h1>{state.count}</h1>
    <button onclick={() => actions.down(1)}>-</button>
    <button onclick={() => actions.up(1)}>+</button>
    <div>
      <Link to="/about">About</Link>
    </div>
  </div>
)

getHelmetNodes(view, state, actions) => VNode[]

アプリケーションのビューを探索し、該当の<head>内タグのVNodeの配列を返す。

import { h, View } from 'hyperapp'
import { App as A } from './App'
import { getHelmetNodes } from 'hyperapp-helmet'

const Fragment = ''
export const Html: View<A.State, A.Actions> = (
  state: A.State,
  actions: A.Actions
) => {
  const helmetNodes = getHelmetNodes(A.view, state, actions)
  return (
    <Fragment>
      <Fragment innerHTML="<!doctype html>" />
      <html lang="ja">
        <head>
          <meta charset="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <script src="/index.js" defer />
          {helmetNodes}
        </head>
        <body>
          <div id="app">{A.view}</div>
        </body>
      </html>
    </Fragment>
  )
}

こんだけ! サンプルプロジェクトを公開してあるので、動かして見てください。

開発

せっかくなので、小話をば。

経緯

  • 小さくて速いウェブアプリが作りたいな〜
  • Reactはエコシステムが大きいから開発が早いけど、サイズがデカいしな〜
  • おっしゃ、Preact挑戦してみたろ
  • このpreact-compatっての入れるとReact用のライブラリが使えるが、サイズがデカくなるぞ? う〜ん……
  • そういえばHyperappってのがあったな?
  • @hyperapp/routerでルーティングもできるし、@hyperapp/renderってやつでSSRもできる。全部小さい! ええやんけ!
  • (素振りリポジトリを作ってみる)
  • えっ、HyperappにHelmetないの? マ? 今時SSRはOGP/Twitter Card対応だから意味ないやんけ! う〜ん……
  • せっかくだし、作ってみるか!

実装

そんな難しいことはしてないのですが、迷いながら実装したところがあるので、説明させてください。

具体的には2点あり、 1. <Helmet>コンポーネントが<template>タグを返し、それがレンダリングされる。 2. <head>内タグの更新のとき、一旦全削除して、全追加している。

まず、1についてですが、Reactコンポーネントと違って、Hyperappのライフサイクルメソッドは外挿なんですよね。なので、なんらかのノードを返す必要がありました。

最初は<div style={{ display: 'none' }}>{children}</div>を返していたのですが、<div>の中に<title>とは入ってるのかっこ悪い……となって一度はchildrenを書き出さず、空の<div>だけ返してました。しかし、SSRをするに当たってノードを探索・解釈してみたんですが、実際にはマウントされないため、oncreateライフサイクルメソッドが実行されず、タグを格納するグローバル変数に入らないという問題が発生しました。

というわけで、またchildrenを書き出す実装に戻したんですが、上記の通り気味が悪いので、実際には表示されないエレメントは何かないかと探したところ、<template>が見つかりました。これなら<title>等があっても違和感が少ないと考え、これを使うという選択をしました。

現在の実装ではテンプレートとしては使ってないのですが、将来実際にテンプレートとして使って、<head>に流し込むというのもありうるのではないかなと思います。

次に、2についてですが、最初は実際に書き出したエレメントと架空のノードとの差分をとってパッチを当てる実装にしてたんですが、これだと、props/state変数をタグに使用したとき、同定が難しいという問題が発生しました。

ユーザーに逐一keyを設定してもらえば同定できるのですが、それは手間だろうということで、現在のように、<Helmet>毎に全削除&全追加という実装になってます。

おわりに

このライブラリを使うことにより、HyperappでSSRするに当たって、最後の障害がなくなったと思います。ぜひ、使ってやってください。

このライブラリを作るにあたり、@r7kamura 氏のkatatemaを参考にしました。氏にはTwitterで助言を頂いたりして大変お世話になりました。ここにお礼を申し述べます。

よし、これで次はアプリに取りかかれるぞ〜

『カンバン仕事術』を読んでいる3

6章 WIP制限を読んだ。

「デヴィッド・P・ジョイス」が誰だかググッてもよくわからなかった。

おーっ、人にWIP制限をかけるとか、作業項目をタスクに分割するとか、自由度が高い! 物理強い!!

カンバン仕事術

カンバン仕事術

『カンバン仕事術』を読んでいる2

5章 仕掛り作業を読んだ。WIPについて。

チケットに付ける[WIP]と紛らわしそう。「チケットのWIP」「カンバンのWIP」って呼び分けるのかな。それとも前者をWork in Progress、後者をWork in Processと呼び分けるのかな。(追記:角さんのTwitterでのツッコミで、「開発」列を「プルリクエスト」列と「マージ」列に分割すれば「チケットのWIP」などと呼ばないでいいことに気づいた)

「システムのヘルニア」のところでウッとなった。今作ってるやつもバグじゃないけど正しい実装とは思えないのが3つぐらいあって絡まり合ってる。

カンバン仕事術

カンバン仕事術

Clojureを勉強している20

4clojureの問題はギブアップして最後の章やった。難しくて億劫になってしまったもんで。

Clojure 1.7.0から入ったtransduceの説明だった。

これにて終わり。

Living Clojure

Living Clojure

『カンバン仕事術』を読んでいる1

いまのところ4章まで。おもろい。特急レーンのとこで「おっ、頭いいな。なるほど〜」と思った。

4章の作業項目カードは全部はできないなと思った(全部やる必要ないと書いてある)。既に買った付箋がそんなに大きくないので。Post-itの75x100mmのを買ったんだけど、もう一つ上のサイズの方がいいかもしんない。Amazonで見る限りは150x100mmは色のバリエーションがない。

カンバン仕事術

カンバン仕事術

『リモートチームでうまくいく』を読んだ

手を動かさないでよい本も読むことにした。

内容なんですが、流石実践者ということで一日の長があるな、と感じた。特に、2章3節の「オフィスにあってリモートにないものを補完する」が面白かった。オフィスにはある「存在感」「雑談」などを試行錯誤でリモートに導入する試行錯誤。恐らくこれで完成ではなく今も時おり改良が進んでいるのだろう。

また、2章5節で外部とのやりとりはメールは非同期メッセージングとして優秀だが記録・多人数での参照などで不完全なので「掲示板のようなもの」を作り使ってるというのがなるほどなと思った。私も受託開発してたときがあるんですが、メールだと重すぎるんよね。

リモートハッカソンとリモート飲み会はどうやろと思ったけど、これまた実践者ならではの細々とした諸注意がある。

あと1つ懸念点があって、同じ時間に作業するってのがどうにかならないものかと思った。オープンソースの開発みたいな感じだとやはりスピードは失われるような気もするし。半日分ぐらいいいやみたいな判断ではどうか。スピードはともかく、顔を見ながら作業できないというのはやはり厳しいか。う〜ん。

リモートチームでうまくいく マネジメントの?常識?を変える新しいワークスタイル

リモートチームでうまくいく マネジメントの?常識?を変える新しいワークスタイル

はじめに 第1章 リモートチームという古くて新しい働き方 第2章 リモートチームが実践している習慣と環境づくり 第3章 リモートチームの成功は企業文化にかかっている 第4章 リモートチームで変わるマネジメント 第5章 リモートチームで変わるワークスタイル 第6章 リモートチームで起きる課題を解決する 第7章 リモートチームに至るまでの道のり おわりに

Clojureを勉強している19

  • Week3
    • Day 1
      • To Tree, or not to Tree
        • バイナリツリーのチェックかー。3要素のコレクションだったらいいのかな?と思ったらfalseと空リストはアカンっぽい
        • できた。
      • Beauty is Symmetry
        • 安直に95の条件に(= (second t) (last t))を加えてみたが4つ目で死んだ。シンメトリックだからleftとrightが入れ替えなあかんのか
        • アカン。解けんかった。我慢できずに他の人の答え見た。我慢が足りない。

Living Clojure

Living Clojure

  • Chapter 10 Weekly Living Clojure Training Plan
    • How Do I Use This Training Plan?
    • What If I Miss a Day or Two?
    • What If I Don’t Understand the Exercise?
    • Week 1
    • Week 2
    • Week 3
    • Week 4
    • Week 5
    • Week 6
    • Week 7
    • Congratulations