レンダリングから始めるNuxtのSSR
SSR?サーバサイド…?
Vue 3やVue 3に一定のルールを与えるNuxt 3には、(クライアントサイドではない)サーバサイドでレンダリングを行うサーバサイドレンダリング(以下、SSR)と呼ばれるモードを備えています。
Vue 3でもNuxt 3でも、SSRを前提とした ClientOnly
コンポーネントや Server Components といった機能が提供されています。
https://www.youtube.com/watch?v=17zBODTpuoo
いづれもSSRの理解が前提となっているため、公式ドキュメントを参考に周辺の知識を深堀りしてみます。
前提条件
- この記事には、繰り返し「レンダリング」という言葉が登場します。コンテキストにより解釈幅があるようですが、ここでの意味はウェブで使われるそれに限定されます。
- React等他のUIフレームワークでもSSRは実装されますが、本記事ではVue.jsを話の土台にします。
- 「SSR理解してみた」系記事がたくさんある中で、SSRを使う理由はよく語られていることと、本記事の趣旨が異なるため、SSRはあるユースケースにおいて有用なものである前提に立ちます。
レンダリングから始めよ
SSRは、文字通り「サーバサイドでレンダリングする」技術です。一度でもウェブアプリケーションを書いたことがあれば「サーバサイドでレンダリング」した経験を持っているはずで、なんでわざわざ「サーバサイドレンダリング」というのでしょうか。
そもそも、筆者は記事作成前時点で「レンダリング」(と表示、描画)の差をあまり意識してこなかったことに気が付きました。そこで、理解のとっかかりとして「レンダリング」とはなにか、という話からはじめてみたいと思います。
「レンダリング」とは
“render” という言葉には「描く」といったニュアンスが含まれます。”draw”に近い言葉のようです。英英辞書にはいくつかの定義がありますが、例えば以下のような定義が近いです。
to represent something in a work of art or a performance:
また、ここでは深く立ち入りませんが、ウェブに限らずコンテンツを生成する作業を指して「レンダリング」と呼ぶことがあるようです。表示や描画まで含むケースもあるようですが、いづれにしても表示する元ネタを生成するプロセスとして広くレンダリングという言葉は使われています。
一般的に、ウェブサーバはリクエストを受け取るとHTMLの文字列(以下、マークアップ)を作りクライアントに返します。HTMLを返す作業を描くプロセスになぞらえて「レンダリング」と慣習的に呼んでおり、「レンダリング」それ自体は必ずしもブラウザと直接関係がないものと言えます。
つまり、ブラウザがなくともレンダリングはできるということでもあります。
サーバは文字列のマークアップを送信し、ブラウザはこれを解釈、DOMツリーに変換することでブラウザにコンテンツを表示できる。レスポンスヘッダのContent-Type
がtext/htmlで指定されることで、受信したブラウザは文字列をDOMツリーの元ネタと認識できるのです。
マークアップを返すということ
マークアップの生成をサーバサイドでのみ行うレンダリングを、SSRと対比するため以後、独自に「テンプレートレンダリング」と呼ぶこととします。これはVue 3/Nuxt 3に馴染みがなくとも、古来から行われてきた伝統的なレンダリング手法で、ウェブアプリケーションの基本的な振る舞いでもあります。
たとえば、Express.jsによるテンプレートレンダリングの例です。
app.get('/', (req, res) => { res.render('index', { title: 'Hey', message: 'Hello there!' }) })
ブラウザから任意のエンドポイントにアクセスされたら、マークアップをすべて1から「レンダリング」しています。特に目新しいことはありません。
Express.js同様、Ruby on RailsでもSpringBootでも、マークアップを返却していればサーバサイドでレンダリングを行っていることになります。
例えば、Ruby on Railsでもマークアップを返す関数は render
関数で、これもレンダリングと呼ぶことができます。Ruby on Railsでは「ビューをレンダリングする」という言い回しをしていました。
Controllerの redner
関数で ビューを返します。
class UsersController < ApplicationController def new end def create # ... 中略 ... render "new" end end
オプションを指定しなければ Content-Type
が text/html
に指定されるので、受信したブラウザが文字列をマークアップと認識できます。
このように、ウェブの世界では昔から「マークアップを作って返す作業」を「レンダリング」と呼んできたのでした。そして、ウェブにおけるレンダリングはマークアップの生成を指すケースが大半なので、ブラウザにおけるDOMツリーの構築やコンテンツの表示は厳密にはレンダリングと区別されるべきでしょう。
Vue 3におけるSSR
我々がさきほど、個別に認識しやすいようつけた「テンプレートレンダリング」に呼び方がとても似ていて、かつ初見では混同しやすい概念に「サーバサイドレンダリング」があります。
Vue 3はSSRについて、公式ドキュメントに丁寧な解説をつけています。
同じコンポーネントをサーバー上で HTML 文字列にレンダリングし、それをブラウザーに直接送信し、最終的にクライアント上で完全にインタラクティブなアプリケーション内に静的なマークアップを"hydrate(ハイドレート)"することもできます。
サーバで初期表示に使うマークアップのレンダリングをするところまではテンプレートレンダリングと同じです。
いっぽうで、テンプレートレンダリングと違い、JavaScriptはバンドルせずにマークアップのみをブラウザに返却します。
少しだけ、実装に近い概念に踏み込んでみます。Vue 3では createSSRApp
でAppインスタンスを作ることでSSRに対応したレンダリングを実行できます。
Express.jsと組み合わせた、簡易的なSSRのサンプルコードが公開されています。とてもわかりやすいのでそのまま引用します。
const server = express(); server.get('/', (req, res) => { const app = createApp(); renderToString(app).then((html) => { res.send(` <!DOCTYPE html> <html> <head> <title>Vue SSR Example</title> <script type="importmap"> { "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } } </script> <script type="module" src="/client.js"></script> </head> <body> <div id="app">${html}</div> </body> </html> `); }); }); server.listen(3000, () => { console.log("server listening ..."); });
get
関数のコールバック冒頭に来ている createApp
関数はクライアントサイドと共有しているファクトリメソッドのようなものです。なお、クライアントサイドと生成するマークアップを共有していることはSSRの重要な概念であるので、念頭をおいておくと良いです。
export const createApp = () => { return createSSRApp({ data() { return { count: 0 }; }, template: ` <button @click="count++">increment</button> <p>count: {{ count }}</p> `, }); };
renderToString
からつながる then
関数のコールバック引数はcreateAppが返すマークアップです。標準出力に出してみると以下のようになります。
<!--[--><button>increment</button><p>count: 0</p><!--]-->
ブラウザで見ると、同じように[]で囲まれた箇所がHTMLとして解釈されていることを確認できます。
SSRはテンプレートレンダリングではないのか
ここまでで、SSRはVue 3を使ったサーバサイドにおけるレンダリング技術だとわかりました。いっぽうで、それではさきほど定義したテンプレートレンダリングとなんら変わらないという疑問が浮かびます。
そこで、ここからはSSRとテンプレートレンダリングを分けるものがなにかを追いかけてみます。
テンプレートレンダリングは、JavaScriptおよびマークアップをひとまとめにして返却するレンダリングで、かつクライアントサイドではレンダリングされたマークアップの読み込み以外のことはしない。
ハイドレーション
そもそも、Vue 3/Nuxt 3といった宣言的なUIフレームワークは根底に「クライアントサイドでDOMを構築できる」という強烈な特徴を持っているのでした。SRモードにおいてもその特徴は活用されます。
さて、SSRモードを使う場合、、サーバから受信したマークアップはHTMLとして解釈できる状態になっているものの、リアクティビティや動的DOMの変更を含まない静的な構造になっているのみです。
試みに、さきほど引用したボタンのマークアップを、ブラウザでクリックしてみます。なにも起きないことがわかります。
<button @click="count++">increment</button>
つまり、静的なマークアップに対して各種イベントハンドラを組み込むステップが必要となります。
SSRモードでは、サーバから受信したマークアップをもとに、ブラウザ上でDOMツリーを構築し、さらにJavaScriptを使ってリアクティビティや仮想DOMの構築を行うプロセスを行い、動的なDOMに変換するそうしたプロセスをハイドレーションと呼びます。
Vue 3をベースにしたUIフレームワークであるQuasarにもハイドレーションの説明があります。
Client Side Hydration | Quasar Framework
Hydration refers to the client-side process during which Vue takes over the static HTML sent by the server and turns it into dynamic DOM that can react to client-side data changes.
Since the server has already rendered the markup, we obviously do not want to throw that away and re-create all the DOM elements. Instead, we want to “hydrate” the static markup and make it interactive.
テンプレートレンダリングと比較すると、Vue.jsにおけるSSRは受信したDOMを仮想的なものとして動的に扱えるようにするのが大きく異なる。
おまけ: Hydrationの語源
クライアントサイドで動的なDOMにするプロセスを”hydration”と呼ぶのが、一般的な言い回しでなくてわかりづらかったのですが、ReactやVue 3といったUIフレームワーク固有の用語のようです。少なくとも他の用法は観測できませんでした。
他のSSRの解説記事に引用されていた、こちらの回答がイメージ持ちやすそうです。
急速真空乾燥したインスタント麺を、熱湯三分でもとどおりの味と香りにするようなものです。
その他
SSRにおけるライフサイクルフック
ここまでで述べたように、SSRモードではサーバサイドで静的なマークアップをレンダリングするため、初期レンダリング時点ではDOMの更新やリアクティビティが発生されない状態となります。そのため、SSRを利用する場合 onMounted
や onUpdated
といったライフサイクルフックはトリガーされません。
setup()
あるいは<script setup>
のルートスコープでクリーンアップが必要になるような副作用を発生させるようなコードは避けるべきでしょう。
実業務でSSRを使う場合は、それら初期化型ライフサイクルフックがトリガーされないことに留意する必要があります。
約6ヶ月間、毎日コミット頑張ってみた話
約半年ほど、毎日1コミットしようと決めてから、少し失敗もありつつもやりきれました、という話です。
きっかけ
直接のきっかけはこちらのスライド、Write Code Every Dayに触発されたからです。
このスライドを見て、「t-wadaさんですらチャレンジだったんだ」と衝撃を受け(当たり前にやっていそうだと思っていた)、それなら自分にとってもよい機会になりそう、というのと当時手を動かす機会をすっかり失っていたので手慰みにもなるだろう、といった理由でやってみることにしました。
あとはgithubの芝生がすごくて、一回くらいやってみようかなと思ったのもあります。
ただ、はじめは何を得たいか、そこまで深く目的は置かず、見切り発車的にチャレンジをはじめてみました。
ルール
何もなくやっていたわけではなく、いくつかルールを設定していました。とはいえ、かなりシンプルなものでもあります。
- 毎日1コミット以上すること
- 意味のあるコードを書くこと(ドキュメントやコメントの更新だけはノーカン)
そもそも、「続けること」を目標に始めたので、高い目標を置いてやめてしまうような状況を避けることを一番の優先事項としました。つまり、まずは毎日1コミット以上すればOKとしました。
2つ目なのですが、さきほどのスライドに「意味のあるコードを書く」とあり、続ける上で効果を得るためにこれだけは守ったほうがよいと思い実践していました。
また、自分も書いたものはすべてgithubに置いて見える成果物としていました。これはOSS化したほうがよいといった高尚なものではなく、たんにやったことの可視化程度です。
毎日続けられて、その上でなにかを得ようとする際にきつすぎず、それでいてちょうど良い縛りだと思ったのでほとんどそのまま参考にしていました。
どうなったか
だいたい半年コミット達成した(と言いたい)。
間に何日か抜けているのは
- ワクチン接種後の副作用でそもそも起きているのすらしんどかった時期がある
- どうしてもネタを絞り出せなかった
- まとまった区切りまでいったけどコミット忘れてた(笑)
でした。なので正確には達成したとは言い切れないのですが…
3つめは純粋に忘れてただけなので悔しいですね。
とはいえ、旅行中も少しずつ進められていたし、トータルで見ると多くのものを得られたので良しとします(笑)
その後、仕事をどうしても優先したい時期が2週間ほど続いたので、まとまった期間トライできたということで一度チャレンジを打ち切ることにしました。
なお、現在は時間取っても調整できるようになったので再開してみています(再開というかもう習慣のようなものなのですが…)。
得られたもの
はじめてから2ヶ月くらいは変化感に自覚的ではなかったものの、最近変化を感じるようになりました。
成果物(コード類)
人生の中でもこれでもかというくらいしっかりコード書いた時期だったと思います。業務でコードを書くのと自分のものを自分でメンテするのだと方向性が違っていて、制約がかなり小さい中でまとまったコードを書けていました。
このチャレンジの間に転職を挟んでいるのもあり、転職先での技術スタックキャッチアップは大きな課題でした。そういった事情もあり、特にフロントエンド/UIコンポーネントは実務経験がほぼゼロだったため、よくテーマにしていました。
フロントエンド
Vue.js Nuxt.jsのコードを中心に5つほどリポジトリを作り、それぞれ80 - 100コミットくらいずつ更新していました。内容はばらつきがあって、
- 普通のメモ帳アプリ
- テーマを決めて個人開発
- リアクティブの実装
などなど、宣言的UIを使ったUI作りをやっていました。業務ではVue.js、Nuxt.jsを使っているのでフレームワークの使いこなしを中心に、まずは人並みにアウトプットできるようコーディングしていました。
- https://github.com/simonNozaki/artproducesmana
- https://github.com/simonNozaki/capacidor
- https://github.com/simonNozaki/miactive
転職先にフロントエンド周りを色々教えてくださる方がいて、そのおかげもありスムーズにキャッチアップが進みました。
バックエンド
前述のとおり、ちょうど転職と軌を一にして技術キャッチアップの必要性が高まった時期でした。というわけで、バックエンドはRailsで、RubyとRailsが手に馴染むレベルにはいくつか書いていました。
Railsチュートリアルちょっと流したくらいで、人に見せるほどのアウトプットはなかったのですが!
システムプログラミング
以前から興味のあったプログラミング言語作りもテーマにしていました。当時、いろいろな課題感から、自分が仕事で使うならこういう仕様になっていてほしいというのがぼんやりと、けどはっきりとした感覚としてあって、いつか作ってみたかったものでした。
リアルタイムではなかったものの、WEB+DB PRESS Vol.125が作って学ぶプログラミング言語の回で、興味のあった内容なのでトレースしつつ拡張するといったことをやっていました。
その他、新しい言語を学ぶときにLispインタプリタを書いてみているというのをTwitterで見かけ(その少し前にZig試されているのを見た)、今後自分もサンプル作るならそれだと思ったので、その参考になるものをRubyやClojureで書くなどしていました。
システムプログラミングから得られたものはとても多いのですが、それを振り返っていると紙幅が半端じゃなくなってしまうのでここでは割愛です。勉強にもなったし、できてから遊べて楽しいですよね。
スキル、習慣
技術的な側面はもちろん、マインド面でも少し変化が出てきたように思います。
もはやエディタ開かないと気持ち悪い
まず思いつくものはこれです。エディタを開かないと気持ち悪く感じる体質になりつつあります。
エディタを開いて静かに考えていると、ゾーンのような没頭できる時間があると思います。その傾向がより顕著になった感覚で、落ち着いて考える時間から安心感・充実感を得られるようになりました。その時間は結構大事にしていて、これからも継続していきたい。
できることが増えた
何回か言及しているとおり、いくつかの理由から技術的なキャッチアップを迫られていたのですが、お題を決めて継続的にコードを書いていたので純粋に扱える技術の幅は広がりました。
仕事で書くようになったのもあって、特にフロントエンドはほぼゼロスタートでしたが今はとりあえずなんとか仕事できるくらいにはなったかなと…まだまだなのは事実なんですが、少なくとも抵抗なく業務にあたったり周辺知識に触れられるようになったとは思います。
明らかにコーディングが早くなった
早ければいいわけではないし、毎日書いていることだけが寄与しているわけではないけど、考えてからコードにするまでが明らかに早くなったと思います。
それまでは、このへんがうーん、みたく少し考えてから書いていたけど考えながら書いてみてもあまりおかしなアウトプットにはならなくなった気がします(どっちかというと仕事の影響が大きいかもしれない…)。
ただ、軽く書いて試す抵抗が小さくなったので、アイデアを試してみたくなってから試して検証してみるサイクルが短く、かつ楽にはなりました。
その他
達成感
こうやって振り返ってみると、まとまったコードが手元に残るのは達成感があります。githubに置いていたので、コミット履歴とともにアウトプットが可視化され、結構ちゃんとコーディングしてたんだなあなんて思いました。
本当は1年くらいがんばって継続してみても良かったかもと思う反面、ほぼ習慣化してもいるので嫌でも数ヶ月は継続する状態でもあります。
お題が難しい
自分は、毎日コードを書くと決めて取り組むのは全然苦にならない一方で、お題決めにちょいちょい苦戦していました。
この期間はライブラリやフレームワークといった役立ちそうなものに興味がなかったのもあり、ネタを決めるのに難航していました。jQueryの作者も、jQueryという壮大なアジェンダがあったので、継続してコミットする必要性が高かったんだと推測していますが、自分の場合は続けることをテーマにはじめたのではて…?となることもあり…
今でもネタに困ることもありますが、100 - 150コミットくらいは必要そうな、一定入り組んだテーマを常に仕入れたりアンテナ貼っておく必要はあるかなと思います。
平日は仕事やなんかでそこまで考える時間も取れなかったりするので、ネタ決めや情報収集は土日にやるようにしていました。
意味のある単位で毎日コミットするには工夫がいる
まとまりの大きい変更を加えたいがまとまった時間が取れないとか、書く前に時間とって考えたいとか、そういう状況もままあって、毎日毎日いいコードを置けたかというとそんなことはなかったです。
そういうときは、正直本質的ではないなという気もしつつ、毎日コミットすることを優先して作業の区切りでwipにしていました。なので、次の日に考え直してまるっと書き換えたり消したりといったこともやっていました。
このへんが業務と違うところで、普通は意味のある単位でコミットを置いていくのでコミットすることを目的とした作業単位を意識する必要が出てきます。
最近は少し慣れてきて、まとまりが大きそうなら後回しにするとか、そもそも形にする速度が上がったのもあってwipにするケース自体が減りました。やっていくうちに慣れるので、はじめのほうは雑にでもコミットすることを優先してもいいんじゃないかとは思います。
おわりに
深く考えずにやってみようかな程度ではじめたチャレンジで、特になにかを得られると期待していたわけではないけど、自分の作ったもの・書いたものが残っていくのがたのしかったです。
収益を生むほどブログが続くブロガーは、全体の中でも非常に少ないそうですが、プログラミングも同じで興味がないとなかなか続けるのが難しいとは感じます。自分は、なにかしらテーマを決めるだけで飽きずに続けられたのですが、そうではない場合、もっといろいろな工夫が必要そうではあります。
今後
毎日なにかを残すこと自体は問題がなくなりつつあるので、少し縛りをキツめにしてみるチャレンジ中です。
「イケてないコード出してくる人は時間を与えてもいいものを出せない」という話を聞いたことがあり、最近自分もたしかになと思ったので「一回で人に見せられるものを書く」ようにしています。この話は、昔スポーツをしていたときに聞いた「練習は本番のように」と似通っていて、その観点でも雑に残すのはやめようとしてみています。
で、まだまだ試行錯誤中です。限られた時間でまともな成果物を生み出していきたい。
HTML解体新書を読んだ
はじめに
フロントエンドの開発に関わるようになり、薄々気づいていたけどもフレームワークの使い方やコンポーネント指向開発以上にhtmlそのものにまつわる悩みが増えました。
知らないことばっかりだった、まだまだがんばります。
特に勉強になったこと
"内容モデル"
この概念を知っただけでもこの本を読んだ意味がありました。普段htmlでマークアップするときには、どの要素がどの要素に入るかを明確に理解してなくて、なんとなくこれはこの中だな、くらいでやってしまってました。HTML Living Standardが読めそう
現行HTMLの基礎になっている概念をインプットできたのでHTMLの言語仕様であるHTML Living Standard も適宜参照できそうです。
レイアウトを作っているときに「ここに横に並べたいときどうすれば」となるので、そのときに扱っている要素の性質を仕様をもとに考えられるようになったかなと思います。
htmlをとっかかりにcssの仕様(Cascading Style Sheets Level 2 Revision 2 (CSS 2.2) Specification)も読めるようになったのでちまちま読んでいます。
考察: ウェブアプリケーションとhtml
書籍を通じて、htmlの要素(タグの種類)とそれの性質がよく使うものを中心に紹介されていました。そもそも、htmlはハイパーテキストをマークアップするための規格であるので、文書には見出しがあって小見出しがあって段落があってその中にリンクが埋め込めて...といった、文書を書くためのツールキットの側面が強いです(もちろん広範なウェブ技術に対応するべく拡張はされていますが)。
昨今DXだ、デジタライゼーションだなどITに推進の機運が高まり続けていて、並行して個人が使えるコンピューティングリソースが潤沢になっています。すると、そのような有り余るリソースを土台にリッチな動きができるウェブアプリケーションの需要も高まり、UIを構成するhtmlには文書を作る以上の役割を担わせています。実際、UIを作っているとタグの並びと見た目があまり噛み合ってない状況の遭遇します。
本来ハイパーテキストをマークアップする規格であったhtmlを入り口にjsを使ったapiコール、非同期での仮想DOM操作などなど飛び技を駆使していることを考えると、一周回ってそのレイアウトにこのタグを使うべきなのか?本来の用途に適しているか?を熟慮する必要性が高まっているなと感じました。ボタンクリックで出てくるドロップダウンをdivでやりきることはできるけど、アクテシビリティや本来の用途考えるとselectで良いケースがその一例です。
書籍で勉強になったこと
ここからは各章でなるほどと思ったことのメモです。
内容モデル(Content model)
ある要素がどのような要素に属することができるかは、ルールで決まっている。このルールのことを内容モデル(Content Model)と呼ぶ。
内容モデルには以下のようなものがある:- Metdadata
- Flow
- Phrasing
- Sectioning
- Heading
- Interactive
- Embeded
<span> <p> このネストはできない </p> </span>ウェブアプリケーションのマークアップによく用いられるdivやp、ulはFlowコンテンツ。spanやstrong, codeといった段落内テキストを修飾するような要素はPhrasingコンテンツ。 以下のようなマークアップはFlowコンテンツにPhrasingコンテンツが含まれる例ということになる。
<p> <span>text</span> </p>
補足: ブロックレベル要素とインラインレベル要素
ブロックレベル要素、インラインレベル要素はCSSの視覚要素レベルで、cssの 視覚整形モデル とHTMLの内容モデルは本質的に異なる概念。 HTML5でレベル分類が一新され、HTML4でインライン要素とされていたものはPhrasingコンテンツとなった。ブロックレベル要素とされていたものはFlowコンテンツに相当するものの、 FlowやPhrasingも含むため1:1に対応しない。
htmlの主要な要素
セクショニング
sectionやnavを用いることで見出しに続くセクションを明示できる。見出し要素(h1 - h6)が入っているFlowコンテンツはsection
を使わずともセクショニングされる。
body, header, footer
html要素2番目の子で、文書の表示する内容を示す。セクショニングルートになる。それぞれ、一つのhtmlに一つだけ含められる。section
一般的なセクションを表現する。通常見出し要素を置くが、それができない場合はdivで囲って代用できる。style
メタデータコンテンツ。metaやtitleと同じ内容モデルなので、`head`に置くことができるグルーピング
blockquoteはFlowコンテンツで、文字をそのまま置くこともできる。pで囲った文字列をそのまま置くこともできる。
別のソースから引用されたセクションがここに表示されます。
整形済みテキスト
preは改行やスペースをそのまま維持した、整形済みテキストとして表示する。
ul li { list-style: none; }
列挙
- ol、ulは自身とliのみ子要素になれる
- ol, ulは空でもよい
- liはFlowコンテンツ。他のFlowコンテンツないしはPhrasingコンテンツを含むことができる。
main要素
mainはその性質上、bodyに一つだけ存在することを想定した要素。hiddenで隠せば複数置くことができ、jsなどでmainの出し分けを行うユースケースを考えられる。mainはbodyの直下に置くこと。
div
divに特別な意味を持たないFlowコンテンツ。主にスタイルやレイアウトのために付与する使い方がある。
テキストレベルセマンティクス
テキストレベルセマンティクスはテキストをマークアップする用途で用いられ、すべてPhrasing contents。
テキストマークアップの要素
テキストレベルで修飾する、Phrasingコンテンツのマークアップ例。すべて対応するHTMLタグでマークアップされている。リンク関連
a
内容モデルはtransparent。親要素の内容モデルを引き継ぐので、divの子要素になっていればFlowコンテンツのように振る舞う。
<div> <a> <h1> 見出しでもクリックできる </h1> </a> </div>
ウェブにおけるリンクは重要度が高い。そのためリンク要素のアクセシビリティには十分留意すること。
- リンク先が推測できること e.g. "こちら" を避ける(here症候群)
- リンクボタンはあくまでリンクなので、支援技術は "リンク" として認識する。
link要素
Metadataコンテンツで、終了タグがない、単独で存在できる要素。- linkはaと異なり、html文書が参照するリソースとの関係を表現する要素。リンク先リソースの種類はrel属性で指定される。
- 使用できる文字列表現はHTMLリンクタイプに定義される。
- 多くの場合、relはhrefと一緒に用いてリンク先の実体とそれの種類を設定する。
stylesheet
にした典型的なマークアップ。
<link rel="stylesheet" href="./style.css">
テーブル
tableの内容モデルは複雑。thead, tbodyなどを子要素にできる。trを直接配置できるが、tbodyが補われる。
tr
テーブルの行を表現する。内容モデルはtd, thのみ。td, thはどちらもFlowモデル。ただしthの子孫にheader, 見出しを含められない。
変更日 | 商品名 | 在庫数 |
---|---|---|
商品1 | 50 | |
商品2 | 70 |
caption
テーブルにキャプションをつける。省略可能。
フォーム
ユーザが入力した情報をサーバに送信する機能があり、そのための要素が存在する。ユーザがテキストを入力するパーツをフォームコントロールと呼ぶ。
スクリプティング要素
以前のブラウザはVBScriptが動くこともあったが、現在はJavaScriptの動作のみ想定されている。
script
script
はほとんどの要素の子要素になることができ、Meta、Flow、Phrasingのどれでもある。内容モデルは複雑で、src属性が指定されているときは非置換要素になる。
script要素は複雑な経緯を持っているため、srcで外部リソースを読み込んで使うようにする。
src属性で外部リソースを読み込む際、asyncやdeferを用いることで読み込みの遅延を実現できる。defer属性を指定すると、htmlの解析を優先してスクリプトのロードを遅延させる。
noscript
属性、ARIA
ARIA(Accesible Rich Internet Applications)属性は、アクテシビリティを向上させるための属性で、任意のマークアップ言語と組み合わせることができる。
ARIA属性は、その特徴から3つに分類できる:- ロール
- ステート
- プロパティ
代表的なaria-*属性
aria-hidden[state]
支援技術に存在しないことを伝える属性。意図しない、ないしは冗長な読み上げをスキップさせられる。
aria-label[property]
要素にアクセシブルな名前を与える。例えば、アイコンボタンなどテキストから文脈を伝えづらいケースでは与えた名前によってアクションが明瞭に伝わる。
aria-labelledby[property]
aria-haspopup
属性が指定された要素にポップアップがあることを伝える。視覚的にしかわからないドロップダウンの存在を提示できる。
「ついやってしまう」体験のつくりかたを読み返した
何年か前に「ああ、いい本だなあ」と思ってから自分も体験の作り方にコミットするようになったので読み返してみた。初めて読んだときほどの衝撃はなかったけど、やっぱりそうだよねと気付きもあったので言語化しておく。
書籍はこちら: 「ついやってしまう」体験のつくりかた 人を動かす「直感・驚き・物語」のしくみ
なお、自分は普段業務システムを相手にしているので、第二章以降「驚きのデザイン」「物語のデザイン」の読んだまとめは割愛。
直感をデザインする、あるいは直感的ということ
第一章はマリオを題材に、ユーザにルールを伝える技法が説明されている。マリオはひと目見てルールが言語化されているわけではないし、小さい子に聞いてみると「つまらなさそう」に思えるそう。それでも、なんとなく操作してみてなんとなく先に進めてなんとなく楽しめる、そんな体験が埋め込まれている。
仮説と検証
ユーザは、マリオが右を向いていて、背景がなんとなく右に向かっているように見えることがわかる。そこで、「右かな?」と思い進んでみる。すると、左に向かってくるクリボーに遭遇し、「右に進めばなにかがある」「このゲームは右に進めばいいんだ」と学習する。このとき、ユーザは
- 仮説
- 検証
- 歓喜
の仮説検証サイクルを経験する。
この仮説検証サイクルに乗ってもらうため「右に向いたマリオ」や左に向く背景画像など仮説のもとになる手がかり(= シグニファイア)を埋め込んでおく。これによりどのようなことができそうのか、仮説が立てられ、また結果をFBすることでマリオでどのようなことができるかを体験できる。
仮説を立て、実行することで検証を行い、期待通り・望ましい結果が得られたら嬉しくなり、またやってみようと思う。このサイクルに乗ることでユーザはゲームを「おもしろい」と感じるようになる。
考察: "直感的に使える"
体験を作るときに、「直感的に!」、とか「直感的じゃないよね」などというけど、「直感的」の解像度を上げるなら、要素ごとに基準が存在するんじゃないだろうか。
- 行動の仮説を立てやすいこと
- 検証できること
- 正しいFBを受けられること
など。プロダクトのUIを作っていて「使いづらい」と感覚的に思うときには、概ねどれかが足りていない。「直感に反する」と言ったり感じたりするときも同じ。
- そもそも、シグニファイアになるボタンやリンク、inputが探せない
- やりたいことがあるのに、どのボタンを押せばいいのか、判別がつかない
- 登録や検索をしたのになにも返事がない
といったUIデザインになってしまっていないだろうか。
画面や箱の情報量が多いから削ったら、今度はどこからなにができるのかわからなくなった(実は3点リーダーの中にありました...とか)、はあるあるなんじゃないだろうか...
ゲームデザインにおける直感のデザインは、UIデザインにおけるインタラクションと全く同じ性質を持っている。少し前になんか読んだことあったような、と思ったらマイクロインタラクションにあった「トリガー」「ループ」「フィードバック」と全く同じことが書かれているなと思ったのだった。
ただ、ゲームデザインと異なり、プロダクトのUIは操作していて驚くようなこと、禁忌やタブーに触れるようなものがあってはいけない。業務システムは、特定のシーンで特定の業務を遂行することを念頭に設計されるので、驚きや謎解きはなく、できるだけ早く、かつ簡単に処理できるようなデザインにしたい。言い換えると高いユーザビリティを提供できるものにしたい。
おまけ: ユーザビリティについて
ユーザビリティの定義、高め方はISO 9241に規格として存在しているのでぜひ参考したい。たとえば人間中心設計推進機構が出している、入門向け資料がわかりやすい。
人間中心設計の網羅的な説明はJIS規格が参考になる。 kikakurui.com
(サーバサイドエンジニアのための)モダンフロントエンド入門
現代のフロントエンドは、フロントエンドから距離のある人にとっては混乱に満ち満ちた近寄りがたい存在に映ります。かくいう自分がそうでした。サーバサイドのコードはそれなりに書けたし、アーキテクチャパターンや関数型プログラミングなどのトピックにキャッチアップして習熟できてもいました。
ではフロントエンドはどうかというと… 苦手意識が強かった。フロントエンドなのになんでnode.js要るのかよくわからないし、双方向データバインディング?フレームワークはどれがいいの… など、容易に混乱をきたします。
この記事は転職を機にフロントエンドに入門するまでの学習記録を書いたものです。
入門!これだけ読んどけ!的な、教科書というよりも個人の学習記録ではありますが、なんとか仕事できるようになってきたので一つのサンプルとしてご参考いただけるかと思います。
0. 前提条件
この記事のスタイルについて
この記事は、フロントエンド開発のキャッチアップにあたり入り口となるトピックに広く触れていくスタイルをとります。そのため、一つひとつの説明は薄くなってしまいますが、もしトピックに興味が湧いたら個別に調べてみてもらえたらと思います。
html/cssの知識
ウェブ開発に関わるなら、html/cssからはやはり逃れられません…
こういうフレームワークがあるとか、こういう勉強するといい、といった話の前提にはhtml/cssが簡単にでも読み書きできることがあります。
1. 現代フロントエンドに入門しよう。でもその前に…
サーバサイドの開発と比較すると、フロントエンドは技術的な要素以外にUIデザイン、ウェブデザインといったウェブ制作の知識も持っていたほうがキャッチアップが速いです。
ウェブUIにありがちなボタンやナビゲーション、ヘッダやフッタといった基礎知識を抑えておくと何を書いていくべきなのか、どういった役割なのかの理解が早くなります。
UIデザインを学ぶ
UIデザインを学ぶといってもいきなりきれいなUIを作れるようになる必要もないし、またすぐになるのは(もちろん)難しいです。あくまでフロントエンド開発ができるようになることを到達地点とするので、開発をすすめるのに持っておきたい知識の習得を狙って進めます。
幸い、入門レベルに良書がたくさんあります。いくつか選んで一通り読めばそれで十分です。
書籍
例えばサーバサイドのコードを書くとき、すべてをmainに打ち込むことはせず、だいたいこういうコードはControllerだなとか、ドメイン固有型にしようかなとか、そういったお作法というか典型パターンがあります。デザインにおけるそれを短く、例付きで説明してくれていて、まずはこれを読んだほうがいいです。すべてのデザインは原則に則っていますし、実務でUIを作る際に納得感・違和感を持ちながら(自分で考えながら)実装にあたれる、その入口となります。
有名な書籍ですし、こちらはおすすめしたいです。
無料でダウンロードできて本当にありがたかった一冊でした。こちらも、ウェブUI頻出のパターンを短く説明してくれ、普段触っていたウェブサービスの骨格がちょっとだけ理解できます。
腕試し程度に読んでから、理解度や興味に応じて他の書籍をあたるのも良いです。
「はじめのUIデザイン」だけでも十分ではありますが、こちらはさらにしっかり説明がついています。いきなりこちらから読んでも困難なく読めると思います。デバイスの物理的な制約やインタラクションなど、自ら開発者としてUIを作るときに知っておくとよい知識までトピックが網羅されていました。
マイクロインタラクション ―UI/UXデザインの神が宿る細部
タイトルのとおり、大局的なデザイン指針の話ではなくトリガーやユーザへのフィードバックといった、タイトルの通り”マイクロなインタラクション”を説明した書籍です。
体験の良さは、全体の指針と同じくらいインタラクションの積み重ねでも決まるといった趣旨のもので、少しフロントエンドに慣れてからのほうが気づきがあるかもしれません。
バリデーションやモーダル、アラートといった入力や操作に対する一般的なインタラクションをさらっておくのに有用です。
2. 現代フロントエンドに足を踏み入れる
それではいよいよ、現代フロントエンドに触れていきたいと思います。
JavaScript、html/cssの基礎
Vue.jsなどのUIフレームワークは、UIを構築するための強力なフレームワークですが、フレームワークを使う場合下地になる技術には大なり小なり理解があることが望ましいです。このへんはサーバサイドでSpringだけ理解しようとしても限界がある、という話に似ています。
まずはJavaScriptの進化からです。
現代のJavaScript
古来からあるJavaScriptの問題点に関する解説はどこかで目にしたことがあるかもしれません。問題点はあれど、JavaScriptは見違えるほどに成長しています。評判の悪かったプロトタイプベースオブジェクト指向からクラスベースのシンタックスに、そしてコールバック地獄への回答としてasync/await。
特に、JavaScriptの共通規格であるECMAScriptのエディションである es6
から、他のモダンな言語仕様にも近いシンタックスが多数追加されました。主要なものには
- let, constによる変数宣言
- アロー関数
- for/of ループ
- Promise
Promiseによる非同期ラップ、アロー関数などは処理を簡潔・簡便にするのに非常によく使うものですし、1度はチェックしておきたいです。
当時入った機能をおさらいするのに便利なリポジトリです。
GitHub - lukehoban/es6features: Overview of ECMAScript 6 features
TypeScript
感度の高い方であれば、フロントエンド開発に関わらずとも動向をチェックされているかもしれませんが、改めて触れておきます。
TypeScriptはJavaScriptのスーパーセットで、AltJSと呼ばれる言語でもあります。変数やメソッドシグネチャにおいて型の宣言を行うことができ、JavaScriptで大規模な開発を行う際に生じやすい課題への解決策としてとても注目されている言語です。トランスパイルするとJavaScriptになるのでJavaScriptで作ったツールともしっかり互換性があります。
ReactやVue.jsはTypeScriptで書き、jsにトランスパイルしてバンドルを吐くといったツールチェーンが整備されており、できるならはじめからTypeScriptで実装したいですね。
npm(node.js)に親しんでおく
Vue.jsやReactといったUIフレームワークはもちろん、ユーティリティライブラリ、開発支援ツール、そしてTypeScriptといったありとあらゆるパッケージが https://www.npmjs.com/ で公開されています。開発環境を構築する際やテストを実施する際にnpmを用いることになるので、npmには親しんでおきたいです。
とはいえ、パッケージマネージャであることに変わりはないのでGradleやComposer、gemといったサーバサイドでもお馴染みのそれと使用感に大きな差はなく、馴染みやすいものであります。
html/cssの性質を理解する
html/cssとしてレンダリングが処理されるのでhtml/cssからは逃れられません。htmlもcssも、仕様書原文を読みに行かずとも、ふんわりでも振る舞いを理解しておきたいです。
もし、html/cssのシンタックス、プロパティや画面の基本的な作り方に不安があるなら、より基礎的な書籍でのキャッチアップも有効です。
たとえば、こちらの書籍は初歩的な事項からよくまとまっており読みやすかった記憶があります。
1冊ですべて身につくHTML & CSSとWebデザイン入門講座
cssにもいくつかの設計手法があり、シンタックスに加えてどのようにスタイルを構造化して管理するのか学ぶことで、実務で不安が減ります。cssの設計では、例えば以下のような書籍が参考になります。少し古めですが、今も有効な概念・テクニックが多いです。
Web制作者のためのCSS設計の教科書 モダンWeb開発に欠かせない「修正しやすいCSS」の設計手法
動くものを作ろう
テーマを決めてなにかを作ってみることはサーバサイドもフロントエンドも定番の学習方法です。
作るテーマを決めるのに少し苦労するかもしれませんが、最初は「ログインできるメモ帳」や「TODO管理アプリ」といった、よくあるテーマを選ぶとよいと思います。
そういったアプリは定番の振る舞い(CRUD)を網羅でき、かつ作っていくUIコンポーネントはボタンやカード、文字インプットなど定番のものをたくさん書いていけるので初期の学習に向いていました。
フレームワークの使い方すら知らない状態でなにかを作るのは困難なので、書籍やコンテンツに頼りたいところです。サーバサイドと比べると、決定的・確実な情報源が少ない印象で、最新の書籍を見つけるのが難しいことがあります(Vue.js/Nuxt.jsはそうでした)。そのため、udemyを活用すると良いです。入門向けコンテンツは一式揃っているし、フレームワークだけでなくFirebaseも使ってくれたりするので最新(?)の情報に一通り触れることができます。
Online Courses - Learn Anything, On Your Schedule | Udemy
フロントエンド開発の歴史、経緯も知っておく
ふとした瞬間に、Vue.jsは使えるようなってきたけど、なぜ今これが流行っているのか、疑問に思うこともあるかもしれません。
Vue.jsやReactは多機能かつ高性能なUIフレームワークですが、少し前まではAngularやjQuery(まだ全然使われているか…)などとシェアを分け合ってました。
技術の進化が早く、言い換えるとそれだけ非常に多くの試行錯誤が繰り返されてもいる領域なので、どのような思考・試行過程を経て現在に至るのか、周辺の知識なども理解できると良いです。
フレームワークを使う上で有用な概念をいくつか取り上げます。
命令的UI、宣言的UI
命令的UIと宣言的UIという考え方があります。命令的UIは操作の手順を明示的に指示するようなUIです。例えば、jQueryのようなライブラリでDOMを直接操作する場合が該当します。
例えば、jQueryを使ってToDoリストを作ろうとするとこのようになります:
$(function() { var $newTodoInput = $('#new-todo'); var $todoList = $('#todo-list'); $('form').on('submit', function(event) { event.preventDefault(); var $newTodo = $('<li>').text($newTodoInput.val()); var $deleteButton = $('<button>').text('Delete'); $newTodo.append($deleteButton); $todoList.append($newTodo); $newTodoInput.val(''); }); $todoList.on('click', 'button', function() { $(this).parent().remove(); }); });
htmlのDOM要素を手続き的に記述しています。ここでは省いてしまっていますが、jsからhtmlを触ってDOMを構成することになります。
一方、宣言的UIはUIの状態を宣言することで、内部的にDOM操作を行ってくれます。Vue.jsやReact.jsが代表的な宣言的UIのフレームワークです。同じようにVue.jsのサンプルコードも示してみます:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Vue ToDo List</title> </head> <body> <div id="app"> <h1>Vue ToDo List</h1> <todo-form v-on:add-todo="addTodo"></todo-form> <todo-list v-bind:todos="todos" v-on:delete-todo="deleteTodo"></todo-list> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script> <script src="app.js"></script> </body> </html>
todo-form
コンポーネントは、ここでは記述を割愛してしまっていますが、ここで述べたいのはjQueryを使った場合と比較してhtmlの構造が「宣言的」の記述されている、という点です。
はじめ、私はどちらでもいいんじゃないの?とかjQueryどこにいった?などとと思っていましたが宣言的UI(もう少し言えば宣言型プログラミング)は明確に見逃せないメリットがあります:
- 手続きから生成されるDOMの構造をひと目に把握しづらい = 変更容易ではない
- 手続きにより変数やDOMが更新されてほしくない = イミュータブルであってほしい
- ビューとロジックをコンポーネントとして切り出せ、大規模なウェブアプリにも堪えうるアーキテクチャにできる
htmlのタグにclassを仕込んだりイベントハンドラを仕込んだりするわけですが、宣言的な記述のほうが明らかにロジック・ビューのメンテナンスが容易であることが想像できます(見た目通りに動くので)。
また、現代のウェブアプリケーションに求められる機能が多機能高品質化しています。サーバサイドで仕事しているとクラスやパッケージを分割して責務を分けたくなりますが、フロントエンドでもそのような分割・責務凝集性向上がほしくなります。宣言的UIはその性質上パーツにわけてビューとロジックをカプセル化しやすい or できるようになっているため、アプリケーションの規模が大きくなってもスケールさせやすいです。しっかりプロダクトをチームで作っていくようなケースにおいては、もうなくてはならない概念・アーキテクチャと言えます。
経緯も踏まえたモダンフロントエンドについては、いくつか優れた記事などありますのでぜひこちらもご参照ください。
なぜ仮想DOMという概念が俺達の魂を震えさせるのか - Qiita
[FECF2019] 歴史から学ぶ現代のフロントエンド #fec_fukuoka #fec_fukuoka_b
コンポーネント指向
Vue.jsはコンポーネント指向のUIフレームワークです。公式ドキュメントの「はじめに」を読むと、次のようにあります。
標準的な HTML、CSS、JavaScript を土台とする、コンポーネントベースの宣言的なプログラミングモデルを提供します。
またVue.jsをベースにしたNuxt.jsのドキュメントには、コンポーネントは再利用可能なパーツであると述べられています。
Most components are reusable pieces of the user interface, like buttons and menus.
コンポーネントには、ボタンやリストなどの小さなUIパーツから、フォームやメニューバーなどの大きなUIパーツまで、あらゆるUI要素を含むことができます。
ボタンやリンク、ナビゲーションといったどの画面でもよく使うパーツを振るまいとともにコンポーネント化できるため再利用性が高まり、アプリケーションが大きくなってもメンテナビリティを維持しやすくなります。
Atomic Designの考え方
Atomic Designとは、UIを構成する最小単位である「原子」から始め、それらを組み合わせて「分子」、「有機体」、「ページ」などの階層的な構造を作り上げるデザインシステムの考え方です。
こちらはUIデザインの手法の一つです。
フロントエンド開発においても、Atomic Designの考え方を取り入れることで、コンポーネントベースの開発がしやすくなります。
さきほど、Vue.jsはコンポーネント指向のUIフレームワークだと述べました。画面をコンポーネントに分解する際、どのような粒度で分解するかを検討することになりますが、その際Atomic Designの考え方を知っていると(1観点だけど)判断しやすくなります。コンポーネントのフレームワークを使う場合Atomic Designを採用するとはかぎらないが、知っていると役に立つことが多いと思います。
”atomic design” で調べると「やめた」などのサジェストが出てくるように、実装にそのまま持ち込むと辛さが出てくるようですが、全く無意味ということもないです。
より詳しい階層ごとの説明は、以下のような解説記事に譲ります。
アトミックデザインとは?5つのコンポーネントから作るUIデザイン手法
3. おわりに
フロントエンドの開発には、必要な前提知識が多いですよね。私自身、チームの会話についていったり実務にあたったりするのに苦労したので、学習の範囲に絶望するかもしれません。
物事が複雑なことには変わりはないものの、やはりhtml/css/JavaScriptに収斂するので、困ったらhtml/css/JavaScriptの方面から捉え直してみると、困りごとが減ったりデバッグが捗るかもしれません。
本記事が、これからフロントエンドに入門しようとする方の学習パスとしてご参考いただければ幸いです。
TypeScript: デコレータでAOPのようなロギング。
手元にずっととってあったメモ。そんなに変なことを書いていなかったのでブログとして公開してみる。
デコレータとは
A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
TypeScriptのDecoratorについて - 公式ドキュメント日本語訳
クラスの宣言やメソッド、アクセサ、プロパティ、パラメーター に付与できる特別な宣言。見た目、機能、付与できる部分などがJava/Kotlinのアノテーションに似ている。
デコレータの使いみち
ビジネスロジックをもつクラス、メソッドのうち、ロギング・キャッシュなどの機能を剥がすことでクラスやメソッドの責務をより明確にさせることができる(関心事の分離)。トレースログのような、各レイヤごとに共通でやりたいことをアスペクトで切ってまとまった処理にすることができる。
自分がJVMを母語とするプログラマで、Springの大規模フレームワークに体が慣れているから、そういった機能を欲するというのもあると思う。
共通ロギング
使いみちのうち、最もポピュラー?なロギングをデコレータによって実現する。
前提条件として、デコレータといいつつもメソッドの付与してメソッドのメタデータを動的に取得する、リフレクションとも組み合わせて実現することになる。
デコレータになるメソッドのシグネチャ:
const trace = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
}
descriptor.value
にアクセスすることで、デコレータが付与されているメソッドを直接参照できる。この性質を利用して、一度 descriptor.value
を変数にとっておき、その後上書きしながら実行する関数に処理を動的に挟み込むようにする。
参考
デコレータについて