Produced by Fourier

HandlebarsからWordPressの固定ページへ変換

Sena Sena カレンダーアイコン 2022.08.05

概要

弊社では静的コーディングの際に Handlebars を使うケースが増えていますが、 WordPress 固定ページにインサートをするのが非常に大変です。

なので WordPress の固定ページを自動的に変換するコードを作りました。

一度 Handlebars から JSON に変換することによって、 PHP で簡単に読む事が出来るのでそのようにしました。

flowchart LR
    A[Handlebars] -->|Node.jsで処理| B[JSON]
    B -->|PHPで処理| C[WordPress]

1. HandlebarsをJSONに変換

フォルダ構成例

resources/handlebars/Handlebars コードを入れます。

dist/ に出力された JSON が入ります。

.
├── resources
│   └── handlebars
│       └── index.html
└── dist
    └── index.json

中間データとしてNode.jsでも PHP でも使える JSON に変換します。

'use strict';

const fs         = require('fs');
const handlebars = require('handlebars');
const path       = require('path');
const glob       = require('glob');
const {JSDOM}    = require('jsdom');
const prettier   = require('prettier');
const makeDir    = require('make-dir');

const srcDir  = `${process.cwd()}/resources/handlebars`;
const distDir = `${process.cwd()}/dist`;

/**
 * @param {string} fileName
 * @param {string} srcDir
 * @param {string} distDir
 * @returns {Promise<void>}
 */
const convert = async (fileName, srcDir, distDir) => {
    try {
        const compile = await handlebars.compile(fs.readFileSync(path.resolve(srcDir, fileName)).toString());
        {
            const {document} = (new JSDOM(compile({}))).window;
            const output     = {
                title: document.querySelector('title').innerHTML,
                html : prettier.format(document.querySelector('body').innerHTML, {parser: 'html'})
            };
            const distPath   = path.resolve(distDir, fileName);
            makeDir(path.dirname(distPath)).then(() => {
                const htmlPath = path.format({
                                                 dir : path.dirname(distPath),
                                                 name: path.basename(distPath, '.hbs'),
                                                 ext : '.json',
                                             });
                fs.writeFileSync(htmlPath, JSON.stringify(output));
            });
        }
    } catch (e) {
        console.error(e);
    }
};

glob(
        '**/*.hbs',
        {
            cwd   : srcDir,
            ignore: '**/_*.hbs',
        },
        async (er, files) => {
            for (const fileName of files) {
                convert(fileName, srcDir, distDir);
            }
        }
);

2. JSONをWordPressの固定ページに入れる

JSONWordPress の固定ページに入れます。

この時ページの親子関係も保存されます。

フォルダ構成例

PROHECT/WordPress インストール先です。

dist/Handlebars から変換された JSON が入ります。

.
├── PROJECT(WordPressインストール先)
│       ├── wp-blog-header.php
│   └── ・・・
├── dist
│   └── index.json
└── 下記PHPスクリプトファイル
<?phpconst DOING_AJAX = true;
$_SERVER['REQUEST_METHOD'] = 'GET';
require __DIR__ . '/PROJECT/wp-blog-header.php';
remove_filter('content_save_pre', 'wp_filter_post_kses'); // timeタグなどが削除されるのでフィルターを外す

function _scanDir($dir): array
{
    $list = [];
    foreach (glob($dir . '*/', GLOB_ONLYDIR) as $child) {
        if ($tmp = _scanDir($child)) {
            $list = array_merge($list, $tmp);
        }
    }
    foreach (glob($dir . '{*.json}', GLOB_BRACE) as $file) {
        $list[] = $file;
    }
    return $list;
}

function jsonToPage(array $json_files, string $root_dir)
{
    // 親ページを設定するため、親ページをなるべく前にソート
    usort($json_files, function ($a, $b): int {
        $a_c = substr_count($a, '/');
        $b_c = substr_count($b, '/');
        if ($a_c === $b_c) {
            if (substr($a, -10) === 'index.json') return -1;
            elseif (substr($b, -10) === 'index.json') return 1;
            return 0;
        }
        return $a_c <=> $b_c;
    });
    $parent_dirs = array_map(fn($dir) => str_replace('.json', '', str_replace('index.json', '', str_replace($root_dir, '', $dir))), $json_files);

    $set = []; // 親ページのコレクション
    foreach ($json_files as $key => $dir) {
        $json_str   = file_get_contents($dir);
        $json       = json_decode($json_str);
        $name       = $parent_dirs[$key];
        $name_array = array_filter(explode('/', $name), fn($name) => $name !== '');
        if ($name !== '/' && substr_count($name, '/') >= 2) {
            // 第二階層以上
            $page_name    = $name_array[array_key_last($name_array)];                       // ページ名(ex: c)
            $current_name = implode('/', $name_array);                                      // 親の名前を含めた現在のページの名前(ex: a/b/c)
            array_pop($name_array);                                                         // 現在のページを除外
            $parent_name = implode('/', $name_array);                                       // 親のページの名前(ex: a/b)
              $parent_id   = (array_key_exists($parent_name, $set)) ? $set[$parent_name] : 0; // 0の場合は、親無し(WordPressの仕様)
            // 親ページ
            $id          = wp_insert_post([
                                             'post_title'   => $json->title,
                                             'post_name'    => $page_name,
                                             'post_content' => $json->html,
                                             'post_type'    => 'page',
                                             'post_status'  => 'publish',
                                             'post_parent'  => $parent_id,
                                         ]);
                        if (str_ends_with($name, '/')) {
                $set[$current_name] = $id;
            }
        } else {
            // 第一階層
            $id = wp_insert_post([
                                     'post_title'   => $json->title,
                                     'post_name'    => str_replace('/', '', $parent_dirs[$key]),
                                     'post_content' => $json->html,
                                     'post_type'    => 'page',
                                     'post_status'  => 'publish',
                                 ]);
            update_option('page_on_front', $id); // start page(ホームページ)に設定
        }
    }
}

$query = new WP_Query([
                          'post_type'      => 'page',
                          'posts_per_page' => -1,
                      ]);
while ($query->have_posts()) {
    $query->the_post();
    wp_delete_post($query->post->ID, true);
}

jsonToPage(_scanDir('dist'), 'dist');

これを実行すると、 JSON を読み込んで WordPress の中に自動的に固定ページが入っていきます。

実行例

Handlebars

<!doctype html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>TOP</title>
</head>
<body>
	<main>TOP</main>
</body>
</html>

出力されるJSON

{"title":"TOP","html":"<main>TOP</main>\n"}so

その後、出力された JSONWordPress の固定ページに入れると管理画面では以下のようになります。

最後に

新規開発時に WordPress の管理画面上でコーディングをする事は非効率な事が多く、特にコーダーへ WordPress の説明や環境準備をする必要があり大変でした。

そのため WordPress と静的コーディングを分けていますが、今まではHTMLコーディングを WordPress へ取り込む際には管理画面に入り手動でやっていました。

ですがこのコードを開発・実行することにより、簡単に WordPress へ取り込む事が出来ました。

コードについては荒削りな所がありますが、是非使ってみてください。

Sena

Sena slash forward icon Engineer

生涯に亘り技術を極めていきたい。

関連記事