Googleウェブマスター向け公式ブログで相性が良いと紹介され、話題になったJSON-LDとWeb Componentsを組み合わせる簡単なサンプルを作ってみました。

Googleウェブマスター向け公式ブログに掲載された、「Web Components と JSON-LD でウェブサイト開発がより簡単に」という記事にて、HTMLの構造化データの記法であるJSON-LDとWeb Componentsの相性の良さが紹介され注目を集めました。

JSON-LDはJSON Linked Dataという構造化データの実装に使用できるJSONをベースとしたデータ形式です。構造がシンプルであるのと、schema.orgなどの語彙を記述できるため、検索エンジンなどのマシンにとって読みやすくまたプログラムから扱いやすいという利点があります。

Web Componentsは、Webサイト/アプリにおける部品を作るための技術です。その仕様はまだ策定中でブラウザによる実装も完全ではありませんが、ウェブの開発を簡単にする技術として注目されています。

上記の記事では、相性が良い理由としてJSON-LDとWeb Componentsは表示の機能とデータの関係性となり、マシンリーダブルなデータを簡単にビジュアライズできる組み合わせであると述べられています。

記事内で合わせて作成に役立つリソースの紹介がありましたので、実際に簡単なサンプルを作ってみました。よって以下のサンプルは下記のリソースを参考にしています。

JSON-LDを用意する

今回はこのトレンドウォッチの記事リストをJSON-LD形式で用意しました。

<script id="jsonld" type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "BlogPosting",
  "name": "文字情報の最適化 UDフォント",
  "url": "http://digiper.com/trend/article/286.shtml",
  "datePublished": "2015-03-11T09:25:18+09:00"
}
</script>

通常のJSONとの違いは、@context@typeでボキャブラリーを指定するところです。 今回は、schema.orgのBlog記事に相当するBlogPostingを選択しました。 その他は、BlogPosting で使用可能なプロパティを選択しています。

用意したJSON-LDを表示するWeb Components を作成する

Web Components は

  • template要素
  • HTML imports
  • Shadow root
  • Custom Elements

の4つの技術から成り立っています。その全てを使う必要はありませんが、今回はサンプルということで全てを使用してみます。

template要素

まずtemplate要素についてですが

template要素は、スクリプトによる文書への挿入・複製が可能なHTMLの断片を定義します。

HTML5&CSS3/2.1全辞典(小川・加藤 &できるシリーズ編集部, 2015) p.43

ということで、テンプレートのHTMLをtemplate要素で囲うと不活性なHTMLテンプレートを定義できます。

今回は記事リストのHTMLをtemplate要素内に定義します。今回データで用意されているのは

  • 記事の投稿時間
  • 記事ページのURL
  • 記事名

の3つですので、投稿時間を<time>要素のテキストとdatetime属性に、記事へのリンク(リンクテキストは記事名)というひな形を作ります。 と言っても普通にマークアップしたものを、<template>で囲うだけです。

<template id="article-item-template">
<li class="article-item">
  <time datetime=""></time>
  <a href=""></a>
</li>
</template>

template要素は、実際に使用されるまで実際の画面には表示されず、画像などがあっても読み込みに行きません。実際に使用するにはjavascriptでアクティベートする必要があります。

//template要素の中身を取得
var t = document.querySelector('template');

//importNode()を使用してクローンを作成
var articleItem = document.importNode(t.content, true);

//クローンの要素に値を代入
articleItem.querySelector('a').textContent = "文字情報の最適化 UDフォント";

document.body.appendChild(articlItem); //クローン要素をbodyに追加

ここで、クローンの値にJSON-LDのデータを挿入すると最もシンプルなWeb ComponentsとJSON-LDの連携となります。JSON-LDの値をtemplate要素に渡すには、script要素のテキストデータを取得し、JSONオブジェクトに変換して使用します。

// JSON-LDのテキストデータを取得
var jsontext = document.querySelector('script[type="application/ld+json"]').textContent;

// Object形式に変換
var jsonld = JSON.parse(jsontext);

// クローンした要素に代入
articleItem.querySelector('a').textContent = jsonld.name;

HTML imports

HTML importsは、ページで使用する外部のリソースをHTMLの枠組みで取得する機能です。これまでHTMLはCSSやjavascript, もしくは画像などのリソースは読み込んで来ることが出来ましたが、例えばHTMLそれ自体を読み込むことは出来ませんでした。そのため、共通で利用するファイルは、サーバーサイドの技術や、Ajaxなどの技術で取り込んでいました。

HTML importsはlink要素を用いて、別のHTMLを読み込みます。先ほどのtemplate要素で記述したtemplateをarticle-item.htmlとして保存し、それを読み込むには

<link rel="import" href="article-item.html" />

と1行記載するだけです。ただしimportされたコンテンツは、読み込まれただけで表示はされません。template要素と同じく、scriptなどをアクセスし利用する必要があります。importされたコンテンツはlink要素のimportプロパティからアクセスできます。

var content = document.querySelector('link[rel="import"]').import;

よって、さきほどのtemplate要素を読み込んで使用するには、下記のコードとなります。

<head>
  <link rel="import" href="article-item.html" />
</head>
<body>
  <ul class="article-list"></ul>
<script>
  var link = document.querySelector('link[rel="import"]').import; // link要素を取得
  var t = link.querySelector('template');           // 読み込んだtemplate要素を取得
  var articleItem = document.importNode(t.content, true);  // template要素をクローン

  ~クローンした要素にデータを代入(省略)~

  document.querySelector('.article-list').appendChild(articleItem); // ul.articleItem に追加
</script>
</body>

段々と部品らしくなってきました。

Shadow DOM

3つめのWeb Componentsを構成する要素は、Shadow DOMです。Shadow DOMはWeb Componentsを完全に独立した部品として機能させるのに大事な役割を持っています。

例えば、部品がスタイルを持っていた際、それが既存の要素にまで影響してしまいます。

article-item.shtml
<template id="article-item-template">
<li class="article-item">
  <style>
    time { font-weight: bold; }
    a { text-decoration: none; }
  </style>
  <time datetime=""></time>
  <a href=""></a>
</li>
</template>
index.html
<head>
  <link rel="import" href="article-item.html" />
</head>
<body>
  <ul class="article-list"></ul>
  <p><time datetime="2015-03">2015年03年</time>の<a href="/2015/03/">記事一覧へ</a></p>
<script>
 ~~ 省略 ~~
</script>
</body>

この場合、ul.article-list直下のp要素内のtimeaにまでスタイルがあたってしまいます。

そのため、これまではセレクタが一意になるようなclassやidを要素に追加していましたが、それでも重複しない保証はありません。そこで要素を完全にカプセル化してしまうのがShadow DOMです。

Shadow DOMを使用すると、その要素は通常のDOMツリーと別のShadow Rootを持つDOMツリーを持ちます。今回は、記事を内包するulをカプセル化して、templateを複製して作成した記事アイテムをShadow Rootに追加してみましょう。

Shadow DOMは、createShadowRoot()という関数を用いて作成します。そこにtemplate要素をクローンした要素を追加すると、全体がカプセル化され内部のスタイルは他の要素に影響を与えません。

<script>
  var shadow = document.querySelector('.article-list').createShadowRoot();
  ~~ 中略 ~~
  shadow.appendChild(articleItem);
</script>

これで他の要素に影響を与えない、単独の部品として扱えるようになりました。

Custom Elements

Custom Elementsを使用することで、制作者は新しいHTMLの要素を作成し、既存の要素にはない機能をもたせたり、既存の要素を拡張したりすることができます。

Custom ElementsはregisterElement()関数を使用して作成します。

<script>
  var articleList = document.registerElement('article-list');
</script>

Custom Elementsを作成するときは、要素名に"-"を含む必要があることに注意してください。このルールがあることで通常のHTML要素(これから策定される要素を含む)と重複しないことが約束されます。

また既存の要素を拡張する際は、要素を作成するときに既存の要素を継承する形で作成します。その場合は、registerElement()の二つ目の引数に継承する要素を指定します。今回は、記事アイテム<li>要素を持つ<ul>要素を拡張します。

<script>
  var articleList = document.registerElement('article-list', {
    prototype: Object.create(HTMLUListElement.prototype),
    extends: 'ul'
  });
</script>

作成したCustom Elementには独自のプロパティやメソッドを定義することが可能で、さらに特徴的なのがライフサイクルコールバックというメソッドを定義することが出来る点です。ライフサイクルコールバックはCustom Elementのインスタンスが作られた時や、ドキュメントに追加された時、属性に変更があった時などのタイミングで呼び出されるメソッドです。

コールバック名 呼び出されるタイミング
createdCallback 要素のインスタンスが作られた時
attachedCallback インスタンスがドキュメントに追加された時
detachedCallback インスタンスがドキュメントから取り除かれた時
attributeChangedCallback(attrName, oldVal, newVal) 属性が追加、削除、更新された時
via Custom Elements HTML に新しい要素を定義する

今回は、要素がインスタンス化されたタイミングで、ページ内のJSON-LDを読み込み自身の子要素に記事アイテムを追加する関数を定義してみます。Custom Elementは宣言するとインスタンス化されますので、ページ内でCustom Elementを記述するだけで、JSON-LDをビジュアライズ化してくれます。

  // HTMLUListElement を継承して prototypeを作成
  var articleListProto = Object.create(HTMLUListElement.prototype);

  // 要素がインスタンス化された時の挙動を定義
  articleListProto.createdCallback = function() {

    // 読み込まれたページ内の JSON-LDを取得して、jsonld属性に付与
    var jsonld = document.body.querySelector('script[type="application/ld+json"]').textContent;
    this.setAttribute('jsonld', jsontext);
  }

  // 要素の属性が変更された時の挙動を定義
  articleListProto.attributeChangedCallback = function(attrName, oldVal, newVal) {

    var jsonld = JSON.parse(newVal);      // 受け取ったテキストデータをObjectに

    var shadow = this.createShadowRoot(); // Custom Element を Shadow DOMに

    ~~ (中略)JSON-LDデータを template要素からクローンした要素に代入 ~~

    // クローンした要素をShadow DOMに追加
    shadow.appendChild(articleItem);

    // 二つのメソッドを持ったCustom Elmentsを登録
    document.registerElement('article-list', {
      prototype:articleListProto,
      extends: 'ul'
    });
  }

上記のscriptとtemplateを持ったarticle-item.htmlをHTML importsで読み込み、HTMLの中で<ul is="article-list"></ul>を宣言するだけでページ内のJSON-LDデータを表示するリスト部品が使用できます。

現時点で動作するのは Google Chromeのみですが、Chromeベースのアプリケーションや、Googleが作成しているPolymerなどのPolyfillを用いて使用されるケースも増えてきています。

もしかしたら今後は、JSON-LD形式とWeb Componentsを設置すればWebサイトが出来るようになるかもしれませんね。

※以上のコード全てが動作しているデモページは私のBlog記事のデモページを見てください。

  • 1

デジパでは、一緒に働いてくれる仲間を募集しています。詳しくはこちら