😀🌏👨‍🔧📖　🌊🔧💧🕸️◀️🌐▶️🌊

😄❗️　🙂❤️😀，📝🌐📄😀．

---
タイトル日本語訳：　絵文字の世界を作った話　～h2oの設定方法とWeb Components～
上の文の日本語訳：　こんにちは！　私は絵文字が大好きなので、絵文字でウェブページを作ってみました。

【単語】😀　絵文字 / 👨‍🔧 作る / 📖 話 / 🔧 設定する / 📝 書く / 🌐📄（または🕸️📄） ウェブページ

---

というのは冗談ですが、少し前に以下の🌐📄を作成したので、少しですがそのときの知見を共有しようと思い記事を作りました。

- [😀🌏.uhyo.space](http://xn--vg8h70c.uhyo.space)

# 発端
発端は私のこのツイートです。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">だんだん拡張子の存在感が薄くなってきたこのご時世だが　そろそろ絵文字を拡張子にできるのではないか（ぇ<br><br>index.📄<br>image.🖼️<br>settings.⚙️<br>archive.📦.🗜️<br><br>みたいなのりで（ぇ</p>&mdash; 🈚️うひ🈚️💀 (@uhyo_) <a href="https://twitter.com/uhyo_/status/1053563139822780416?ref_src=twsrc%5Etfw">2018年10月20日</a></blockquote>

要するに「拡張子が絵文字だったら面白いんじゃね？」的なことを言ったわけですが、数としては多くないものの100RTくらいされると肯定的な反応や否定的な反応が飛んできます。そこで、とりあえずやってみたのが冒頭で紹介したウェブページです。

ページのアドレスに注目してみてください。トップページはよく見ると「index.📄」です。拡張子が「.📄」になっていますね。また、ソースを見てもらえると分かるのですが、CSSファイルが「styles.💄」となっていたり、画像ファイルが「card.🖼️」となっていたりします。

この記事では、このように絵文字を拡張子として使うウェブサーバーの設定を紹介します。使用するウェブサーバーは[H2O](https://h2o.examp1e.net/)です。といっても、そんなに変なことをやっているわけではないのでH2Oの設定方法紹介みたいになりますが。

H2OはHTTP2にいち早く対応した新進気鋭のウェブサーバーとして知られています。冒頭のサイトも、もちろんHTTP2で配信しています[^cloudflare]。この記事の内容は、H2Oをまだちゃんと使ったことのない方なら、まあこんな風に設定するんだよという参考にはなるのではないかと思います。

[^cloudflare]: 実はCloudflareを間にはさんでいるのでH2Oがどうとかは無関係なんですが。

# H2Oの設定
まずH2Oの設定ファイルを載せます。これが関連する設定の全てです。

```yaml:main.yml
reproxy: on
"xn--vg8h70c.uhyo.space:443":
  listen:
    port: 443
    ssl:
      <<: !file commons/ssl.yml
  header.add: "Strict-Transport-Security: max-age=31536000"
  file.index:
    - "index.📄 "
    - "index.html"
  paths:
    "/":
      mruby.handler-file: sites/xn--vg8h70c.uhyo.space/handler.rb
      file.dir: /var/www/xn--vg8h70c.uhyo.space
"xn--vg8h70c.uhyo.space:80":
  listen:
    port: 80
  paths:
    "/":
      redirect: "https://xn--vg8h70c.uhyo.space/"
```

```ruby:handler.rb
lambda do |env|
    if env["PATH_INFO"] == "/"
        return [301, {
          "location" => "/index.📄 ",
        }, []]
    end
    if env["PATH_INFO"] == "/index.html"
        return [399, {
          "link" => "</elements.%F0%9F%93%9C>; rel=preload, </styles.%F0%9F%92%84>; rel=preload"
        }, []]
    end
  
    if /^(.*)\/([^\/]*)\.%F0%9F%93%84$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.html"}, []]
    end


    if /^(.*)\/([^\/]*)\.%F0%9F%93%9C$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.js"}, []]
    end
    

    if /^(.*)\/([^\/]*)\.%E2%9A%99(?:%EF%B8%8F)?$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.js"}, []]
    end


    if /^(.*)\/([^\/]*)\.%F0%9F%92%84$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.css"}, []]
    end


    if /^(.*)\/([^\/]*)\.%F0%9F%8E%A8$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.gif"}, []]
    end


    if /^(.*)\/([^\/]*)\.%F0%9F%93%B7$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.jpg"}, []]
    end


    if /^(.*)\/([^\/]*)\.%F0%9F%96%BC(?:%EF%B8%8F)?$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.png"}, []]
    end

    return [399, {}, []]
end
```

メインのYAMLファイルと何かRubyのコードが書いてあるファイルの2つに分かれています。というか、Rubyのほうが長いですね。（あとで説明しますが、正確にはこれはMrubyのコードです。）

## punycode
まず最初に、ホスト名が`xn--vg8h70c.uhyo.space`となっていますが、これはASCII文字ではないドメイン名を表現するために使われるpunycodeというエンコーディングで、実際これは😀🌏.uhyo.spaceを表しています。

基本的にはサーバーの設定とかDNSの設定は全部punycode表現で行って、実際の絵文字とpunycodeとの変換はなんかブラウザがいい感じにやってくれます。

絵文字の場合、残念ながらChromeやFirefoxは絵文字を表示せずにpunycodeそのままになるようです。Safariは絵文字でドメイン名を表示してくれます。

---

では、設定の解説に入ります。

実は、このウェブページでは少しずるをしています。裏に用意されているウェブサイトのデータ上はファイル名は`index.html`や`styles.css`となっており、絵文字拡張子ではありません。これは絵文字拡張子に対してエディタの設定などをするのが面倒くさかったのが主な理由です。

そうなると、ウェブサーバー（H2O）に絵文字拡張子のリクエストを受け付けてもらって、拡張子を元に戻す（📄 → html）処理を行ってからファイルを探すことになります。この設定はいわゆるURLの書き換えで、Webサーバーにおいては定番の設定です。

H2Oにおいては、このようなことを行いたい場合は**Mruby**の手を借りることになります。要するに、リクエストに対する処理をMrubyで書いたプログラムにやらせるということです。何だかlua-nginx-moduleを思い出しますね。それをやっているのは`mruby.handler-file: sites/xn--vg8h70c.uhyo.space/handler.rb`というところです。これはファイル名を指定して読み込んでいますが、`mruby.handler`ディレクティブを使うことで、MrubyのコードをYAMLファイル内にベタ書きすることも可能です。

H2OにおいてはMrubyの出番は結構多いようです。例えば、単純なルーティングなら`paths`以下の設定でできますが、URLに対して正規表現によるマッチを行いたい場合もMrubyの出番となります。今回は`"/":`以下に`mruby.handler-file`ディレクティブがあるため、全てのリクエストに対してこのハンドラが走ることになります。

`handler.rb`に目を向けると、`lambda`でProcオブジェクトを作っています。（Rubyには詳しくないので用語が違うかもしれませんが許してください。）よく見ると、これは1つの引数`env`を受け取って3つ組の配列を返すようになっています。これは[Rack](https://github.com/rack/rack/blob/master/SPEC)という仕様に従っているようです。見てわかると思いますが、1つ目がステータスコード、2つ目がヘッダーです。3つ目は今回は全部空の配列になっていますが、レスポンス本文です。

```ruby
    if env["PATH_INFO"] == "/"
        return [301, {
          "location" => "/index.📄 ",
        }, []]
    end
```

これは先ほどの設定から抜粋したコードですが、これは`/`にアクセスされたときはステータスコード301で`/index.📄`にリダイレクトするという意味であることが分かります。実際、 [https://😀🌏.uhyo.space/](https://😀🌏.uhyo.space/) にアクセスすると、[https://😀🌏.uhyo.space/index.📄 ](https://😀🌏.uhyo.space/index.📄)にリダイレクトされます。今回のサイトでは、当初の目的である絵文字拡張子を強調する目的でこの設定をしています。

そのすぐ下のやつは一旦飛ばして、2つ下からは似たようなif文がたくさん並んでいます[^elsif]。

[^elsif]: `elsif`を使えよという突っ込みを入れたくなるかもしれませんが、プログラムからif文を自動生成した結果なので許してください。

```ruby
    if /^(.*)\/([^\/]*)\.%F0%9F%93%84$/.match(env["PATH_INFO"])
      return [307, {"x-reproxy-url" => "#{$1}/#{$2}.html"}, []]
    end
```

これは、リクエストURLの最後のセグメントの拡張子が`%F0%9F%93%84`ならば[^percent_encoding]ステータスコード307を返す処理に見えますが、`x-reproxy-url`というヘッダを付加しているのが特徴的です。

[^percent_encoding]: これは📄をいわゆるパーセントエンコーディングした結果です。URLの文法上は絵文字などの文字は許されておらず、パーセントエンコーディングされているものをブラウザでいい感じに処理しているということになります。Mrubyのハンドラにおいては、パーセントエンコーディングされたままの状態で扱う必要があるようです。

これはreproxyというもので、`x-reproxy-url`に指定されたURLをH2Oがフェッチしてその結果をクライアント側に送信してくれるという機能です。今回はMrubyのハンドラによって付加していますが、H2Oをアプリケーションサーバーへのリバースプロキシとして利用している場合もこの機能は有効です。

今回の場合は、`/index.📄`というリクエストに対しては`X-Reproxy-URL: /index.html`というヘッダが生成されることになります。この場合はいわゆる内部リダイレクトの挙動となり、`/index.html`の内容が返されることになります。

まとめると、Mrubyを用いて絵文字拡張子のURLを本来の拡張子のURLに内部リダイレクトする処理が行なわれていることになります。

ハンドラの末尾には`return [399, {}, []]`とあり、399という意味不明なステータスコードが返されています。実は、この特殊なコードが返された場合、リクエストの処理を次のハンドラに委譲するという意味になります。次のハンドラとは何かというのは、H2Oの設定ファイルをの中身を見返せばわかります。

```yaml:設定ファイル（抜粋）
  paths:
    "/":
      mruby.handler-file: sites/xn--vg8h70c.uhyo.space/handler.rb
      file.dir: /var/www/xn--vg8h70c.uhyo.space
```

`mruby.handler-file`の次に`file.dir`がありますね。これは、指定したディレクトリからファイルを配信するという意味です。H2Oはハンドラが指定された順番に処理されるという素敵な仕様になっていますので、Mrubyのハンドラによって処理されなかった（399が返されなかった）もののみこのディレクトリから配信されることになります。

例えば`/styles.💄`は`/styles.css`に内部リダイレクトされますが、`/styles.css`はMrubyのハンドラによって処理されませんから、ここで指定されたディレクトリの中の`styles.css`が無事に配信されることになります。

最後に、Mrubyのハンドラ内でまだ1つ説明していない部分がありました。上から2番目のif文ですね。

```ruby
    if env["PATH_INFO"] == "/index.html"
        return [399, {
          "link" => "</elements.%F0%9F%93%9C>; rel=preload, </styles.%F0%9F%92%84>; rel=preload"
        }, []]
    end
```

ここでは、399を返しているのにヘッダが指定されています。こうすると、ヘッダを追加しつつ、後ろのハンドラに処理を委譲する動作となります。つまり`/var/www/xn--vg8h70c.uhyo.space`内の`index.html`を配信しつつ`Link`ヘッダを送信することができるのです。たいへん便利ですね。

なお、Mrubyにはデフォルトでは正規表現の機能はありませんが、H2OのMrubyでは正規表現が扱えるようにmgemが追加されています。[Rubyの正規表現エンジンとして知られているOnigmoをMruby用にしたもの](https://github.com/h2o/h2o/tree/master/deps/mruby-onig-regexp)が利用可能なようです。正規表現の書き方に困ったらOnigmoを調べてみるといいかもしれません。

---

以上が今回のH2Oの設定のすべてです。ここまで読んだ方はあることにお気づきかもしれません。それは、直に[https://😀🌏.uhyo.space/index.html](https://😀🌏.uhyo.space/index.html)にアクセスすれば拡張子が絵文字でない現実の世界を覗くことができることです。これを拒否する設定は多分できると思いますが、今回は面倒なのでやっていません。Nginxなら`internal;`と書けばいいだけなので楽なんですが。

# Web Componentsの話

さて、ここから話はがらっと変わります。このウェブページのソースを見ると、だいたいこんな感じになっています。

```html
<e-🗣️>
  <e-1⃣>😀🌏</e-1⃣>
</e-🗣️>
```

`e-🗣️`や`e-1⃣`といった見慣れない要素が使われていますね。言うまでもなくこれが**Custom Elements**です。

Custom Elementsは、新しい要素を自分で定義できる機能です。ただし、ネイティブのHTML要素と区別するために、名前には`-`を必ず入れる必要があります。また、パーサーとの兼ね合いから、タグ名の最初の文字はアルファベットでなければいけません。本当は全部絵文字のタグ名にしたかったのですが、このような制限から`e-🗣️`のような名前になっています。ちょっと見苦しいですが仕方ありません。

どのようにCustom Elementsを定義しているのかについては、詳しくは[コード](https://github.com/uhyo/emoji-world/blob/19c3d665a60f301d53c8071fd07e41b28f5696b6/site/elements.js#L87)を見てください。概要としては、ここでは単純に既存のHTML要素に対するエイリアスになるようにカスタム要素を定義しています。例えば、`e-🗣️`は`header`要素に相当します。`e-🗣️`要素は、そのshadowツリーに`<header><slot></slot></header>`という構造を持ちます。これは要するに`<e-🗣️></e-🗣️>`の中に入れられたものを全部`<header></header>`の中に突っ込んで表示するということです。これによって、`e-🗣️`を実質`header`のように扱うことができています。コードとしては以下のような感じになります。

```js
class CustomHeaderElement extends HTMLElement {
    constructor() {
        super();
        // shadow rootをこの要素に作成
        this.attachShadow({mode: 'open'});
        // shadow rootの中に置くためのhedaer要素を作成
        const innerElement = this.innerElement = document.createElement('header');

        // header要素の中にslot要素を設置
        const slot = document.createElement('slot');
        innerElement.appendChild(slot);
        // header要素をshadow root内に追加
        this.shadowRoot.appendChild(innerElement);
    }
}
customElements.define('e-🗣️', CustomHeaderElement);
```

ポイントは、`CustomHeaderElement`が`HTMLElement`を継承したクラスになっている点ですね。Custom Elementsはこのようにclass構文を用いて作ったクラスでないと受け付けられないのでした。

実際のコードは、カスタム要素に指定された属性を内部の要素に伝達するためのコードなどが追加されて多少複雑になっています（現在のところ、伝達したい属性の名前をベタ書きするという残念な構成なのですが）。

---

ところで、つい最近リリースされたFirefox 63がCustom Elementsに対応したという嬉しいニュースがありました。これで、主要ブラウザの中でまだ対応していないのはEdgeを残すのみとなりました。（実際のところは対応が出揃ってきているのは一部だけで、customized built-in elementsはまだChromeしか対応していませんが。）

とはいえ、対応していないブラウザがあるということで、このサイトではCustom Elements非対応ブラウザ向けの処理も書いてあります（[該当部分のコード](https://github.com/uhyo/emoji-world/blob/19c3d665a60f301d53c8071fd07e41b28f5696b6/site/elements.js#L37)）。

Custom Elementsに対するPolyfillとしてはPolymerが有名なように思いますが、今回は静的サイトであるのをいいことに、たいへん適当な実装になっています。それは、「ページが読みこまれたら`e-🗣️`などの要素を元の要素（`header`）で置換する」というものです。実はCustom Elementsに対応していないブラウザでも、`<e-🗣️>`の要素は「未知の要素」として木構造上に残ります。このスクリプトはそのことを利用しており、`<e-🗣️> ... </e-🗣️>`のような構造はスクリプトによって`<header> ... </header>`に置換されます。やり方の適当さはさておいても、この方法だと開発者ツールでページの構造を見たときに絵文字が消えているのが残念ですね、Custom Elementsが有効ならページの構造内に絵文字が残った状態になっています。

これを行う際には、CSSも修正する必要があります。CSSの修正も、やはり要素名を置換するだけという適当な実装です。CSSは次のように置換されます。

```css
body e-🗣️ e-1⃣ {
  margin: 1rem 0;
  font-size: 4rem;
  text-align: center;
}

/* ↓↓↓↓↓ */

body header h1 {
  margin: 1rem 0;
  font-size: 4rem;
  text-align: center;
}
```

それを行っているコードは[この辺](https://github.com/uhyo/emoji-world/blob/19c3d665a60f301d53c8071fd07e41b28f5696b6/site/elements.js#L61)です。コードを見ると分かりますが、[CSSStyleRule](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule)等の`selectorText`に対して文字列置換を行って書き換えるようになっています。このようにCSSの木構造に踏み込んで書き換えを行うコードを書くことはあまりなく、珍しいのではないかと思います。（それにしてはやっていることが適当ですが仕方ありません。）

# まとめ
今回作成した[😀🌏.uhyo.space](https://😀🌏.uhyo.space/)の中身の解説は以上です。特に、H2Oの設定とCustom Elementsの利用という2つのトピックに触れました。

後者についてはtemplateなどを併用せずに純粋なCustom Elementsのみを用いた例となっていますのでもしかしたら参考になるかもしれません。また、Polyfillを使わない（非常に適当な）未対応ブラウザ対応の例を示しました。

余談ですが、[😀🌏.uhyo.space](https://😀🌏.uhyo.space/)のコンテンツを1ページ作ったら狂気が尽きてしまいました。1ページだけだとウェブサイトとしては寂しいので、コンテンツを拡充してくれる方は常に募集中です。[GitHubリポジトリはこちらです](https://github.com/uhyo/emoji-world)。