フロントエンドエンジニアをやっています。
頑張るから読んでほしい。

ブラウザのレンダリングの仕組み

「ブラウザを立ち上げてアドレスバーに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エンジンとは?

CSSOMとは?

CSSOMは、CSS Object Modelを意味する略語であり、ブラウザでロードされたCSSのツリー構造を保持する仕組み。ブラウザは、このCSSのツリー構造をDOMに対して適用することで、上位のスタイルからより具体的な下位のスタイルへと連鎖的にスタイルを決定していく。

2. ブラウザのレンダリングの流れ

レンダリングの大まかな流れ

Loading(リソース読み込み)

Scripting(JavaScript実行)

Rendering(レイアウトツリー構築)

Painting(レンダリング結果の描画)

4つの工程からレンダリングが始まって、最終的に描画されるまでをフレーム(Frame)と呼ぶ。

3. リソース読み込み

まず行われるのがリソース読み込み

ブラウザは、与えられたURLからHTMLを読み込んで、そこからさらにレンダリングに必要な付属するリソースを読み込んで解釈する このフェーズでは、次の2つの処理がある。

  • リソースのダウンロード
    • HTMLを含むリソースをサーバーからダウンロードする。
  • リソースのパース
    • ダウンロードしたリソースをパース(構文解析)してレンダリングエンジンの内部表現に変換する
      • HTMLやCSSは、それぞれDOMツリーやCSSOMツリーの変換される

リソース

  • HTMLファイル
  • CSSファイル
  • JS画像ファイル

3.1 リソース取得に用いるネットワークプロトコル

ブラウザは与えられたURLをもとにレンダリングをもとにレンダリングに必要なリソースを様々なネットワークプロトコルを通じて取得する。

ブラウザでよく利用されるネットワークプロトコルHTTP

  • URLに含まれるホスト名の解決
  • HTTPによる取得
    • TCP接続の確立
    • HTTPSであれば)TLS接続の確率
    • HTTPリクエストの送信と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

  • ウェブページのコンテンツを送受信するために利用されるプロトコル
  • シンプル
  • クライアントはサーバーに対してHTTPリクエストを送信し、サーバーはクライアントに対して- - HTTPレスポンスを返す。

DNS(Domain Name System)

IPアドレスを解決するためのシステム、プロトコル

TCP接続では、接続を開始するのに相手方のIPアドレスが必要。 ブラウザはURLに含まれているホスト名をIPアドレスに変換した上でHTTPリクエストをサーバーへと送信する。

DNSはホスト名に紐づくIPアドレスを検索するための分散システム。

4. それぞれのリソースの読み込み

  • ブラウザはホスト名の解決などを経て、HTML,CSS,JacaScriptや画像といったリソースを取得する。

  • 一番最初に読み込まれるリソースはHTMLファイル。

  • HTMLファイル内に記述されているリソースの参照があれば、さらにそのリソースを読み込む。
  • 取得したリソースは、パースされてブラウザの内部表現に変換される。
    • HTMLコンテンツの場合はDOMツリーに
    • CSSの場合はCSSOMツリーに
    • それ以外にリソースも、レンダリングエンジンの実装に沿った内部表現に変換される。

HTMLの読み込み

  1. ブラウザは与えられたウェブページのURLを元にサーバーへHTTPリクエストを送信。 HTTPレスポンスとしてHTMLを取得する。
  2. 読み込んだHTMLを解釈してドキュメントのDOMツリーを構築する。DOMツリーへの変換過程の中でツリーに含まれる画像やCSSなどのドキュメントに紐づくリソースに取得や読み込みを行う。
  3. ブラウザは構築したDOMツリーを元にしてRenderingの処理を行う。

DOM(Document Object Model

HTMLのドキュメントを表現するオブジェクト。 DOMツリーは、レンダリングエンジンが利用する木構造を持つ内部表現。



f:id:cidermitaina:20190303224436p:plain

DOMツリーへの変換の工程

  1. 字句解析によるトークンのリスト化
  2. 構文解析による構文木構築
  3. 構文木内にあるJavaScriptを実行しつつDOMツリーの構築

字句解析

コンパイラーがソースコードを解析し、目的のプログラムを生成する際の処理工程のひとつ。字句解析は、ソースコードに記述された変数や定数などの値を実際の値に展開するまでを担当し、その結果を次の工程である構文解析に引きわたす。

トーク

1つの塊になっている文字列

CSSの読み込み

読み込まれたCSSは、レンダリングエンジンによってパースされてCSSOM(CSS Object Model)ツリーへと変換される。

f:id:cidermitaina:20190303224508p:plain



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)コンパイル型の実装

JIT(Just In Time)コンパイル

5.3 実行

実行可能な形式にコンパイルされたJavaScriptのコードは、処理系内部の仮想マシン、もしくはCPUで実行される。

6. レイアウトツリー構築 - Rendering

JavaScriptの実行が終わると、レイアウトツリー構築(Rendering)が行われる。

具体的に以下

  • スタイルの計算
  • レイアウト

レイアウトツリー

ブラウザで DOM と CSSOM を組み合わせて、ページ上の表示可能なすべての DOM コンテンツと、各ノードのすべての CSSOM スタイル情報を取り込んだもの

6.1 スタイルの計算

DOMツリー内の全てのDOM要素に対して、どのようなCSSプロパティが当たるのか計算する。

  1. CSSOMツリー内を全て参照して、CSSルールのCSSセレクタマッチング処理が行われる。
  2. CSSルールの詳細度を算出して個別のDOM要素に対して、どのようなCSSプロパティが適用されるか判断

CSSルールのマッチング処理

CSSOMツリーからCSSルールセットを走査、DOMツリーからDOM要素を走査。 どんなCSSルールが適用されるのかを計算する。

CSS セレクタのマッチング

CSSセレクタのマッチング処理は右側から行われる。

body > .container > .button {
    ...
}
  1. DOM要素のclass属性にbutton
  2. 親要素のclass属性にcontainer
  3. 親要素の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と呼ばれる内部の低レベルグラフィックエンジンのための命令の列を生成。
  • 組み込まれるグラフィックエンジンはブラウザの実装ごとに異なる

    • WebKit 異なるグラフィックエンジンを組み込めるように設計されている。
    • Blink  Skiaが使われている 。
    • Safari CoreGraphicsが使われている

7.2 ラスタライズ

  • 生成された命令を用いて実際にピクセルに描画する。
  • レイヤーごとに一枚一枚描画される。
  • レイヤーが生成されるのは、 positiontransform ,opacityなどのプロパティが適用されているとき

レイヤー単位でピクセルに描画するのはなぜ? 再レンダリングする場合、すでに描画が終わったレイヤーを再利用することで、素早く再レンダリングできる場合がある。

7.3 レイヤーの合成

ピクセルにしたレイヤーを合成して最終的なレンダリング結果を生成する。

  • CSS基本的にはCPUによって合成される
  • transformCSSプロパティに3D変形関数を指定するとGPUによって合成される

CPU

コンピューター全体の計算処理

GPU

3Dグラフィックなどの画像描写に必要な計算処理

7.4 コンテンツの表示

レイヤーの合成の処理を終えてやっと

レンダリングエンジンがコンテンツを表示します!

8 再レンダリング

ユーザーやブラウザの何らかのアクションやJavaScriptのコードの実行やドキュメント内のイベントによってレンダリングは再度引き起こされる。

  • 全てのレンダリングの処理が最初からやり直しになるわけではない。
  • 多くのレンダリングエンジンは、これまで描画のために構築した内部表現のオブジェクトをなるべく再利用しようとする

レンダリングが引き起こされるのは?

  • DOMイベントが発火するとき





「ブラウザを立ち上げてアドレスバーにURLを打ち込んでEnter押してからページが表示されるまでに (裏側で) 何が起こっているかわかる限り説明してみてください。」

はじめは、この質問を何も見ずに15分考えると、ぼんやりとした流れを説明することしかできなかったのですが、ブラウザの仕組みを勉強したあとは3倍くらいの量できちんと説明できるようになりました◎





参考

Webフロントエンド ハイパフォーマンス チューニング

Webフロントエンド ハイパフォーマンス チューニング

  • 作者:久保田 光則
  • 発売日: 2017/05/26
  • メディア: 単行本(ソフトカバー)

Vue.jsのリアクティブシステムみたいなのを作ってみた。

Vue.jsの中身を読んでみる!というのを最近やってみていて、算出プロパティ部分を読んでいたのですが、 実際に同じようなコードが書けないかなあと思い、少し書いてみました。

Vue.js 算出プロパティ部分のコード

Vue.jsでの実現方法は多分こんな感じ

initDataobserveが呼ばれている

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 */)
}

observeObserverオブジェクトを生成

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])
    }
  }

defineReactiveObject.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

あるオブジェクトに新しいプロパティを直接定義したり、オブジェクトの既存のプロパティを変更したりして、そのオブジェクトを返します。

developer.mozilla.org

実際に書いてみる

<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;
});

以下のようになります。

f:id:cidermitaina:20190128100626g:plain

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.js

やってみたこと

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 に変換します って書いてた気がする

リアクティブの探求 — Vue.js

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(状態管理)の概念を、「最小限」で、「実用的」に、一つにまとめた(「独立している」)ライブラリ。

github.com



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 内で使える便利関数のことです。

$_

$_には最後に評価した式の結果が保存されています。

f:id:cidermitaina:20181208150101p:plain

$0 〜 $4

$0から$4にはElementsパネルで選択した要素が5つ保存される

f:id:cidermitaina:20181208150913g:plain

$() と $$()

$(selector)は、指定された CSS セレクターを含む最初の DOM 要素への参照を返します。この関数は、document.querySelector() 関数のエイリアスです。

$$(selector) は、指定された CSS セレクターに一致す

f:id:cidermitaina:20181208152035g:plain

dir(object)

dir(object) は、指定されたオブジェクトのすべてのプロパティをオブジェクト スタイルのリストで表示します。このメソッドは、コンソール API の console.dir() メソッドのエイリアスです。

f:id:cidermitaina:20181208160419g:plain

inspect()

inspect(object/function) は、指定された要素またはオブジェクトを適切なパネルで開いて選択します。

f:id:cidermitaina:20181208152532g:plain

getEventListeners()

getEventListeners(object) は、指定されたオブジェクトに登録されているイベント リスナーを返します。戻り値は、各登録済みタイプ("click"、"keydown" など)の配列が含まれているオブジェクトです。各配列のメンバーは、各タイプに登録されているリスナーを記述するオブジェクトです。

f:id:cidermitaina:20181208153700g:plain

monitorEvents() & unmonitorEvents()

monitorEvents()は要素とイベントを指定することで、その要素に対するイベントの発生をモニタリングすることができます。

f:id:cidermitaina:20181208162134g:plain







ChromeのDeveloper Toolを使ったデバック方法についてもう少しいろいろ書きたいな...


参考

developers.google.com



Dockerで作ったWebサーバでページを表示してみる。

Dockerの勉強を始めました。

サーバー構築してApacheとかnginxのデフォルトページを表示するっていう記事が多くて、実際に作ったページをDockerで表示するにはどうするんだろう?と思って調べたことをまとめました。

こんな感じのページをDockerで作ったWebサーバで表示させます。

f:id:cidermitaina:20180905120917p:plain

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のデフォルト画面が表示されるはずです。

f:id:cidermitaina:20180905122429p:plain



実際にページを作成してみる

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ファイルの作成

htmlとcssを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/ にアクセスすると

f:id:cidermitaina:20180905120917p:plain

が表示されると完成です◎






参考にした本と記事です。

docs.docker.com



続きの記事として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属性で要素を画面上でどのように表示するか指定します。

www.ampproject.org

これだけで遅延ロードができます。

<!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はまだよく分かっていないので時間があるとき調べたい...です。



参考

www.ampproject.org