Eleventy v2で、ページ内の一部要素をPartial Hydration(w/Preact + JSX)する
BEENOSの三上です。
今回は社内の新規サイト構築でEleventyの使用を検討している過程で、Preactで作ったcomponentのPartial Hydrationを試してみたので、その知見を共有させて頂きます。
なお、Preact componentのbuildにはRollupとBabelを用い、フロントエンドでのロードの制御には is-land を使用しています。
(bundle toolについては、昨今様々な選択肢がありますが、執筆者三上の学びを兼ねてRolllupを使っています。)
今回の検証の目的
弊社内で新規にサイト構築する見込みがあり、その際に何で作っていこうかというのが事の発端でした。
下記3点が、技術選定するにあたり意識していた判断軸です。
- 多言語化を行う
- サイト内コンテンツは基本静的なものだが、ほんの一部動的要素が見込まれる
- サイトの開発、運用にはエンジニアだけでなく、デザイナーさんも直接関われると良さそう
- なお、他サイトの構築などでEJSを用いており、デザイナーさんの学習コスト的にEJSは使い回せると嬉しい
上記の条件を満たすツールがないかの検証を進め、その過程で得られたEleventyに関する知見があまり日本語で共有されていなかったため、今回執筆に至った経緯です。
Eleventyとは?
EleventyはA simpler static site generator
です。(11ty/eleventyのREADMEより)
Eleventy自体の説明については、他に詳しい記事があるため、そちらに譲ります。
- Eleventy1.0という選択肢 - takanorip: https://zenn.dev/takanorip/articles/26754e75dc8753
- 静的サイトジェネレーターEleventy | 第1回 Eleventyとその特徴 - 中村 享介: https://www.codegrid.net/articles/2019-11ty-1/
因みに2023/02/08にEleventy v2がリリースされています。
ELEVENTY V2.0.0, THE STABLE RELEASE - Eleventy Blog
この記事では最新のv2で検証をしていきます。
他記事を参照される際はメジャーバージョンの違いに気をつけて頂けると良さそうです。
環境
モノ | version |
---|---|
Node.js | 18.14.1 (2023/02/19時点での最新のLTS) |
npm | 9.5.0 |
Eleventy | 2.0.0 |
Rollup | 3.17.1 |
11ty/is-land | 3.0.1 |
Initialize
まず適当なdirectoryを作ってnpmのinitializeをしておきます。
mkdir my-11ty-prj
cd my-11ty-prj
npm init -y
Eleventyをセットアップしていきます。
この後Eleventyの設定をいじるんですが、is-land
に関する設定も行うため、先にinstallしています。
npm i @11ty/eleventy @11ty/is-land
Eleventyに関するディレクトリ構成を少しばかりいじっていきます。
この辺りはお好みでいじってください。
module.exports = function (eleventyConfig) {
// なんらか最終成果物にコピーしていきたいリソースがある場合は、`addPassthroughCopy`に渡していきます。
// cf.: https://www.11ty.dev/docs/copy/
eleventyConfig.addPassthroughCopy({
// `is-land.js`を使いたいので、`node_modules`配下から引っ張ってきます。
'./node_modules/@11ty/is-land/is-land.js': 'assets/js/is-land.js',
});
return {
dir: {
input: './src/contents',
output: './dist',
},
};
};
上記.eleventy.js
内のdir.input
で指定したパスに、ページ描画の元となるテンプレートを配置します。
---
title: Eleventy experimental implementation w/Preact partial hydration.
---
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%- title %></title>
</head>
<body>
<h3>
<%- title %>
</h3>
<script type="module" src="/assets/js/is-land.js"></script>
</body>
</html>
動作確認をするためにpackage.json
のscriptsにEleventyのコマンドを追加して走らせてみます。
まずnpm scriptsとしてEleventyのコマンドを追加しましょう。
{
"name": "my-11ty-prj",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "dev:web": "eleventy --serve"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@11ty/eleventy": "^2.0.0",
"@11ty/is-land": "^3.0.1"
}
}
npm run dev:web
で起動してみます。
❯ npm run dev:web
> my-11ty-prj@1.0.0 dev:web
> eleventy --serve
[11ty] Writing ./dist/index.html from ./src/contents/index.ejs
[11ty] Copied 1 file / Wrote 1 file in 0.04 seconds (v2.0.0)
[11ty] Watching…
[11ty] Server at http://localhost:8080/
http://localhost:8080/
にアクセスして、下記のようなページが表示されていれば正しくページを構成できています。
8080
が既に使用されている場合は、8081
などのportでlistenする可能性があります。
EleventyがServer at ~~~
の用に表示したURLに対してアクセスしてください。
Rollupの導入
JSXを含むPreact componentのjsをtranspile&bundleするためにRollupを用います。
ついでに、RollupとEleventyのプロセスを同時に走らせるためにconcurrently
を使用するので、このタイミングで導入しておきます。
npm i rollup @rollup/plugin-commonjs @rollup/plugin-babel @babel/plugin-transform-react-jsx @babel/preset-react @rollup/plugin-node-resolve preact concurrently
Preactでちょっとした描画するcomponentを作っておきます。
import { h, render } from 'preact'
function HelloComponent () {
return (
<p>
<strong>
Hello from Preact!
</strong>
</p>
)
}
const Component = (el) => {
render(<HelloComponent />, el)
}
export default Component
Rollup
の設定を書いていきます。
import { dirname, resolve } from 'path';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import { nodeResolve } from '@rollup/plugin-node-resolve';
const extensions = ['.ts', '.tsx', '.json', '.js'];
const projectRoot = dirname(new URL(import.meta.url).pathname)
export default {
input: [
resolve(projectRoot, './src/assets/js/partials/preact-component.js')
],
output: {
entryFileNames: '[name].js',
dir: 'dist/assets/js/partials',
},
plugins: [
nodeResolve({
extensions: ['.js'],
}),
babel({
presets: ['@babel/preset-react'],
plugins: [
['@babel/plugin-transform-react-jsx', { "pragma":"h" }]
],
babelHelpers: 'bundled',
}),
commonjs(),
],
};
EleventyとRollupを同時に起動するためのnpm scriptsを追加しておきます。
...
"scripts": {
- "dev:web": "eleventy --serve"
+ "dev": "concurrently 'npm:dev:*'",
+ "dev:web": "eleventy --serve",
+ "dev:js": "rollup -c --watch"
}
...
npm run dev
を実行して動かしてみましょう。
❯ npm run dev
> my-11ty-prj@1.0.0 dev
> concurrently 'npm:dev:*'
[web]
[web] > my-11ty-prj@1.0.0 dev:web
[web] > eleventy --serve
[web]
[js]
[js] > my-11ty-prj@1.0.0 dev:js
[js] > rollup -c --watch
[js]
[js] rollup v3.17.2
[js] bundles [プロジェクトのパス]/src/assets/js/partials/preact-component.js → dist/assets/js/partials...
[web] [11ty] Writing ./dist/index.html from ./src/contents/index.ejs
[web] [11ty] Copied 1 file / Wrote 1 file in 0.04 seconds (v2.0.0)
[web] [11ty] Watching...
[web] [11ty] Server at http://localhost:8080/
[js] created dist/assets/js/partials in 192ms
エラーなく起動出来ているようです。
次はbuild出来たPreactのcomponentを描画していきます。
Preact Componentを表示するページを作成
dist/assets/js/partials/preact-component.js
をページで読み込んで表示してみます。
...
<body>
<h3>
<%- title %>
</h3>
+ <is-land on:visible autoinit="preact" import="/assets/js/partials/preact-component.js"></is-land>
+
<script type="module" src="/assets/js/is-land.js"></script>
</body>
...
下記のような表示になっていれば成功です。
is-land
によるlazy loadの挙動を確認
今回、is-land
を用いており、描画対象領域がviewport内にあるかどうかをIntersection Observerにより判定してjsのload&描画を行っています。
これが想定通り動作しているかも検証してみましょう。
まず、ページの初期表示時はPreact componentの描画領域が画面外に出るように、div要素を足して適当な高さを設定しましょう。
...
<body>
<h3>
<%- title %>
</h3>
+ <div style="height: 500px;"></div>
<is-land on:visible autoinit="preact" import="/assets/js/partials/preact-component.js"></is-land>
<script type="module" src="/assets/js/is-land.js"></script>
</body>
...
ファーストビューではPreact componentが表示されないような形になっていればOKです。
このファーストビューから、スクロールしてPreact componentを表示してみた例が↓のgifです。
スクロールして描画領域が視覚的viewport内に入ったタイミングで、preact-component.js
ファイルがロードされていることがわかります🎉
まとめ
お疲れさまです。
ここまでで、Eleventyを使って生成している静的ページ内の一部を、Preactを使って動的なComponentとして描画することが出来ました。
あくまで「Preactから描画できているよね」という単純な検証をしただけですが、似たようなことをしようと考えている方の参考になれば幸いです。
Wanted!!
BEENOSグループでは一緒に働いて頂けるエンジニアを強く求めております!
少しでも気になった方は、社内の様子や大事にしていることなどをThe BEENOSにて発信しておりますので、是非ご覧ください。
とても気になった方はこちらで求人も公開しておりますので、お気軽にご応募ください!
「自分に該当する職種がないな...?」と思った方はオープンポジションとしてご応募頂けると大変嬉しいです。🙌
世界で戦えるサービスを創っていきたい方、是非ご連絡ください!よろしくお願い致します!!
Discussion