JavaScript
SPA
webpack
babel
skatejs

Skate.jsのwebpack/babel利用時における各レンダー向けビルドまとめ

Skate.jsは、Web Componentsを薄くラッピングし、React風味で扱えるようなライブラリです。React風味とはいっても、JSXが欠かせないとかではなく、レンダリングは、ノーマル版のほかに、LitHTMLPreactReactから選択ができますし、加えて自分でカスタムレンダラーを作ることもできます。

軽量で、疎結合性が高いSkate.jsですが、すでにバージョンも5となり、元々多くなかったドキュメント群も古くなったりしている箇所があり、今からやる人はたぶん詰まると思います。そこで、各レンダラー向けのビルドまでをちょっとまとめてみました。違っている箇所やもっといい箇所があったらどんどんツッコミお願いします!

前提

Skate.jsはWebpackとかBabelとか使わなくていいはずなのですが、例えばちょこっとサンプルを試そうとして、HTMLに

<script src="https://unpkg.com/skatejs/dist/index-with-deps.min.js"></script>

こういうのをサンプルなどから見つけて挿入しようとすると、Cannot find module "/dist/index-with-deps.min.js" in package skatejs@5.1.1とか言われてお詰みにになってしまいます。

また、skatejs-web-componentsを使うらしいというドキュメントを見つけて、サイトへ飛ぶと、「DEPRECATED」とか書いてあって、「お、おう」となります。

運良く様々なブログなどからサンプルコードらしきものを見つけてWebpack + Babelで組むと、たいてい発生するのが「Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.」というエラーで、いろいろいじってもこれがやっぱり頻出して、やはりお詰みます。

このあたりで悩むのは、つらいので、それぞれのポイントみたいなのを書いていきます。

プロジェクトの構成について

まず、プロジェクトのディレクトリ構成として、

  • index.html
  • index.js // ここがエントリポイント
  • bundle.js // これ吐き出されたJavaScript

という感じでプロジェクト直下に各種ファイルがある簡易的な構成ということで進めます。

同一ディレクトリ内に、.babelrcやwebpack.config.js、package.jsonもいます。

まずは、webpack.config.js

LitHTMLPreactReact共通で、下記のようにします。

module.exports = {
    entry: ['babel-polyfill', './index.js'],
    output: {
        path : __dirname,
        filename: 'bundle.js'
    },
    resolve: {
        extensions: ['.js']
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            }
        ]
    }
};

続いて、共通部分として、index.htmlを作っておきましょう。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello Skate</title>
  <script type="text/javascript" src="./bundle.js"></script>
</head>
<body>
    <hello-my-component name="BOOB"><strong>Hello!!</strong></hello-my-component>
</body>
</html>

内容としては、hello-my-componentというカスタムタグを作るというものです。
これを、LitHTMLPreactReactそれぞれのやり方で書いてみます。

.babelrcですが、これはそれぞれ異なるので、ここからは別々に解説してきます。

標準構成の場合

標準構成の場合は、結論からいくと、.babelrcのみでOKです。

{
    "presets": ["es2015"]
}

index.jsは下記のようにします。

import 'skatejs-web-components';
import { props, withComponent } from 'skatejs';

class HelloMyComponent extends withComponent() {
  static get props() {
    return {
      name: props.string,
    };
  }

  render({ name }) {
    return `
    <span>${name}! : <slot></slot></span>
    `;
  }

}

customElements.define('hello-my-component', HelloMyComponent);

ポイントは、skatejs-web-componentsを最初にインポートしておくことで、これをしないといろいろと問題が発生します。DEPRECATEDなので、のちのちは外していくことになります。slotタグで、子タグが出力されます。

LitHTMLの場合

LitHTMLの場合は、標準構成の.babelrcと同じで大丈夫です。skate.jsのレンダー系は、これが一番易しいと思いました。

index.jsは、下記のような感じ。

import 'skatejs-web-components';
import { props, withComponent } from 'skatejs';

// lit-html
import withLitHtml from '@skatejs/renderer-lit-html';
import { html } from 'lit-html';

class HelloMyComponent extends withComponent(
  withLitHtml()
) {
  static get props() {
    return {
      name: props.string,
    };
  }

  render({ name }) {
    return html`
    <p>LitHtml's ${name}! : <slot></slot></p>
    `;
  }

}

customElements.define('hello-my-component', HelloMyComponent);

ポイントとして、withLitHtmlをかましている点、htmlでテンプレートスニペットを作成している点でしょうか。これもslotタグで、子タグが出力されます。割と素直です。

Preactの場合

Preact、Reactとの組み合わせはいくつかハマりポイントがありました。

.babelrcは下記のようにおさまりました。

{
    "presets": ["es2015", "preact"]
}

preactプリセットは、babel-preset-preactというものが存在しているので、こちらを使います。

index.jsは、こうなりました。

// @jsx h

import 'skatejs-web-components';
import { props, withComponent } from 'skatejs';

// preact
import withPreact from '@skatejs/renderer-preact';
import { h } from 'preact';

class HelloMyComponent extends withPreact(withComponent()) {
  static get props() {
    return {
      name: props.string,
    };
  }

  render({ name }) {
    return <p>Preact's {name}! : {this.props.children}</p>;
  }

}

customElements.define('hello-my-component', HelloMyComponent);

冒頭のjsxプラグマは、preactプリセットを最終的に使うことになったため不要かもしれません。.babelrcの設定は、いろいろな手法があり、pluginを使う方法などもありますが、いろいろ試して上記に落ち着きました。

Preact、React両方のハマりポイントがありまして、LitHTMLでは、withComponent(withLitHtml())と書く部分が、Preact、Reactでは、withPreact(withComponent())と入れ子が逆になる必要があるということです。

ちなみに、childrenを使わない場合は逆でもちゃんと動作するのですが、実はchildrenを使い始めるとうまくいきません。そもそもPreactやReactではslotタグが使えないので注意が必要です。

Reactの場合

.babelrc、Reactは、いつもの通りでOKです。

{
    "presets": ["es2015", "react"]
}

Reactは基本的にPreactと同等のやり方で動きます。なので、JavaScript側も、PreactがReactになるのみでOKです。

import 'skatejs-web-components';
import { props, withComponent } from 'skatejs';

// preact
import withReact from '@skatejs/renderer-react';
import React from 'react';

class HelloMyComponent extends withReact(withComponent()) {
  static get props() {
    return {
      name: props.string,
    };
  }

  render({ name }) {
    return <p>Reacts {name}! : {this.props.children}</p>;
  }

}

customElements.define('hello-my-component', HelloMyComponent);

ということで、ここまで無事に表示できれば、あとは、Skate.jsのライフサイクルなどを使って開発していけるはずです。結構スタート時に大変だったので誰かの役に立てばとまとめておきます。