こんにちは。
皆さんは静的サイトをコーディングする際にどんな環境をお使いでしょうか。
今回は、Astro、Nuxtjs、NextJsなどを使わずに、
なるべく純粋なHTMLで実装するために
Handlebarsを使ったコーディング環境を紹介します
Node | 18以上 |
---|---|
Vite | 4.4.0 |
Vite-plugin-handlebars | 1.6.0 |
まずコーディングのベースとして機能するには
サイトの実装時に毎回書くようなものをテンプレート化させることを目指します
タイポグラフィやプライマリーカラーなどはscssの変数を使います
これらに依存するレイアウトやコンポーネントはscssにて管理します
コンポーネントに関しては、複数サイトで使いまわせるようにしたいため、
Handlebarsで切り分けたコンポーネントのhtmlファイル内でCSSを記述したいです
そこはtailwindcss を使うことで解決できます
タイポグラフィや色はCSSの変数使えばさらに汎用性が高まりますが、そこまでやるならそれなりの規模かと思うのでUI Frameworkを検討しても良いと思います
なので、多少の汎用性は犠牲にしてラフに実装していきます
構文チェックはlinterに任せます
コミット単位でlint通すために導入します
公式のリンクをまとめます
- .husky
- src
- assets
- images
- js
- scss
- data
- pageData.json
- index.html
- other.html
- .markuplintrc.json
- .stylelintrc.json
- .eslintrc.json
- .gitignore
- tailwind.config.js
- postcss.config.cjs
- vite.config.js
- package.json
- package.lock.json
コンポーネントの活用をすると、そのままのhtmlではサーバーにアップしてもうまく表示されません
Viteを用いたビルドが必要です
今回はlinterやtailwindも入れているので、それらの設定ファイルも必要ではありますが、
他の設定ファイルは割愛します。
import { defineConfig } from 'vite';
//import設定を追記
import { resolve } from 'path';
/**
* HTMLの複数出力を自動化する
* ./src配下のファイル一式を取得
* 2階層目まで取得する
*/
import fs from 'fs';
// const fileNameList = fs.readdirSync(resolve(__dirname, './src/'));
const fileNameList = [];
const getFiles = (dir, fileList, nested) => {
// assets, components, data, publicは除外
if (/assets|components|data|public/i.test(dir)) return;
const files = fs.readdirSync(dir);
files.forEach(file => {
if (fs.statSync(dir + '/' + file).isDirectory()) {
getFiles(dir + '/' + file, fileList, nested ? nested + '/' + file : file);
}
else {
nested ? fileList.push(nested + '/' + file) : fileList.push(file);
}
}
);
};
getFiles(resolve(__dirname, './src/'), fileNameList, '');
//htmlファイルのみ抽出
const htmlFileList = fileNameList.filter(file => /.html$/.test(file));
//build.rollupOptions.inputに渡すオブジェクトを生成
const inputFiles = {};
for (let i = 0; i < htmlFileList.length; i++) {
const file = htmlFileList[i];
inputFiles[file.slice(0,-5)] = resolve(__dirname, './src/' + file );
}
/**
* ページ単位の基本情報
*/
//import設定を追記
import handlebars from 'vite-plugin-handlebars';
import pageDataJson from './src/data/pageData.json';
//HTML上で出し分けたい各ページごとの情報
const pageData = pageDataJson;
/**
* config情報
*/
export default defineConfig({
base: './', //ルートパスの設定。デフォルトは'/
root: './src', //開発ディレクトリ設定
plugins: [
handlebars({
//コンポーネントの格納ディレクトリを指定
partialDirectory: resolve(__dirname, './src/components'),
helpers: {
// 変数内のhtmlタグを描画する
html: (contents) => {
const str = contents
return str;
}
},
//各ページ情報の読み込み
context(pagePath) {
return pageData[pagePath];
},
}),
],
build: {
outDir: '../dist', //出力場所の指定
rollupOptions: { //ファイル出力設定
input: inputFiles,
output: {
assetFileNames: (assetInfo) => {
let extType = assetInfo.name.split('.')[1];
//Webフォントファイルの振り分け
if (/ttf|otf|eot|woff|woff2/i.test(extType)) {
extType = 'fonts';
}
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
return assets/images/[name][extname];
}
//ビルド時のCSS名を明記してコントロールする
if(extType === 'css') {
if(assetInfo.name === 'index.css') {
return assets/css/index-[hash].css;
}
return assets/css/[name].css;
}
return assets/${extType}/[name][extname];
},
chunkFileNames: 'assets/js/[name].js',
entryFileNames: 'assets/js/[name].js',
},
},
},
});
以下の記事が大変参考になりました
詳細はぜひ下記をご参照ください
参考 : 【詳細版】Viteでコーダーのコーディング環境(HTML(ejsライク:ハンドルバー化)・Sass・JS)を作る
markuplintは補足させていただきます
こちらもコンポーネントで切り分けていると、構文チェックがうまくできません
そこで、コンポーネントなどを全て処理した後で構文チェックをしてくれるようにparserを入れます
npm i -D @markuplint/mustache-parser
上記をインストールした上で、以下のように設定ファイルを記述してください。
{
"extends": [
"markuplint:recommended"
],
"parser": {
".mustache$|.hbs$|.html$": "@markuplint/mustache-parser"
},
"rules": {}
}
例、ボタン
- components
- baseButton.hmtl
<a
href="{{href}}" class="c-baseButton"{{#if id}} id={{id}}{{/if}} {{#if isDisabled}}disabled{{/if}}
>
{{text}}
</a>
使い方
{{> baseButton
href="URL"
text="ボタンのテキスト"
id="id"
isDisabled=false
}}
Jsonは下記ディレクトリに配置します
- src
- data
- pageData.json
{
"/index.html": {
"meta": {
"title": "タイトルが入ります",
"description": "ディスクリプションが入ります"
},
}
}
ページが多い場合はファイル分けても良いと思います
※応用例としては、ページのmeta情報です
ヘルパー関数を定義するとできます
ヘルパー関数はvite.config.jsファイルに記述します
参考文献をご参照ください
この方法だとaタグもJSON内部にかけます
応用例としては、
よくある質問のQ/AをJSONに書いたときの、
改行やフォームへのリンクです
export default defineConfig({
...
plugins: [
handlebars({
...
helpers: {
// 変数内のhtmlタグを描画する
html: (contents) => {
const str = contents
return str;
}
},
...
}),
],
...
{{#html 変数}}{{/html}}
※ローカルにおきますので
外部から悪意のあるコードを差し込まれる可能性などは考慮しません
いかがでしたか
制作の開発環境について、品質向上のヒントになっていると嬉しいです