ブラウザのレンダリングの仕組み
「ブラウザを立ち上げてアドレスバーにURLを打ち込んでEnter押してからページが表示されるまでに (裏側で) 何が起こっているかわかる限り説明してみてください。」
っていう問題を先輩から出題されたのですが、上手く答えられず...
ブラウザのレンダリングについてWebフロントエンド ハイパフォーマンス チューニングを読んで勉強したのでそのmemoです。
1. レンダリングエンジン
ブラウザ | レンダリングエンジン | JavaScriptエンジン |
---|---|---|
Google Chrome | Blink | V8 |
IE | Trident | Chakra |
Microsoft Edge | EdgeHTML | Chakra |
Firefox | Gecko | SpiderMonkey |
Safari | Webkit | Nitro(JavaScriptCore) |
Blink,WebKit,Geckoはオープンソースのレンダリングエンジン。 BlinkはWebKitをフォークして誕生。
ブラウザ内のコンポーネント
いくつかのソフトウェアコンポーネントによって構成されている。
知っておきたい2つの重要なコンポーネントは以下。
- レンダリングエンジン
- JavaScriptエンジン
レンダリングエンジンとは?
- HTMLの描画エンジン。
- Webページのレンダリングのみを担当。
- HTMLや画像ファイルやCSS、JavaScriptなどを読み取り画面上の実際のピクセルとして描画する。
JavaScriptエンジンとは?
- JavaScriptの実行環境を提供するソフトウェアコンポーネント。
- DOMツリー、CSSOMツリーなど内部のオブジェクト、APIに対してJavaScriptからアクセスできるようにバインディングを提供。
- ブラウザの拡張機能を実行するために利用される
CSSOMとは?
CSSOMは、CSS Object Modelを意味する略語であり、ブラウザでロードされたCSSのツリー構造を保持する仕組み。ブラウザは、このCSSのツリー構造をDOMに対して適用することで、上位のスタイルからより具体的な下位のスタイルへと連鎖的にスタイルを決定していく。
2. ブラウザのレンダリングの流れ
レンダリングの大まかな流れ
Loading(リソース読み込み)
↓
Scripting(JavaScript実行)
↓
Rendering(レイアウトツリー構築)
↓
Painting(レンダリング結果の描画)
4つの工程からレンダリングが始まって、最終的に描画されるまでをフレーム(Frame)と呼ぶ。
3. リソース読み込み
まず行われるのがリソース読み込み
ブラウザは、与えられたURLからHTMLを読み込んで、そこからさらにレンダリングに必要な付属するリソースを読み込んで解釈する このフェーズでは、次の2つの処理がある。
- リソースのダウンロード
- HTMLを含むリソースをサーバーからダウンロードする。
- リソースのパース
リソース
- HTMLファイル
- CSSファイル
- JS画像ファイル
3.1 リソース取得に用いるネットワークプロトコル
ブラウザは与えられたURLをもとにレンダリングをもとにレンダリングに必要なリソースを様々なネットワークプロトコルを通じて取得する。
ブラウザでよく利用されるネットワークプロトコルはHTTP
- URLに含まれるホスト名の解決
- HTTPによる取得
IP(Internet Protocol)
ネットワークのノード間のパケットのやり取りを中継するプロトコル。
HTTPでネットワーク越しにリソースを取得する場合、リソースの取得の速さはIPのパケットの届く速度に依存する。 →HTTPでやり取りされるデータパケット内に格納されるから。
パケット データの通信に利用される最小単位のデータ。 通信するデータの入れ物としてやり取りされる
TCP(Transmission Control Protocol)
TCPは、IPに対して以下のような機能を付加する上位プロトコル
- 相手先に確実にデータが届いているかどうか確認
- データの欠損や破損をけんちして再送
- データの送信順を保証する
TLS(Transport Layer Security)
与えられたURLのプロトコル部分がhttpsだった場合TCPとHTTPの間でTLSプロトコルを利用する。 TLSは一般にSSLと呼ばれる。
- クライアントとサーバーの認証機能
- 通信データの暗号化
- データの改ざんの検出
UDP (User Datagram Protocol)
IPに対して機能を付加。 TCPと似ている。 TCPプロトコルは、IPに対してデータの欠損の検知やデータの送信順の保証などにお信頼性のあるデータ通信を提供するがUDPではそのような機能は特に追加しない。
HTTP
DNS(Domain Name System)
TCP接続では、接続を開始するのに相手方のIPアドレスが必要。 ブラウザはURLに含まれているホスト名をIPアドレスに変換した上でHTTPリクエストをサーバーへと送信する。
DNSはホスト名に紐づくIPアドレスを検索するための分散システム。
4. それぞれのリソースの読み込み
ブラウザはホスト名の解決などを経て、HTML,CSS,JacaScriptや画像といったリソースを取得する。
一番最初に読み込まれるリソースはHTMLファイル。
- HTMLファイル内に記述されているリソースの参照があれば、さらにそのリソースを読み込む。
- 取得したリソースは、パースされてブラウザの内部表現に変換される。
HTMLの読み込み
- ブラウザは与えられたウェブページのURLを元にサーバーへHTTPリクエストを送信。 HTTPレスポンスとしてHTMLを取得する。
- 読み込んだHTMLを解釈してドキュメントのDOMツリーを構築する。DOMツリーへの変換過程の中でツリーに含まれる画像やCSSなどのドキュメントに紐づくリソースに取得や読み込みを行う。
- ブラウザは構築したDOMツリーを元にしてRenderingの処理を行う。
DOM(Document Object Model)
HTMLのドキュメントを表現するオブジェクト。 DOMツリーは、レンダリングエンジンが利用する木構造を持つ内部表現。
DOMツリーへの変換の工程
- 字句解析によるトークンのリスト化
- 構文解析による構文木構築
- 構文木内にあるJavaScriptを実行しつつDOMツリーの構築
字句解析
コンパイラーがソースコードを解析し、目的のプログラムを生成する際の処理工程のひとつ。字句解析は、ソースコードに記述された変数や定数などの値を実際の値に展開するまでを担当し、その結果を次の工程である構文解析に引きわたす。
トークン
1つの塊になっている文字列
CSSの読み込み
読み込まれたCSSは、レンダリングエンジンによってパースされてCSSOM(CSS Object Model)ツリーへと変換される。
DOMツリーと違ってCSSOMツリーの深さは可変ではなく、一定ですが、ルールセットが増えれば増えるほどDOMツリー内の要素に適用されるスタイル計算にかかる時間が大きくなる。
5. JavaScript実行 Scripting
リソースを一式読み込んだ後、JacaScript実行(Scripting)へ移行。
レンダリングエンジンは、JavaScriptのコードをJavaScriptエンジンに引き渡して実行させる。
JavaScript実行の流れ
(JavaScriptコード)
↓
字句解析
↓
(トークン列)
↓
↓
(抽象構文木)
↓
↓
(実行可能コード)
↓
実行
※JavaScriptエンジンの実装によって異なる。
JavaScriptの実行は、最初にJavaScriptファイルを読み込んだとき以外にも、DOMイベントが発火し、イベントリスナが起動するときに起こる。
5.1 字句解析と構文解析
JavaScriptエンジンは、与えられたコードを何らかの実行可能な形式に変換(コンパイル)した上でJavaScriptに書かれた処理を実行する。
コンパイルを行うために、JavaScriptのコードを抽象構文木(英: abstract syntax tree、AST)と呼ばれるコンパイル可能な形に変換する。 JavaScriptの場合はJavaScriptオブジェクト(JSON)として表現される。
抽象構文木
構文構造をデータ構造に起こしたもの。JavaScriptの文法に沿った形で表現される木構造のデータ。
抽象構文木
{ "range": [ 0, 10 ], "type": "Program", "body": [ { "range": [ 0, 10 ], "type": "VariableDeclaration", "declarations": [ { "range": [ 4, 9 ], "type": "VariableDeclarator", "id": { "range": [ 4, 5 ], "type": "Identifier", "name": "a" }, "init": { "range": [ 8, 9 ], "type": "Literal", "value": 1, "raw": "1" } } ], "kind": "var" } ], "sourceType": "module" }
5.2 コンパイル
JavaScriptエンジン内部のコンパイラは、先ほど構築した抽象構文木を実行可能な形式にコンパイルする。
スクリプト言語の言語処理系の実装の方法
JavaScriptエンジンで多いのは、JIT(Just In Time)コンパイル型の実装
5.3 実行
実行可能な形式にコンパイルされたJavaScriptのコードは、処理系内部の仮想マシン、もしくはCPUで実行される。
6. レイアウトツリー構築 - Rendering
JavaScriptの実行が終わると、レイアウトツリー構築(Rendering)が行われる。
具体的に以下
- スタイルの計算
- レイアウト
レイアウトツリー
ブラウザで DOM と CSSOM を組み合わせて、ページ上の表示可能なすべての DOM コンテンツと、各ノードのすべての CSSOM スタイル情報を取り込んだもの
6.1 スタイルの計算
DOMツリー内の全てのDOM要素に対して、どのようなCSSプロパティが当たるのか計算する。
CSSルールのマッチング処理
CSSOMツリーからCSSルールセットを走査、DOMツリーからDOM要素を走査。 どんなCSSルールが適用されるのかを計算する。
CSS セレクタのマッチング
body > .container > .button { ... }
- DOM要素のclass属性にbutton
- 親要素のclass属性にcontainer
- 親要素のDOM要素名がbody
レンダリングエンジンはセレクタを右から左に解釈してマッチング処理を行う
どのDOM要素に対してどのCSSルールセットが適合するかレンダリングエンジンには分かるようになる。
CSSルールセット
div.my-button, button { background-color: green; color: white; }
の{}
部分
適用されるCSSプロパティの算出
DOM要素にどのCSSプロパティと値が適用されるのか算出する。
(margin, padding,positionの算出ではない)
詳細度の計算など
<p class="foo"> foo </p> .foo { color: red; } p { color: blue; }
.fooのスタイルが当たることの計算
6.2 レイアウト
DOM要素に当たるCSSプロパティを算出した後、レンダリングエンジンはDOMツリー内のすべてのノードの視覚的なレイアウト情報の計算、レイアウトを行う。
設計図的なもの
レイアウト情報
- 要素の大きさ
- 要素のmargin
- 要素のpadding
- 要素の位置
7. レンダリング結果の描画 -Painting
DOMツリーのレイアウト情報の算出が終わると、レンダリング結果の描画(Painting)。
レンダリングエンジンはユーザーが見ることができる実際のピクセルを描画。
3つの処理が行われている。
- ペイント(Paint)
- ラスタライズ(Rasterize)
- レイヤーの合成(Composite Layers)
最後のレイヤーの合成が終わることで、ユーザーの目にはレンダリングエンジンが描画した表示になる。
7.1 ペイント
内部の低レベルな2Dグラフィックエンジン向けの命令を生成。
- RenderTreeを元にDisplay Listと呼ばれる内部の低レベルグラフィックエンジンのための命令の列を生成。
組み込まれるグラフィックエンジンはブラウザの実装ごとに異なる
7.2 ラスタライズ
- 生成された命令を用いて実際にピクセルに描画する。
- レイヤーごとに一枚一枚描画される。
- レイヤーが生成されるのは、
position
やtransform
,opacity
などのプロパティが適用されているとき
レイヤー単位でピクセルに描画するのはなぜ? 再レンダリングする場合、すでに描画が終わったレイヤーを再利用することで、素早く再レンダリングできる場合がある。
7.3 レイヤーの合成
ピクセルにしたレイヤーを合成して最終的なレンダリング結果を生成する。
CPU
コンピューター全体の計算処理
3Dグラフィックなどの画像描写に必要な計算処理
7.4 コンテンツの表示
レイヤーの合成の処理を終えてやっと
レンダリングエンジンがコンテンツを表示します!
8 再レンダリング
ユーザーやブラウザの何らかのアクションやJavaScriptのコードの実行やドキュメント内のイベントによってレンダリングは再度引き起こされる。
再レンダリングが引き起こされるのは?
- DOMイベントが発火するとき
「ブラウザを立ち上げてアドレスバーにURLを打ち込んでEnter押してからページが表示されるまでに (裏側で) 何が起こっているかわかる限り説明してみてください。」
はじめは、この質問を何も見ずに15分考えると、ぼんやりとした流れを説明することしかできなかったのですが、ブラウザの仕組みを勉強したあとは3倍くらいの量できちんと説明できるようになりました◎
参考
- https://ja.wikipedia.org/wiki/HTML%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3
- https://webukatu.com/wordpress/blog/928
- https://qiita.com/Tsuyoshi84/items/5575aff0408ea7e64e68
- https://kotobank.jp/word/%E5%AD%97%E5%8F%A5%E8%A7%A3%E6%9E%90-4120
- https://kotobank.jp/word/%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3-6633
- https://codingjp.com/web/6407/
- レンダリング ツリーの構築、レイアウト、ペイント | Web | Google Developers
- 作者:久保田 光則
- 発売日: 2017/05/26
- メディア: 単行本(ソフトカバー)
Vue.jsのリアクティブシステムみたいなのを作ってみた。
Vue.jsの中身を読んでみる!というのを最近やってみていて、算出プロパティ部分を読んでいたのですが、 実際に同じようなコードが書けないかなあと思い、少し書いてみました。
Vue.js 算出プロパティ部分のコード
Vue.jsでの実現方法は多分こんな感じ
initData
でobserve
が呼ばれている
vue/state.js at dev · vuejs/vue · GitHub
function initData (vm: Component) { let data = vm.$options.data // ... data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} // observe data observe(data, true /* asRootData */) }
↓
observe
はObserver
オブジェクトを生成
vue/index.js at dev · vuejs/vue · GitHub
export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob }
↓
ObserverクラスでdefineReactive
が呼ばれてる。
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) // ... walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } }
↓
defineReactive
でObject.defineProperty
を使用してリアクティブシステムを実現している...?
vue/index.js at dev · vuejs/vue · GitHub
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } })
自分で同じような仕組み書いてみる
Object.defineProperty
を使用しているみたいなので調べてみました。
Object.defineProperty
あるオブジェクトに新しいプロパティを直接定義したり、オブジェクトの既存のプロパティを変更したりして、そのオブジェクトを返します。
実際に書いてみる
<html> <body> <ul> <li> <p>テキスト</p> <p id="message"></p> <input type="text" id="text"> </li> <li> <p>チェックボックス</p> <input type="checkbox" id="checkbox"> <label for="checkbox" id="label-checkbox"></label> </li> <li> <p>セレクトボックス</p> <select> <option disabled value="">Please select one</option> <option>A</option> <option>B</option> <option>C</option> </select> <p id="selectedText"></p> </li> </ul> </body> </html>[f:id:cidermitaina:20190128100626g:plain]
const data = {}; const p = document.getElementById('message'); const label = document.getElementById('label-checkbox'); const input = document.querySelectorAll('input'); const inputTypeText = document.getElementById('text'); const inputTypeCheckbox = document.getElementById('checkbox'); const select = document.querySelector('select'); const selectedText = document.getElementById('selectedText'); p.textContent = inputTypeText.value = 'Hello, World!'; label.textContent = inputTypeCheckbox.value = 'false'; selectedText.textContent = inputTypeCheckbox.value = 'selected'; // リアクティブプロパティの定義 Object.defineProperties(data, { message: { get() { return message; }, set(newVal) { message = newVal; p.textContent = message; } }, checked: { get() { return checked; }, set(newVal) { checked = newVal; label.textContent = checked; } }, selected: { get() { return selected; }, set(newVal) { selected = newVal; selectedText.textContent = selected; } }, }); input.forEach((el) => { el.addEventListener('input', (ev) => { data.message = ev.target.value; data.checked = ev.target.checked; }); }); select.addEventListener('change', (ev) => { data.selected = ev.target.value; });
以下のようになります。
Vue.jsの中身を読んでみる (算出プロパティ1)
お仕事でVue.jsを書くことが多いのですが、なんとなく中身どうなってるんだろうって思ったので中身を読んでみることにしました。(今年はコードリーディングが目標.です..!)
Vue.jsのコード全て読もうとすると挫折しちゃいそうなので、面白そうなところ、読めそうなところつまみ食いしてみることにしました。
今回は算出プロパティのコードを読んで見ました。
算出プロパティのコードを読む
算出プロパティとは
任意に処理を含めることのできるデータ
テンプレート内にjavascriptが使用できるが、テンプレート内に多くのロジックを詰め込むと、コードが肥大化、メンテナンスが難しくなる。
<div> {{ message.split('').reverse().join('') }} </div>
複雑なロジックには算出プロパティを利用すべき
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div>
var vm = new Vue ({ el: '#example', data: { message: 'Hello' }, computed: { reversedMessage: function () { return this.message.split('').reverse().join('') } } })
console.log(vm.reversedMessage) // =\> 'olleH' vm.message = 'Goodbye' console.log(vm.reversedMessage) // =\> 'eybdooG'
算出プロパティとメソッドの違い
<p>Reversed message: "{{ reverseMessage() }}"</p>
methods: { reverseMessage: function () { return this.message.split('').reverse().join('') } }
↑上記コードで同じ結果になる。
違いは?
算出プロパティは依存関係にもとづきキャッシュされる
算出プロパティは、それが依存するものが更新されたときにだけ再評価されます。
message
が変わらない限り、reversedMessage
が呼び出されてもfunction()
以下の処理は実行されず、前に算出したプロパティの値がそのまま出力される。
message
の値が変われば再び処理が走るが、そうでなければ何回呼び出されても計算が発生しないので、不要に処理が遅くなることがない。
じゃあmethodsっていつ使うの?
computed: { now: function () { return Date.now() } }
Date.now()
は二度と更新されない。
→ いつアクセスしても同じ日時
methodsはメソッドなので、入力値が変わらなくても、呼び出されるたびに毎回処理が走る。
算出プロパティは、それが依存するものが更新されたときにだけ再評価されます。
算出プロパティ付近のコード読んでみる
どうやって実装してるんだろう?
予想を立ててみた
- dataの値が更新されたら再評価される
- なんか
Observer
っぽい気がする。 - Vueの本に
メソッドを呼び出しているわけではないのに何かの処理をしている。これがVue.jsの心臓ともいえるリアクティブシステムです。
みたいなこと書いてた気がする
- なんか
Intersection Observer API - Web API | MDN
やってみたこと
vueのコード読んでみる。
↓
observerディレクトリがある...!
vue/src/core/observer at dev · vuejs/vue · GitHub
↓
index.js見てみる
vue/index.js at dev · vuejs/vue · GitHub
↓
defineReactive関数
見つけた。
↓
Vueの公式に Object.definePropertyを使用して getter/setter に変換します
って書いてた気がする
↓
Object.defineProperty
getter/setter
ある!
↓
Object.defineProperty
?
あるオブジェクトのプロパティを明示的に追加または変更することができます。
Object.defineProperty() - JavaScript | MDN
↓
defineReactive関数
読んでみる
↓
const dep = new Dep()
?
const dep = new Dep() // ... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, // ...
↓
dep.js読んでみる?
vue/dep.js at dev · vuejs/vue · GitHub
↓
続きます...!
Hyperappについて調べてみた。
毎週何かのライブラリをコードリーディングするという目標を立てました◎
はじめてのコードリーディング、何にしようかと迷ったのですが🤔
Hyperappを読んでみることにしました。
Hyperappなら1KBだし、行数も400行くらい。
(コードリーディング初心者の私でも読めるかも...🤔!)
今回の記事はコードリーディング前に事前にHyperappについて調べたのでそのmemoです。
Hyperappとは?
Web アプリのフロントエンド用 JavaScript ライブラリ。React, Preact, Vue といった代表的なものよりもずっと小さく、1 KB という超軽量サイズ。他のライブラリに依存することなく使えて、さらにスピードもある
- QiitaがReactからHyperappに移行
- SHOWROOMの新規開発に採用されている
ReactやVueなどで使われているVirtual DOM
,Lifecycle Events
の概念とRedux や Vuexなどで使われているState Management(状態管理)
の概念を、「最小限」で、「実用的」に、一つにまとめた(「独立している」)ライブラリ。
concept
- Minimal(最小限)
- Pragmatic(実践的)
- Standalone(独立)
Hyperappの関数
h関数
, app関数
の2つだけです。
h関数
:仮想DOMを生成する関数app関数
: Hyperappを利用したApplicationを実行する関数
h
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)
コードリーディングはまだ途中なので、改めて記事にする予定。
Chrome DeveloperToolのconsoleの使い方
Developer Toolのconsoleを上手く使いこなせていなかったのでmemoです。
console.log()
しか使ったことない人とかいませんか?
consoleパネルで使える Command Line API を覚えるとデバックが捗るので よく使うものをまとめてみました。
コマンドライン API とは?
Developer ToolのConsole 内で使える便利関数のことです。
$_
$_
には最後に評価した式の結果が保存されています。
$0 〜 $4
$0
から$4
にはElementsパネルで選択した要素が5つ保存される
$() と $$()
$(selector)
は、指定された CSS セレクターを含む最初の DOM 要素への参照を返します。この関数は、document.querySelector() 関数のエイリアスです。
$$(selector)
は、指定された CSS セレクターに一致す
dir(object)
dir(object) は、指定されたオブジェクトのすべてのプロパティをオブジェクト スタイルのリストで表示します。このメソッドは、コンソール API の console.dir() メソッドのエイリアスです。
inspect()
inspect(object/function)
は、指定された要素またはオブジェクトを適切なパネルで開いて選択します。
getEventListeners()
getEventListeners(object)
は、指定されたオブジェクトに登録されているイベント リスナーを返します。戻り値は、各登録済みタイプ("click"、"keydown" など)の配列が含まれているオブジェクトです。各配列のメンバーは、各タイプに登録されているリスナーを記述するオブジェクトです。
monitorEvents() & unmonitorEvents()
monitorEvents()
は要素とイベントを指定することで、その要素に対するイベントの発生をモニタリングすることができます。
ChromeのDeveloper Toolを使ったデバック方法についてもう少しいろいろ書きたいな...
参考
Dockerで作ったWebサーバでページを表示してみる。
Dockerの勉強を始めました。
サーバー構築してApacheとかnginxのデフォルトページを表示するっていう記事が多くて、実際に作ったページをDockerで表示するにはどうするんだろう?と思って調べたことをまとめました。
こんな感じのページをDockerで作ったWebサーバで表示させます。
Dockerのインストール方法などは省略しています。。。
Dockerって?
まずはDockerとは?っていうところを簡単にまとめます。
Dockerとは?
アプリ開発・移動・実行プラットフォーム
開発者が簡単にアプリケーションを動かす環境を作成します。
Dockerを使えば、OSとアプリがインストールされた環境を閉じ込めて保存しておき、いつでもその環境を立ちあげることができます。
例えば、
ApacheやnginxをインストールしたCentOSのような環境を簡単に作成して保存・立ち上げが可能です。
メリット・デメリット
メリット
- OS依存がなく、導入が容易
- 案件ごとに異なる環境を構築できるため、特定のPC依存を回避
- ミドルウェア導入や新インフラ環境のテストが各自のPCで可能
デメリット
- 学習コスト
DockerfileからDocker imageを作成
実際にやってみようと思います。
1. Dockerfileの作成
まずDockerfileを作成します。
Dockerfileとは?
ベースとするDockerイメージに対して実行する内容を記述するファイルです。
Dockerイメージ
OSとアプリがインストールされた環境を閉じ込めたファイル
Dockerfileを作成します。
$ vi Dockerfile
以下の記述を記入します。
# setting base image FROM centos:centos7 # Author MAINTAINER cidermitaina # install Apache http server RUN ["yum", "-y", "install", "httpd"] # start httpd CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
- FROM: ベースとするDockerイメージを指定
- MAINTAINER: 作成者情報を設定
- RUN: 対象のイメージにインストールされているコマンドを実行
- CMD: コンテナ起動時に実行するコマンド
2. Dockerfileのビルド
docker buildコマンドでDockerfileをビルドして、dockerイメージを作成します。
$ docker build -t sample:ver01 . Sending build context to Docker daemon 2.048kB Step 1/4 : FROM centos:centos7 ---> 5182e96772bf Step 2/4 : MAINTAINER cidermitaina ---> Using cache ---> d5a98e9e0925 Step 3/4 : RUN ["yum", "-y", "install", "httpd"] ---> Using cache ---> 47f3833db651 Step 4/4 : CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"] ---> Using cache ---> 898e9185951b Successfully built 898e9185951b Successfully tagged sample:ver01 SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
- -tオプションで「イメージ名:タグ名」を指定
- 最後にDockerfileの場所を指定(カレントディレクトリなので . )
docker image ls
でdockerイメージが作成されているか確認できます。
docker runコマンドでビルドしたイメージを起動してみます。
$ docker run -d -p 80:80 sample:ver01 a5f0a38da63d783572df5557f51351694f8ee957598a05ea68d7c1de9962fb56
http://192.168.99.100/にアクセスするとApacheのデフォルト画面が表示されるはずです。
実際にページを作成してみる
1. ベースイメージの作成
webシステムを構築するときOSのインストール/環境設定などベースイメージとして作成します。
Dockerfile.baseていう名前でベースイメージを作成します。
# setting base image FROM centos:centos7 # Author MAINTAINER cidermitaina # install Apache http server RUN ["yum", "-y", "install", "httpd"] # deploy ONBUILD ADD web.tar /var/www/html/ # start httpd CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
- ONBUILD: 次のビルドで実行するコマンドをイメージ内に設定する命令
- ADD: web.tarファイルを/var/www/html/配下に配置
ONBUILD命令でイメージの中に開発したプログラムをデプロイする命令を指定しています。
2.web.tarファイルの作成
こんな感じで作成していきます。
web.tar ├─index.html └─public/ └─css/ └─index.css
適当に作成したサンプルです。。。
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Page Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="./public/css/index.css" rel="stylesheet"> </head> <body> <header class="header"> <div class="header__icon-area"> <span class="icon"></span><h1 class="name">your name</h1> </div> </header> <main class="main"> <p class="main__txt"> Hey I'm <span class="shadow shadow--pk">your name</span> </p> <p class="main__txt"> I am <span class="shadow shadow--bl">xxxxx years old</span> born in <span class="shadow shadow--ye">xxxxx</span>.<br> I'm a <span class="shadow shadow--gr">frontend engineer</span>.<br> Because I work hard, I want you to support me... </p> <p class="main__txt"> Find me on <a href="" target="_blank" class="shadow shadow--bl">Twitter</a> and <a href="" target="_blank" class="shadow shadow--pu">GitHub</a>. </p> </main> </body> </html>
index.css
body { color: #4a4a4a; font-size: 1rem; font-weight: 400; line-height: 1.5; font-family: BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif; } .header{ width: 80%; margin: 2.5rem auto; } @media screen and (max-width: 640px) { .header { width: 90%; } } .header__icon-area{ display: flex; align-items: center; } .icon{ width: 50px; height: 50px; display: inline-block; background-color: paleturquoise; border-radius: 5px; } .name{ padding-left: 10px; font-weight: 600; letter-spacing: 1.5px; font-size: 1rem } .main{ font-size: 1.5rem; text-align: center; margin-top: 6rem; } @media screen and (max-width: 640px) { .main { font-size: 1.3rem; } } .main__txt a{ color: #4a4a4a; text-decoration: none; } .shadow--pk{ -webkit-box-shadow: inset 0 -6px 0 rgba(255,74,110,.2); box-shadow: inset 0 -6px 0 rgba(255,74,110,.2); } .shadow--bl{ -webkit-box-shadow: inset 0 -6px 0 rgba(36,177,209,.2); box-shadow: inset 0 -6px 0 rgba(36,177,209,.2); } .shadow--gr{ -webkit-box-shadow: inset 0 -6px 0 rgba(35,209,153,.2); box-shadow: inset 0 -6px 0 rgba(35,209,153,.2); } .shadow--ye{ -webkit-box-shadow: inset 0 -6px 0 rgba(255,221,87,.2); box-shadow: inset 0 -6px 0 rgba(255,221,87,.2); } .shadow--pu{ -webkit-box-shadow: inset 0 -6px 0 rgba(184,107,255,.2); box-shadow: inset 0 -6px 0 rgba(184,107,255,.2); }
3.Dockerfile.baseのビルド
web.tarをDockerfile.baseと同じ階層に置きます。
docker ├─web.tar └─Dockerfile.base
Dockerfile.baseをビルドします。
docker build -t httpd_base -f Dockerfile.base .
※Dockerfileというファイル名の場合は明示する必要はありませんが、 この場合はDockerfile.baseという名前なので明示的に指定する必要があります。
docker imagesコマンドで確認してみます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd_base latest 63d226978f2e 10 seconds ago 326MB
4.Dockerfileの作成とビルド
次は作成したhttpd_baseをベースイメージとしたDockerfileをweb.tarをDockerfile.baseと同じ階層に作成します。
vi Dockerfile
docker ├─web.tar ├─Dockerfile └─Dockerfile.base
Dockerfile
# base image
FROM httpd_base
FROMでhttpd_baseを指定するだけに記述です。
作成したDockerfileをhttpd_webというイメージ名でビルドします。
docker build -t httpd_web .
docker imagesで確認してみると、httpd_webというイメージが作成されているはずです。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE httpd_web latest e0f30741ca8e 5 minutes ago 326MB httpd_base latest 63d226978f2e 10 seconds ago 326MB
5.httpd_webの起動
docker runコマンドでhttpd_webを起動してみます。
$ docker run -d -p 81:80 httpd_web 82925172b0479a00fc76f8ebe5232fe0bba1c35766abc05acd560f0297276622
※80番のポートはデフォルトのapacheページで使っているので81番にしています。
http://192.168.99.100:81/ にアクセスすると
が表示されると完成です◎
参考にした本と記事です。
プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化
- 作者:WINGSプロジェクト阿佐志保
- 発売日: 2015/11/19
- メディア: Kindle版
続きの記事としてAWS ECSでさっきのページを公開してみる記事も書きたいなあ、って思ってます。。。
AMP JSで画像遅延してみました。
このあいだお仕事で表示速度をあげたいって話がでてきたのでいろいろ調べていたのですが、 その際、AMP化の話がでたので気になって調べてみました。 さくっと試してみたかったのでAMP JSで画像遅延をやってみたのでそのメモです。
htmlをAMP化する方法ではなくて、AMPって?、AMP JSを使ってみる、辺りについて書いています。
AMP(Accelerated Mobile Pages)とは?
- Googleが中心となって立ち上げた、モバイルデバイスでのWebブラウジングを高速化することを目的としたオープンソースプロジェクト
- AMPで策定された仕様に沿ってモバイルページを構築すれば、通常の約4倍の速さでページが表示され、データ量が約1/10になると言われている
- 3,100 万のドメインが AMP ドキュメントを発行
- 検索結果の上位表示がされる可能性が高くなる
AMPは使用できるHTMLを制限するとともに、JavaScriptによる動的な表示を制限することで表示を高速化する技術で、次の3つの要素から構成される。
- 表示の高速化のためにHTMLに制約を加えた「AMP HTML」
- 高速にコンテンツのロードを行うためのJavaScriptコード「AMP JS」
- コンテンツのキャッシュ配信を行う「Google AMP Cache」
AMP HTML
安定したパフォーマンスを実現するための制限が設定された HTML
<!doctype html> <html amp lang="en"> <head> <meta charset="utf-8"> <title>Hello, AMPs</title> <link rel="canonical" href="http://example.ampproject.org/article-metadata.html"> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <script type="application/ld+json"> { "@context": "http://schema.org", "@type": "NewsArticle", "headline": "Open-source framework for publishing content", "datePublished": "2015-10-07T12:02:41Z", "image": [ "logo.jpg" ] } </script> <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript> <script async src="https://cdn.ampproject.org/v0.js"></script> </head> <body> <h1>Welcome to the mobile web</h1> <amp-img src="welcome.jpg" alt="Welcome" height="400" width="800"></amp-img> </body> </html>
AMP HTML ページ内の大半のタグは通常の HTML タグですが、一部の HTML タグは AMP 固有のタグに置き換えられる。
ex) amp-img、amp-video
AMP ページは、検索エンジンや他のプラットフォームで <link rel="">
HTML タグにより検出される。サイトのページについて非 AMP バージョンと AMP バージョンの両方を作成するか、AMP バージョンのみを作成するかを選ぶことができる。
AMP JS
AMP JS ライブラリは、AMP HTML ページの高速なレンダリングを確実に行えるようにするもの
head タグの最後に記述
<script async src="https://cdn.ampproject.org/v0.js"></script>
AMP JS ライブラリでは、AMP の最適なパフォーマンスを実現するための方策をすべて実施し、リソースの読み込みを管理して、上記のカスタムタグを提供することにより、サイトのページの高速レンダリングを確実に行えるようにしている。
AMP Cache
Google AMP Cache は、キャッシュされた AMP HTML ページの配信に利用できる。
AMP HTML ページをフェッチしてキャッシュし、ページのパフォーマンスを自動的に改善する。
Google AMP Cache を使用すると、効率を最大限に高められるように、ドキュメント、すべての JS ファイル、あらゆる画像が、HTTP 2.0 を使用する同一の生成元から読み込まれる。
AMPの仕組み
- Googleにインデックスされると、GoogleはそのAMPページをキャッシュする
- Googleの検索結果などからAMPページにアクセスすると、すでにGoogleによってキャッシュされたWebページのデータが返される仕組みになっているので、通常のWebページよりも高速に表示できる
AMP JSを使ってみる
通常のHTMLファイルをAMPページにするには時間がかかっちゃうのでAMP JSだけ使ってを 表示速度を高速化を試してみました。
(AMPでは表示速度を高速化するためにAMP JSというJavaScriptのライブラリーを読み込み、表示速度の改善を行っている。)
AMP JSは非AMPページでも利用できます。
1. AMP JSをhead内に非同期で読み込む
以下の記述を</head>
の直前に記述します。
<script async src="https://cdn.ampproject.org/v0.js"></script>
2. img要素をamp-img要素に置き換える
img
<img src="img.jpg" alt="" />
amp-img
<amp-img src="img.jpg" width="400" height="300" layout="responsive" alt="" />
width属性とheight属性は必須で指定する必要があります。 layout属性で要素を画面上でどのように表示するか指定します。
これだけで遅延ロードができます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>amp-img</title> <script async src="https://cdn.ampproject.org/v0.js"></script> </head> <body> <main> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> <div><amp-img src="images/photo01.jpg" width="600" height="300" layout="responsive" alt=""></amp-img></div> </main> </body> </html>
今までjquery.lazyload.jsなどで遅延ロードを実装していましたが、AMP JSで遅延ロードを実装してみるのもいいかもです。
(デメリットなどはまだ調べられていないですが、、、)
AMPを調べていてPWAと比較している記事を目にしました。
なんとなく、AMPはニュースサイトのような記事コンテンツが多いページに向いてるのかなあって感じています。
PWAはまだよく分かっていないので時間があるとき調べたい...です。
参考