HTMLの文字コードをどうするべきか、あるいはHTMLとは何かという話

HTML文書は文字エンコーディングUTF-8でなければなりませんという記事があり、混乱があるようなのでHTMLについてHTML5とHTML Living Standard(以下HTML LSと省略)について、そしてHTMLファイルの文字コードをどうするかについて、まとめておきます。

TL;DR

  • HTMLファイルの文字コードはHTML Living Standardに従ってUTF-8にする
  • 古いSJISやEUC-JPのHTMLファイルをUTF-8に変換する必要はない

What is "HTML" ?

一般にHTMLと呼ばれる規格には複数あります。

  • HTML4.01を含むそれ以前のHTML (W3C)
  • XHTML1.1 (W3C)
  • HTML5.1 (W3C)
  • HTML Living Standard (WHATWG)

まず一旦古い話は置いておいて、HTML5とHTML LSについて考えることにします。 以下「HTML5」は特筆なき場合「W3Cの勧告したHTML5.1」の意味です。また、HTML5の日本語訳へのリンク先は5.0の文書ですのでご注意下さい。

HTML5(W3C) とHTML LS(WHATWG)の違い

HTML5とHTML LSは基本的には違いはありません。
1.2 Is this HTML5? - HTML Living Standard1.2 これはHTML5か? ― HTML Living Standard 日本語訳
にあるようにWHATWGの策定したHTML LSに基いてW3CがHTML5として勧告を出しています。

そもそもWHATWGはW3CがHTMLをアップデートしないのに業を煮やしたAppleやMozilla、Operaによって設立されました。それを後にW3Cが取り込んだのが規格としてのHTML5となります。
経緯は1.6 歴史 ― HTML Living Standardを参照するのが良いでしょう。

W3CとWHATWGのどちらに従うべきか

ブラウザの実装による、と言ってしまえばそれまでですが、基本的にHTML Living Standardを守れば良いです。ブラウザの実装もこれに則っています。またW3CではHTML LSに基いているだけで、全く同一ではありません。また、標準化のタイミングも異なります。

  1. HTML LSで標準化済みで、HTML5で勧告済み(多くの基本的な内容)
  2. HTML LSで標準化済みだが、W3Cでは未勧告(最新の内容)
  3. HTML LSで標準化済みだが、W3Cでは違いがある(一部の内容)

という3つのパターンがありえます。

3.については1.12 About this document - HTML5.1に列挙されています。

やっと文字コードの話

再掲になりますがHTML文書は文字エンコーディングUTF-8でなければなりませんの文字エンコーディングの話は

  • WHATWGでHTML Living Standardの話
  • W3Cでは未勧告(まだissueが切られただけ)

となります。
つまり、HTML4.01やXHTMLは関係がないので、古いサイトの文字コードをUTF-8に変換する必要はありません。
BOMについては、UTF-8についてはBOMをつけることが許可されているので、つけても良いですが、つけなくて良いでしょう。<meta charset="utf-8">のようにmetaタグで文字コードを指定することも禁止されていません。(が、当然ファイルそのものの文字コードと一致する必要があります。)

逆に他のSJISやUTF-16などの文字コードが利用できないかについては、HTML LSでは

the actual character encoding used to encode the document must be UTF-8.

4.2.5.5 Specifying the document's character encoding - HTML Living Standard

とあり、一方HTML5の現状では

Authors should use UTF-8. Conformance checkers may advise authors against using legacy encodings.
(中略)
Authors must not use encodings that are not defined in the WHATWG Encoding standard. Additionally, authors should not use ISO-2022-JP.

4.2.5.5 Specifying the document's character encoding - HTML5.1

と微妙に差異があります。が、これに逆らってUTF-8以外を選択するメリットは殆どないでしょう。

おわりに

Web開発者であれば、WHATWGとW3Cの文章に目を通すようにしましょう。

iOS10のSafariではVideo要素のインライン再生が可能になった

iOS9までのSafariでは、基本的にvideoのインライン再生はできず、再生すると強制的にフルスクリーンになっていました。

iOS10からはvideo要素にplaysinline属性をつけてあげることでインライン再生が可能になってます。

<video src="path/to/video.mp4" controls playsinline></video>

※ただしiOS9でもWebViewでは webkit-playsinline でインライン再生できました。

そしてこの仕様に併せて、JSからvideoのフルスクリーン制御もできるようになってます。

const video = document.querySelector('video');

// フルスクリーンにする
if(video.webkitSupportsFullscreen){
  video.webkitEnterFullscreen();
}

// フルスクリーンから離脱する
if(video.webkitSupportsFullscreen){
  video.webkitExitFullscreen();
}

video以外の要素で webkitRequestFullscreen を使ってフルスクリーンするのはまだiOSでは解禁されていませんが、videoのみ webkitEnterFullscreen でフルスクリーンできるようになった、ということです

SSRしないNuxt.jsでページロード時に非同期部分を更新する方法

Nuxt.jsでは、外部サーバ等から非同期でデータを取得・表示する場合にも asyncData を使ってあげることでクライアントサイドはもちろん、サーバサイドでもデータを取得し、HTMLで出力することが出来ます。

SSRできる本番環境があれば何の問題もないのですが、SSRせずプリレンダリングのみで運用する場合には、 asyncData は静的ファイルをgenerateした時点でしか走っておらず、ページロード時にデータが更新されていない問題が起こり得ます。

例えばコーポレートサイト等で、別のサーバにWordPressが設置してあり、トップページのお知らせとしてWordPressから記事を取得するような場合を想定すると、こんな感じで作ってあげることで解決できます

// 非同期でデータを取ってくる関数
async function getPostData() {
  return { data } = await axios.get('https://blog.example.com/wp-json/wp/v2/posts');
}

export default {
  async asyncData(context) {
    const obj = {};
    if (context.isServer) { // asyncDataするのはサーバサイド(プリレンダリング時)のみ
      obj.posts = await getPostData();
    }
    return obj;
  },
  async created() {
    if (!this.$isServer) { // クライアントサイドはここで更新をかける
      this.posts = await getPostData();
    }
  },
};

要は asyncData を使うのはサーバサイド(プリレンダリング時)のみ。
クライアントサイドでは created でデータの更新をかけてあげるわけです。

こうすることでプリレンダリング運用でも、一応(少し古いかもしれないけど)非同期データのHTMLもプリレンダリングされていて、ブラウザで訪れた際にはちゃんと最新の情報に更新されます。

Prerender SPA Plugin を使ってSPAサイトのSEO対策をする

株式会社なすびのウェブサイトは、Vue.jsを用いたSPAで作られています(2017年4月現在)。

コーポレートサイトのような情報の提供に重きを置くサイトで、SPAのアーキテクチャを使うことはSEOの面からあまり適した用途であるとは言えません。

それは、GoogleボットこそJSを実行してからパースしてくれるので問題にはならないのですが、FacebookなどのSNSにシェアされた場合は未だにJSの実行までしてくれないからです。

これを解決するためには、サーバーからのレスポンスでHTMLを返す必要性が絶対的にあるのですが、いかんせんSSRを前提に環境の構築を考えると、サーバサイドでNode.jsを噛ませる必要があったりなど、なかなかに敷居が高いのです。

しかしコーポレートサイトのような、ページ数も動的コンテンツも少ない小規模サイトであれば、プリレンダリングという選択肢もあります。

今回はwebpackプラグインでサクッとプリレンダリングする方法の紹介です。

Prerender SPA Plugin
https://github.com/chrisvfritz/prerender-spa-plugin

このプラグインを使って、webpackのbuild過程で静的HTMLを書き出してしまいます。

// webpack.conf.js
var Path = require('path')
var PrerenderSpaPlugin = require('prerender-spa-plugin')

module.exports = {
  // ...
  plugins: [
    new PrerenderSpaPlugin(
      // Absolute path to compiled SPA
      Path.join(__dirname, '../dist'),
      // List of routes to prerender
      [ '/', '/about', '/contact' ]
    )
  ]
}

上記のように保存先とエンドポイントを設定してbuildすると、下記3ファイルが書き出されます。

/index.html
/about/index.html
/contact/index.html

これらのファイルをウェブサーバーに配置すると、当然下記URLでbuildされたHTMLがレスポンスされるのですが、

http://example.com/
http://example.com/about/
http://example.com/contact/

SPAとしてリンクを辿ってaboutページに遷移すると、URLは /about となります。

/about/about/ は違うファイルを指しているため、この状態でリロードすると404になってしまいます。
これを回避するためには、 /about にリクエストが来たら /about/ へリダイレクトする.htaccessを置いてあげれば良いのです

<ifModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !index
RewriteCond %{REQUEST_URI} !.*\.(css|js|html|png|jpg)
RewriteRule (.*) / [L]
</ifModule>