私家版HTML文書執筆ガイド


e-yuuki.orgの文書を書くときに気をつけていることをまとめます。したがってHTML Living Standardの範疇を超える話題も含まれます。ベストプラクティスを集めたわけではありません。WHATWGの勧告やValidatorの更新によって都度変更される可能性があります。

HTML Living Standard

HTMLはWHATWGのHTML Living Standardに従って書きます。人力で規格どおりに書けているかをチェックするのは非現実的です。実際にはvalidatorにチェックを委託します。

HTMLのテンプレートは以下のとおりです。ここでは読みやすさのためインデントをしていますが、下記で述べるように実際に公開するコードはインデントを削除します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="@@/path/to/your/css@@">
    <title>@@Webサイトのタイトルを書く@@</title>
  </head>
  <body>
    <!-- 1ページにつき1 article-->
    <article class="@@class if you have@@">
      <header> <!-- headerにコンテンツのタイトルと概要および目次を書く-->
        <h1>@@ページのタイトルを書く@@</h1>
        <nav> <!-- 目次 -->
          <ol> <!-- ページ内リンクはid名に#をつける -->
            <li><a href="#section001">Section 001</a></li>
            <li><a href="#section002">Section 002</a></li>
          </ol>
        </nav> <!-- 目次の終わり -->
      <header>
      <section> <!-- 節(セクション)はsectionタグで区切る -->
        <h2 id="section001">Section 001</h2>
      </section>
      <section>
        <h2 id="section002">Section 002</h2>
      </section>
      <footer>
        <!-- フッタに著作権情報・日付・タグなどの情報を書く -->
      </footer>
    </article>
  </body>
</html>
「正しい」HTML5

W3Cの勧告どおりにHTML5をコーディングするのはとても大変です。原文が英語なので、なにか調べるのにも非英語ネイティブからすれば一苦労です。くわえてHTML5について書いている記事は無数にありどれを信用していいのかわかりません。

しかし一番大変なのは、HTML5の変更に追従していくことでしょう。こればかりはどうしようもないので、もとのテキストをMarkdownなどの軽量マークアップ言語で書いて、PandocのようなコンバータでHTML5に変換する運用をとるのがもっとも楽かと思われます。事実僕もHTML5らしいコーディングスタイルに完全に移行できていません。

HTML5からHTML Living Standardへ

2021年1月28日にW3CのHTML5が廃止され、HTMLの標準規格はHTML Living Standardになりました。気がかりなのはvalidatorでした。古い規格にしたがってコード検査されてしまうなら新しい規格に準じたコーディングは不可能だからです。しかしhttps://whatwg.org/validator/では下記でvalidatorとして紹介しているThe Nu HTML Checkerが挙げられています。公式サイトで紹介されているなら利用を続けても問題なさそうでしょう。

Validator

The Nu HTML Checkerがおすすめです。ビルド方法はNetBSDでNu HTML Checkerをビルドするを参照してください。Gitを使っているなら、Gitフックのpre-commitスクリプト内でこのソフトウェアを使うと良いでしょう。以下はpre-commitスクリプトの一例です。chmod +xコマンドで実行権限を与え、変数の値は適宜変更してください。

#!/bin/sh

java="/usr/pkg/bin/openjdk8-java"
gitdir="/home/uki/src/github.com"
validator_dir="$gitdir/validator/validator"

git diff --cached --name-only --diff-filter=AM HEAD \
  | grep 'html$' \
  | xargs $java -jar $validator_dir/build/dist/vnu.jar

Webページ公開後はPageSpeed Insightsからページのスコアを確認し、速度が改善できそうな箇所を探します。不要なインデントによるスペース/タブ文字の削除はその代表例です。

日本語校正

日本語の校正にはtextlintを使います。pkgsrc(7)ではtextlintがパッケージとして提供されていません。そのためlang/npmパッケージをインストールし、npm(1)経由でtextlintとそのルールセットをインストールする必要があります。pkgsrc-2018Q4ではlang/npmパッケージをビルドすると途中でSegmentation Faultが起きたのですが、pkgsrc-2019Q1からは正常にビルドできるようになりました。

npm(1)からtextlintとそのルールセット、HTMLプラグインをインストールするには次のシェルスクリプトを実行してください。

#!/bin/sh

cmd="npm"
cmd_opt="install -g"
targets="textlint
@textlint-ja/textlint-rule-no-insert-dropping-sa
@textlint-ja/textlint-rule-no-synonyms
sudachi-synonyms-dictionary
textlint-filter-rule-whitelist
textlint-plugin-html
textlint-rule-first-sentence-length
textlint-rule-ja-hiragana-fukushi
textlint-rule-ja-hiragana-hojodoushi
textlint-rule-ja-hiragana-keishikimeishi
textlint-rule-ja-no-abusage
textlint-rule-ja-no-mixed-period
textlint-rule-ja-no-redundant-expression
textlint-rule-ja-no-successive-word
textlint-rule-ja-no-weak-phrase
textlint-rule-max-ten
textlint-rule-ng-word
textlint-rule-no-dead-link
textlint-rule-no-double-negative-ja
textlint-rule-no-doubled-conjunction
textlint-rule-no-doubled-conjunctive-particle-ga
textlint-rule-no-doubled-joshi
textlint-rule-no-dropping-the-ra
textlint-rule-no-mix-dearu-desumasu
textlint-rule-no-start-duplicated-conjunction
textlint-rule-prefer-tari-tari
textlint-rule-preset-jtf-style
textlint-rule-sentence-length
textlint-rule-terminology"
textlintrc="textlintrc"

for package in $targets; do
  $cmd "$cmd_opt" "$package"
done

if [ -f "$textlintrc" ]; then
    if [ -f "$HOME/.${textlintrc}" ]; then
        exit 0
    else
        cp "$textlintrc" "$HOME/.${textlintrc}"
    fi
fi

textlintの設定ファイル$HOME/.textlintrcを次のように書きます。設定内容は『JTF日本語標準スタイルガイドのルールセットで文章をチェックできるtextlintプリセット』、『文書執筆の指南書で解説されている問題点を textlint で発見する』、『WebサイトのHTMLをtextlintでチェックする』を参考にしました。

{
    "plugins": [
        "html"
    ],
    "rules": {
        "max-ten": {
            "max" : 3
        },
        "no-start-duplicated-conjunction": {
            "interval" : 2
        },
        "no-doubled-conjunctive-particle-ga": true,
        "no-doubled-conjunction": true,
        "no-double-negative-ja": true,
        "ja-no-redundant-expression": true,
        "no-dropping-the-ra": true,
        "ja-no-successive-word": true,
        "@textlint-ja/no-synonyms": true,
        "ja-hiragana-keishikimeishi": true,
        "ja-hiragana-fukushi": true,
        "ja-hiragana-hojodoushi": true,
        "@textlint-ja/textlint-rule-no-insert-dropping-sa": true,
        "prefer-tari-tari": true,
        "ja-no-abusage": true,
        "ja-no-mixed-period": true,
        "ja-no-weak-phrase": true,
        "no-mix-dearu-desumasu": {
            "preferInHeader": "",
            "preferInBody": "ですます",
            "preferInList": "ですます",
            "strict": true
        },
        "no-doubled-joshi": {
            "min_interval" : 1,
            "strict": false,
            "allow": []
        },
        "terminology": {
            "defaultTerms": true,
            "skip": ["Blockquote"],
            "terms": [
                "JavaScript",
                "ESLint",
                "Sass",
                "Less",
                "npm",
                ["front[- ]end(\\w*)", "frontend$1"],
                ["back[- ]end(\\w*)", "backend$1"],
                ["web[- ]?site(s?)", "site$1"],
                ["hot[- ]key", "hotkey"],
                ["repo\\b", "repository"],
                ["CLI tool(s?)", "command line tool$1"],
                ["build system(s?)", "build tool$1"],
                ["id['’]?s", "IDs"],
                ["(\\w+[^.?!]\\)? )webpack", "$1webpack"],
                ["(\\w+[^.?!]\\)? )internet", "$internet"]
            ]
        },
        "preset-jtf-style": "true"
    },
    "filters": {
        "whitelist": "true"
    }
}

コマンドラインから文章校正ができます。

textlint html.html

Gitのpre-commitスクリプトに書いておくと、commit時に自動で文章校正ができます。前述したvalidatorと組み合わせると次のようになります。HTMLと日本語のチェックが同時にでき、強力です。

#!/bin/sh

java="/usr/pkg/bin/openjdk8-java"
gitdir="/home/uki/src/github.com"
validator_dir="$gitdir/validator/validator"

git diff --cached --name-only --diff-filter=AM HEAD \
  | grep 'html$' \
  | xargs $java -jar $validator_dir/build/dist/vnu.jar
git diff --cached --name-only --diff-filter=AM HEAD \
  | grep 'html$' \
  | xargs textlint

一行あたりの文字数とインデント

一行あたりの文字数制限はありません。余計な空白が挿入されることを防ぐため、むしろ一段落(pタグで囲う)は一行に収めます。この方針はバージョン管理システムで差分を取ったとき、どこが変更されたのかが分かりづらいという明らかな欠点があります。この欠点を許容できないなら改行は文単位でおこなうべきです。

ページサイズを抑えるために、インデントは必要ありません。HTMLのコーディングではPythonのようにインデントが義務付けられていません。複雑なリストを書くときはインデントを使って見やすくしても構いませんが、commitするときはインデントをすべて削除します。

JavaScript

JavaScriptは使いません。TwitterやFacebook, Instagramといったユーザインタラクションが重要視されるサービスはともかく、テキスト中心の静的な技術文書はHTMLとCSSのみで書けます。

必ずしもユーザがJavaScriptを有効にしているとは限らないという点に注意してください。セキュリティ意識が高いユーザはJavaScriptを標準で無効にする拡張機能をブラウザにインストールしていることもあります(NoScriptなど)。またCUI Webブラウザ(w3m, lynxなど)ではJavaScriptを実行できません。一方で見栄え・組版・収益化などの都合でJavaScriptがどうしても欠かせないときもあります。そういった場合にはJavaScriptがなくても最低限、文章だけは読めるようにしてください。

キャッシュ(cache)

キャッシュはできる限り活用しますが、運用について少し考えなければなりません。画像ファイル(png, jpeg)は滅多に上書き変更する機会はないでしょう。論文や発表資料のPDFもそうだと考えられます。つまり一度サーバに公開したらそれっきりな拡張子のファイルはキャッシュの有効期間を長めにします。一方でHTMLファイルやテキストファイルは常に変更される可能性があります。そのためキャッシュは使わないか、使うにしても有効期間を短めにします。

キャッシュの設定はWebサーバのプロバイダに依存します。Webサーバがレンタルサーバでなら、/etc/httpdディレクトリ以下にあるファイルを編集したりhttpdデーモンを再起動したりする権限は与えられていないと思われます。したがってこの場合には.htaccessを編集してキャッシュの設定をします。たとえば以下のように.htaccessに追記します。

<Files ~ ".(png|jpe?g)">
    Header set Cache-Control "public, max-age=604800"
</Files>

VPSやクラウドを契約してWebサーバを運用している場合は自由に/etcディレクトリ以下のファイルを編集できるので、NginxやApacheの設定ファイルを適宜変更してください。

gzip圧縮

転送速度向上を目的にテキストファイルをサーバ側で圧縮できます。サーバ側でmod_deflateモジュールが有効になっている必要があります。.htaccessに以下を追記します。

<IfModule mod_deflate.c>
    SetOutputFilter DEFLATE

    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ico)$ no-gzip dont-vary
    SetEnvIfNoCase Request_URI _\.utxt$ no-gzip

    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/atom_xml
    AddOutputFilterByType DEFLATE application/x-javascript
    AddOutputFilterByType DEFLATE application/x-httpd-php
</IfModule>

画像圧縮

This topic was suggested by Andrea Morrys. Thank you.

キャッシュやgzip圧縮の他に、公開している画像そのものをあらかじめ圧縮しておくことも有効な手段です。ここではhttps://commons.wikimedia.org/wiki/File:Andromeda_galaxy.jpgからアンドロメダ銀河の画像をダウンロードし、どの手法でどの程度圧縮できるか確認します。ファイルサイズは4.1MBです。

WebsitePlanet.comではPNG, JPEG形式の画像を圧縮できるツールを公開しています。ひとつ50MB以内で最大40個の画像を一度に圧縮できるようです。

imagecompressor

結果、43%圧縮できたようです。

他にも、graphics/optipngやgraphics/jpegoptimなどpkgsrc(7)で提供されているパッケージをインストールすることで、コマンドラインから画像を圧縮できます。optipng(1)で上の画像を圧縮した結果、15%の圧縮ができました。

optipng html_001.png
** Processing: html_001.png
1350x646 pixels, 4x8 bits/pixel, RGB+alpha
Reducing image to 3x8 bits/pixel, RGB
Input IDAT size = 116635 bytes
Input file size = 116860 bytes

Trying:
  zc = 9  zm = 8  zs = 0  f = 0         IDAT size = 98513

Selecting parameters:
  zc = 9  zm = 8  zs = 0  f = 0         IDAT size = 98513

Output IDAT size = 98513 bytes (18122 bytes decrease)
Output file size = 98570 bytes (18290 bytes = 15.65% decrease)

Tips

引用の方法

引用の方法はそれが短いものか長いものかによって変わります。どの程度から長いと判断するかは曖昧ですが、文を2〜3以上引用するときは長い引用の方法をとります。

短い引用なら「」で囲んで文中に書き、出典元を()の中に明記します。たとえば「出版された情報をコンピュータを使って共有しようとする読者は、 法律上は著作権に違反しているのです」(Richard Stallman,『自由か著作権か?』)

長い引用はblockquoteタグを使います。コードのフォーマットは以下の通りです(読みやすさを考慮し整形)。

<figure>
  <blockquote>
    <p>引用文</p>
  </blockquote>
  <footer>
    &mdash;&mdash;<cite>著者名</cite>, <cite>本のタイトル</cite>
  </footer>
</figure>

これは以下のように描画されます。

しかし、そこには1つの障害が立ちはだかっていました。著作権です。出版された情報をコンピュータを使って共有しようとする読者は、 法律上は著作権に違反しているのです。世界は変わり、かつては出版社にとっての産業上の規制であったものが、 本来奉仕すべき一般の人々に対しての規制となってしまいました。

——Richard Stallman, 結城浩(訳), 自由か著作権か?

HTMLコーディングのための$HOME/.vimrc

タグを自動的に閉じる

対となるタグを「</」と入力した時点で閉じるようにします。$HOME/.vimrcに以下を追記します。

augroup CloseTag
    autocmd!
    autocmd Filetype xml inoremap <buffer> </ </<C-x><C-o>
    autocmd Filetype html inoremap <buffer> </ </<C-x><C-o>
augroup END

textlintを自動実行する

ファイルへの変更を保存した時点でtextlintを自動的に実行するよう、syntasticプラグインを設定します。

let g:syntastic_html_checkers = ['textlint']

NetBSDでNu HTML Checkerをビルドする

pkgsrc(7)ではNu HTML Checkerのパッケージが提供されていません。NetBSDでNu HTML Checkerを使うにはGitリポジトリからソースコードをcloneしてきてビルドします。ビルドには以下のパッケージが必要です。

https://github.com/validator/validatorをcloneします。

git clone https://github.com/validator/validator.git
cd validator

build/build.pyに以下のような変更を加えます。pkgsrc(7)ではPythonインタプリタがバージョン番号付きで提供されており、Javaのコマンド群にはopenjdk8-という接頭辞が付きます。また、ビルド中にException in thread "main" java.lang.OutOfMemoryError: Java heap spaceというエラーが出てビルドが失敗するケースもあるので、メモリを最大4GBまで使えるように変更しています。この数値はビルド環境に応じて変更してください。

diff --git a/build/build.py b/build/build.py
index 4cfc5d0b..8cb3d4cb 100755
--- a/build/build.py
+++ b/build/build.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/pkg/bin/python2.7
 # -*- coding: utf-8 -*- vim: set fileencoding=utf-8 :

 # Copyright (c) 2007 Henri Sivonen
@@ -58,10 +58,10 @@ except ImportError:
     CAFILE = None

 javaVersion = '1.8'
-javacCmd = 'javac'
-jarCmd = 'jar'
-javaCmd = 'java'
-javadocCmd = 'javadoc'
+javacCmd = 'openjdk8-javac'
+jarCmd = 'openjdk8-jar'
+javaCmd = 'openjdk8-java'
+javadocCmd = 'openjdk8-javadoc'
 herokuCmd = 'heroku'
 ghRelCmd = 'github-release'  # https://github.com/sideshowbarker/github-release
 tarCmd = 'tar'
@@ -1430,7 +1430,7 @@ def runTests():
         jarNamesToPaths(["galimatias", "htmlparser", "validator"]) +
         cssValidatorJarPath() +
         jingJarPath())
-    if runCmd([javaCmd, '-classpath', classPath, className] + args):
+    if runCmd([javaCmd, '-Xmx4g', '-classpath', classPath, className] + args):
         sys.exit(1)

ビルドスクリプトを実行します。実行可能なjarファイルはbuild/dist/vnu.jarとして生成されます。

python2.7 build/build.py build
python2.7 build/build.py jar