テンプレートエンジンに React を使いつつ、きれいな HTML を生成したいんじゃ!!
イントロダクション
きれいな HTML で納品してほしいという案件、まだまだあるかと思います。
HTML を生成するツールとして選択肢に出てくるのは、 EJS / Pug / Nunjucks といった JavaScript のテンプレートエンジンだと思いますが、個人的には React を知ってしまってから JSX 以外でマークアップをすることがとても億劫になってしまっています。
HTML はロジックが紛れ込むことを考慮されてない言語だと思うので、 HTML をベースにしたテンプレートは使いづらいです(個人の感想です)。逆に JSX は JavaScript の中に HTML があるので、 HTML の分割のしやすさも相まってロジックの中に紛れ込ませやすく、ほしい要素の塊をコンポーネントとして切り出して使う、コンポーネント指向にとても向いています。
テンプレートエンジンに React を使いたいんじゃ!!
Next.js や Gatsby.js 等の静的サイトジェネレーターを使えば、レンダリングされた HTML を出力することはできるのですが、人が触れるようなきれいな HTML にはなりません。
なので、きれいな HTML を生成できる開発環境を作ってみました。
チュートリアル
renderToStaticMarkup() で React を HTML 文字列に変換
ReactDomServer の renderToStaticMarkup()
を使うと、 React 要素を初期状態の HTML 文字列へ変換することができます。
以下のように、 body 要素の中で <App />
を HTML 文字列に変換すれば、いい感じの HTML が文字列となって export default
されます。
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
const App = () => <div>Use React !!</div>;
export default () => `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>タイトル</title>
</head>
<body>
${renderToStaticMarkup(<App />)}
</body>
</html>
`;
次に、こいつを HtmlWebpackPlugin で HTML ファイルに変換します。
HtmlWebpackPlugin で HTML ファイルに変換
適当な ディレクトリを作り、 webpack と HtmlWebpackPlugin 、そして React をビルドするためのパッケージ一式を npm install しましょう。
$ mkdir react-to-html
$ cd react-to-html
$ npm init -y
$ npm i -D webpack webpack-cli html-webpack-plugin react react-dom babel-loader @babel/preset-react @babel/core
webpack.config.js
を作成し、 HtmlWebpackPlugin と bable-loader の設定をします。
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: "My App",
template: "src/index.jsx",
}),
],
module: {
rules: [
{
test: /\.js(|x)$/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/react"],
},
},
],
},
],
},
};
さきほどの HTML 文字列を作る JavaScript は、 src/index.jsx
で保存します。
HtmlWebpackPlugin の template
に src/index.jsx
を指定すれば、 HTML ファイルとして出力してくれます。
また、 title: "My App"
でタイトルを指定している部分は引数で受け取ることができます。
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
const App = () => <div>Use React !!</div>;
export default ({ htmlWebpackPlugin }) => `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${htmlWebpackPlugin.options.title}</title>
</head>
<body>
${renderToStaticMarkup(<App />)}
</body>
</html>
`;
エントリーポイントとして src/index.js
を空で作っておきます。あとは、以下のコマンドで src/index.jsx
が HTML 化した dist/index.html
として出力されます。
$ npx webpack
Prettier で HTML をきれいにする
prettier で HTML をきれいにしてあげましょう。
$ npm i -D prettier
npm scripts に prettier で dist/index.html
が整形されるように設定します。
{
"scripts": {
"build": "webpack && prettier --write dist/index.html"
}
}
$ npm run build
きれいな HTML になりましたね!
あとは、よしなに @babel/preset-env
なり css-loader
なりを入れて、いい感じに開発してください!
参考: https://dev.to/jantimon/html-webpack-plugin-4-has-been-released-125d
Discussion
とても参考になる記事、ありがとうございます。
同じように構築させていただきましたが、以下のエラーが発生します↓
ReferenceError: TextEncoder is not defined
解決策、わかりますでしょうか?
node v 16.15.0 です。
同じ問題にはまりました。
多分React v18系を使うと出る問題だと思います。
react と react-dom 16.12.0 にしたらいけました。
また下の方のコメントに
とあるのですが具体的にwebpack.cofig.jsにどの様に設定したら解決するかわかりません。
暫定的ですが解決策を発見しました!
といっても他の方が見つけたのですが、以下のissuesに載っていました。
html-webpack-pluginのindex.jsの130行目あたりのvm.createContext()をに TextEncoder: TextEncoder,TextDecoder: TextDecoderを追加する形です。
こちらはあくまで暫定的な方法ですがとりあえずこれで動きました。
すみません、ものすごい亀レスになってしまいました。。。。
該当のエラーの調査はできていないのですが、本質的には「renderToStaticMarkup() で React を HTML 文字列に変換することができる」という部分なので、あとの細かい部分は適にカスタマイズしていただければと思います。
(エラー自体は余力あるときに確認してみます)