📦

React(JSX)で書けるコーディング用SSG - minista v0

2021/06/13に公開
7

はじめに

こんにちは!クラクです。コーディングの仕事用にminista(ミニスタ)という、React(JSX)で書ける静的サイトジェネレーターを作りましたので、よかったら試してみてください!

https://github.com/qrac/minista
https://www.npmjs.com/package/minista

ministaを作った経緯

SaaSの仕事では綺麗なHTMLを納品することが多いです。開発者が別途アサインされているので。ただ、EJS等はもう辛い。Next.jsやGatsby.jsで書くJSXの快適さに慣れすぎたのでしょう。

とはいえ、Reactフレームワークの出力するコードはSPAっぽい動作を前提としているため、納品用としては微妙です。機械的な命名でコード分割されるし、そもそもReact自体が不要...。

色々と試していたときにotsukayuhiさんHishoさんの記事を見かけ、renderToStaticMarkup() を使った構成が良さそうだと思いました!応用で機能をパッケージ化したものがministaです。

ministaの使い方

セットアップ

コーディング用のプロジェクトを作ったら ministadevDependencies に加えます。

$ npm install --save-dev minista react react-dom

package.json にnpm scriptsを追加しておきます。minista または minista dev でライブリロードサーバーを立ち上げてコーディングできます。minista build で納品用データを生成。

package.json
{
  "scripts": {
    "dev": "minista",
    "build": "minista build"
  }
}

必須要素 src/assets/index.js を作成して npm run build、空の dist/assets/scripts.js が生成されたらセットアップは成功です。

$ mkdir -p src/assets && touch src/assets/index.js
$ npm run build

HTML

src/pages/**/*.js がHTMLになります。試しに src/pages/index.js を作成してみましょう。metaタグ用に react-helmet、コメントタグ用に専用のコンポーネントが使えます。全体をministaの render() で囲むのを忘れずに。

$ mkdir -p src/pages && touch src/pages/index.js
src/pages/index.js
import React from "react"
import { Helmet } from "react-helmet"
import { render, Comment } from "minista"

const Home = () => {
  return render(
    <main>
      <Helmet>
        <title>HTML coding with minista</title>
      </Helmet>
      <Comment text="Comment Test" />
      <h1>Hello</h1>
    </main>
  )
}

export default Home

npm run build で生成。dist/assets/scripts.js も読み込まれています。

dist/index.html
<!doctype html>
<html lang="ja">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>HTML coding with minista</title>
  <script defer="defer" src="/assets/scripts.js"></script>
</head>

<body>
  <main>
    <!-- Comment Test -->
    <h1>Hello</h1>
  </main>
</body>

</html>

HTML Components

コンポーネントを分割して管理できます。React(JSX)なので children が使えて便利。以下は共通レイアウトをコンポーネント化した例です。

src/components/app-layout.js
import React from "react"

const AppLayout = ({ children }) => {
  return <main>{children}</main>
}

export default AppLayout

CSS

CSSは src/assets/scripts.js からインポートします。

$ mkdir -p src/assets/css && touch src/assets/css/index.css
src/assets/css/index.css
h1 {
  color: red;
}
src/assets/scripts.js
import "./css/index.css"

npm run build でminifyされた dist/assets/styles.css が追加で生成。HTMLファイルにはリンクが自動的に付与されるようになります。

dist/assets/styles.css
h1{color:red}
dist/index.html
+  <link href="/assets/styles.css" rel="stylesheet">

JavaScript

JavaScriptも src/assets/scripts.js を起点として書きます。

$ mkdir -p src/assets/js && touch src/assets/js/hello.js
src/assets/js/hello.js
console.log("Hello JavaScript")
src/assets/scripts.js
import "./js/hello"

npm run build を実行すると dist/assets/scripts.js にwebpack/Babelを通しつつminifyしたJavaScriptが出力されます。

dist/assets/scripts.js
+  (()=>{var e={27:()=>{console.log("Hello JavaScript")}},r={};function o(t){var a=r[t];if(void 0!==a)return a.exports;var n=r[t]={exports:{}};return e[t](n,n.exports,o),n.exports}o.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return o.d(r,{a:r}),r},o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),(()=>{"use strict";o(27)})()})();

Other

画像などの処理はministaで行いません。その代わり、public ディレクトリ内のファイルをそのまま dist にコピーする機能を使います。

public
└── favicon.png
dist/index.html
+  <link rel="icon" href="/favicon.png" />

ministaの全体図

最終的にプロジェクトの構成を見ると、以下のようになります。srcpublic はNext.js風で、dist の中身はgulpやwebpackで納品していたものに近い形です。

.
├── dist
│   ├── assets
│   │   ├── images
│   │   │   └── demo-icon.png
│   │   ├── scripts.js
│   │   └── styles.css
│   ├── favicon.png
│   └── index.html
├── package-lock.json
├── package.json
├── public
│   ├── assets
│   │   └── images
│   │       └── demo-icon.png
│   └── favicon.png
└── src
    ├── assets
    │   ├── css
    │   │   └── index.css
    │   ├── index.js
    │   └── js
    │       └── hello.js
    ├── components
    │   └── app-layout.js
    └── pages
        └── index.js

上手くいかない点があった場合は、サンプルリポジトリを参考にしてみてください。

https://github.com/qrac/html-coding-with-minista

最後に

なんとか、React(JSX)で快適に書きつつ納品向きなコードを生成するSSGができました!

動かすとわかると思いますが、内部的にはwebpackメインです。ただ、ユーザー設定をマージさせる方法がわからず、結果的に全部入りのゼロコンフィグSSGとなってしまいました...。もし詳しい方がいれば、実装方法を共有いただけるとありがたいです。

余談ですが、以前にはHugoで近しい構成を作ったりもしました。ビルドが早く省エネなものの、テンプレ構造が融通効かないんですよねー。後発のJSバンドラーやDenoで新たな構成も考えられそうですが、現状はwebpackかなーというところです。

Discussion

どうけどうけ

webpack configの合成であれば素直にwebpack-merge でいいような気がします。
プレーンオブジェクトなので上書きしていいものだけ抜き出してマージする感じ。
引数で渡したファイルパスなどからFSで取得とかは別途必要と思いますが。
https://qiita.com/kmatae/items/fa99c131756802a63852

クラククラク

おー!ありがとうございます!!
便利なライブラリがあるんですね。見てみます!

クラククラク

教えていただいたライブラリでうまくいったので、v0.8に導入してみました!
ローカルに webpack.config.js があればマージされます。

dec_shibakendec_shibaken

こちらってcharsetって変更可能なんですか?

クラククラク

いいえ、v2.7.4時点では実装していません。必要な場面がありますか?

dec_shibakendec_shibaken

あーそうなんですね、、shift-jisで大量のページを製造しないといけない案件があるんですよね。
現状だと、rect-helmetでhead内の情報をいじれるかなーとおもっていたのですが、無理でした

dec_shibakendec_shibaken

回答ありがとうございました。

めちゃくちゃ便利な気がするので、アップデート期待しております。