【WordPress】オリジナルブロックの開発環境構築を解説【Gutenberg】

今回は、オリジナルブロックの開発環境構築方法を解説します。

公式とはやり方は異なりますが、今回紹介するやり方の方が楽に開発できるかと思います。

オリジナルのブロックはテーマで実装するよりプラグインを作る方がおすすめなので、プラグインを作る流れで説明します。

ブロック開発環境構築

ブロックの開発には、Reactを使う方法と使わない方法がありますが、Reactの方が便利なのでReactで開発します。

公式のスクリプトを使うと導入は楽ですが、複数のブロックを作成する場合は若干不便なので、独自の方法で進めます。

WordPressの開発環境を作成

WordPressの開発環境は「Local by flywheel」が便利なので、開発環境がない方はそちらを使ってください。

デバッグ

PHPのエラー確認をするために、「Query Monitor」というプラグインをインストールしておきましょう。

エラーの箇所やPHPの処理にかかった時間を把握できます。

Node.jsのインストール

Node.jsは直接インストールするのではなく、nodenvのようなバージョン管理ツールを使うと便利です。

nodenvがインストールできたら、Node.jsをインストールしてください。

今回はNode.jsのバージョン20.10.0で進めます。

フォルダを作成

今回はプラグインを作る方法で解説するので、

wp-content/pluginsに「my-blocks」フォルダを作成します。

Local by flywheelを使った場合は以下のディレクトリにフォルダを作成してください。

package.jsonを作成

以下のコマンドを実行してpackage.jsonを作成します。

npm init

質問が出てきますが、全てそのままEnterでOKです。

パッケージをインストール

ターミナルで「my-blocks」ディレクトリまで移動します。

VS Codeを使っている場合は、「my-blocks」フォルダをVS Codeで開いて、「ターミナル」の「新しいターミナル」でターミナルを起動してください。

以下のコマンドを打ってwebpackをインストールします。

npm install webpack webpack-cli --save-dev

上記コマンドは以下のように省略することも可能です。

npm i webpack webpack-cli -D

webpackをインストールしたら、webpack.config.jsファイルを作成します。

webpack.config.js
module.exports = (env, argv) => {
    const config = {
        entry: {
            editor: './src/editor.js',
            script: './src/script.js',
        },
        output: {
            filename: '[name].js',
            publicPath: '',
        },
    }
    return config;
}

これは後ほど解説します。

Reactもインストールします。

npm i react react-dom -D

古いブラウザに対応させるために、babel関係のパッケージもインストールします。

npm i -D @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react babel-loader

ブロック開発用のパッケージもインストールします。

npm i -D @wordpress/blob @wordpress/block-editor @wordpress/blocks @wordpress/browserslist-config @wordpress/components @wordpress/compose @wordpress/core-data @wordpress/data @wordpress/edit-post @wordpress/element @wordpress/hooks @wordpress/i18n @wordpress/icons @wordpress/keycodes @wordpress/plugins @wordpress/primitives @wordpress/rich-text @wordpress/server-side-render @wordpress/url

その他、lodashやjQueryもインストールしておきます。

npm i jquery lodash -D

実際に開発してみよう

ファイルを作成

必要なパッケージをインストールしたら、実際にファイルを作成してみましょう。

plugin.phpを作成し、以下のように記述します。

plugin.php
<?php
/**
 * Plugin Name: My Blocks
 * Description: Original Block Plugin
 * Version: 1.0.0
 * Author: SHIROMA
 * Author URI: https://retval.jp/
 * Requires PHP: 7.4
 * Text Domain: my-blocks
 * License: GNU General Public License v2 or later
 * License URI: http://www.gnu.org/licenses/gpl-2.0.html
 */

JavaScriptの読み込みと、ブロックに

コメントアウトでプラグインの情報を記述しているので、管理画面に表示されるようになります。

表示されたら「My Blocks」を有効化してください。

次に、package.jsonのscriptsの箇所に以下のコードを記述します。

package.json
  "scripts": {
    "start": "webpack --mode=development --watch"
  },

そして、webpack.config.jsは以下のように記述します。

webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = (env, argv) => {

    const isDevelopment = () => {
        return argv.mode === 'development';
    }
    const config = {
        entry: {
            editor: './src/editor.js',
            script: './src/script.js',
        },
        output: {
            filename: '[name].js',
            publicPath: '',
        },
        optimization: {
            minimizer: [
                new TerserPlugin(),
            ]
        },
        devtool: isDevelopment() ? 'cheap-module-source-map' : 'source-map',
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                plugins: ["@babel/plugin-proposal-class-properties"],
                                presets: [
                                    '@babel/preset-env',
                                    [
                                        '@babel/preset-react',
                                        {
                                            "pragma": "wp.element.createElement",
                                            "pragmaFrag": "wp.element.Fragment",
                                            "development": isDevelopment()
                                        }
                                    ]
                                ]
                            }
                        }
                    ]
                }
            ]
        },
        externals: {
            jquery: "jQuery",
            lodash: "lodash",
            "@wordpress/blocks": ["wp", "blocks"],
            "@wordpress/i18n": ["wp", "i18n"],
            "@wordpress/block-editor": ["wp", "blockEditor"],
            "@wordpress/components": ["wp", "components"],
            "@wordpress/element": ["wp", "element"],
            "@wordpress/blob": ["wp", "blob"],
            "@wordpress/data": ["wp", "data"],
            "@wordpress/server-side-render": ["wp", "serverSideRender"],
            "@wordpress/compose": ["wp", "compose"],
            "@wordpress/url": ["wp", "url"],
            "@wordpress/hooks": ["wp", "hooks"],
            "@wordpress/core-data": ["wp", "coreData"],
            "@wordpress/edit-post": ["wp", "editPost"],
            "@wordpress/plugins": ["wp", "plugins"],
            "@wordpress/keycodes": ["wp", "keycodes"],
            "@wordpress/primitives": ["wp", "primitives"],
            "@wordpress/rich-text": ["wp", "richText"],
        }
    }
    return config;
}

WordPress自体に入っているJavaScriptは、externalsに入れて除外しておきます。

srcフォルダを作成して、その中にscript.jseditor.jsを作成します。

そして、srcフォルダにblocksフォルダを作成し、その中にtest-blockフォルダを作成します。

test-blockフォルダにindex.jsを作成して、以下のように書いてみましょう。

src/blocks/test-block/index.js
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';

registerBlockType('my-blocks/test-block', {
    title: __('test block', 'my-blocks'),
    icon: 'welcome-write-blog',
    category: 'text',
    edit: () => {
        return (
            <p className="my-block-test-msg">TEST</p>
        )
    },
    save: () => {
        return (
            <p className="my-block-test-msg">TEST</p>
        )
    }
})

iconはWordPressのDashiconsの中から指定できます。または、svgを直接記述すること可能です。

editには、エディター側の処理。saveには保存されるHTMLに関する処理を記述します。

test-blockを、editor.jsで読み込みます。

src/editor.js
import "./blocks/test-block";

ここまでできたら、以下のコマンドを実行してみましょう。

npm start

これでdistフォルダにJavaScriptファイルが生成されます。

plugin.phpに以下のコードを追加して、生成されたJavaScriptを読み込みます。

plugin.php
define( 'MYB_VERSION', '1.0.4' );

function myb_blocks_register() {
    wp_register_script(
        'my-blocks-editor-script',
        plugins_url( 'dist/editor.js', __FILE__ ),
        array(
            'lodash',
            'wp-blocks',
            'wp-i18n',
            'wp-element',
            'wp-block-editor',
            'wp-components',
            'wp-element',
            'wp-blob',
            'wp-data',
            'wp-hooks',
            'wp-compose',
            'wp-plugins',
            'wp-primitives',
            'wp-rich-text',
            'wp-server-side-render',
        ),
        MYB_VERSION
    );

    wp_register_script(
        'my-blocks-script',
        plugins_url( 'dist/script.js', __FILE__ ),
        array( 'jquery' ),
        MYB_VERSION,
        true
    );

    register_block_type(
        'my-blocks/test-block',
        array(
            'editor_script' => 'my-blocks-editor-script',
            'script'        => 'my-blocks-script',
        ),
    );
}
add_action( 'init', 'myb_blocks_register' );

ここまでできたら、投稿編集画面を開いてブロックが追加されたか確認してみてください。

無事追加されました。

CSS(SCSS)の読み込み

次に、SCSSが使えるようにしていきます。

SCSS用のパッケージをいくつかインストールします。

npm i -D autoprefixer browserslist css-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin postcss postcss-loader sass sass-loader style-loader

ここでもしエラーが出たら、以下のように--forceオプションを付与してください。

npm i -D autoprefixer browserslist css-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin postcss postcss-loader sass sass-loader style-loader --force

webpack.config.jsを以下のように書き換えます。

webpack.config.js
const autoprefixer = require('autoprefixer');
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = (env, argv) => {
    function isDevelopment() {
        return argv.mode === 'development';
    }
    var config = {
        entry: {
            editor: './src/editor.js',
            script: './src/script.js',
        },
        output: {
            filename: '[name].js',
            publicPath: '',
        },
        optimization: {
            minimizer: [
                new TerserPlugin(),
                new OptimizeCSSAssetsPlugin()
            ]
        },
        plugins: [
            new MiniCSSExtractPlugin({
                chunkFilename: "[id].css",
                filename: chunkData => {
                    return chunkData.chunk.name === 'script' ? 'style.css' : '[name].css'
                }
            }),
        ],
        devtool: isDevelopment() ? 'cheap-module-source-map' : 'source-map',
        stats: {
            children: true,
        },
        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                plugins: ["@babel/plugin-proposal-class-properties"],
                                presets: [
                                    '@babel/preset-env',
                                    [
                                        '@babel/preset-react',
                                        {
                                            "pragma": "wp.element.createElement",
                                            "pragmaFrag": "wp.element.Fragment",
                                            "development": isDevelopment()
                                        }
                                    ]
                                ]
                            }
                        }
                    ]
                },
                {
                    test: /\.(sa|sc|c)ss$/,
                    use: [
                        MiniCSSExtractPlugin.loader,
                        'css-loader',
                        {
                            loader: 'postcss-loader',
                            options: {
                                postcssOptions: {
                                    plugins: [
                                        autoprefixer()
                                    ]
                                }
                            }
                        },
                        'sass-loader'
                    ]
                }
            ]
        },
        externals: {
            jquery: "jQuery",
            lodash: "lodash",
            "@wordpress/blocks": ["wp", "blocks"],
            "@wordpress/i18n": ["wp", "i18n"],
            "@wordpress/block-editor": ["wp", "blockEditor"],
            "@wordpress/components": ["wp", "components"],
            "@wordpress/element": ["wp", "element"],
            "@wordpress/blob": ["wp", "blob"],
            "@wordpress/data": ["wp", "data"],
            "@wordpress/server-side-render": ["wp", "serverSideRender"],
            "@wordpress/compose": ["wp", "compose"],
            "@wordpress/url": ["wp", "url"],
            "@wordpress/hooks": ["wp", "hooks"],
            "@wordpress/core-data": ["wp", "coreData"],
            "@wordpress/edit-post": ["wp", "editPost"],
            "@wordpress/plugins": ["wp", "plugins"],
            "@wordpress/keycodes": ["wp", "keycodes"],
            "@wordpress/primitives": ["wp", "primitives"],
            "@wordpress/rich-text": ["wp", "richText"],
        }
    };
    return config;
}

package.jsonに、サポートしたいブラウザのバージョンを指定します。

package.json
  "browserslist": [
    "extends @wordpress/browserslist-config"
  ],

先ほどインストールした「@wordpress/browserslist-config」を使えば、WordPressが推奨しているサポートリストを自動で読み込んでくれます。

src/blocks/test-blockに「style.scss」を追加します。

src/blocks/test-block/style.scss
.my-block-test-msg {
    color: #c61515;
}

次に、script.jsを作成してstyle.scssを読み込みます。

src/blocks/text-block/script.js
import './style.scss';

src直下のscript.jsで、このJSファイルを読み込みます。

src/script.js
import "./blocks/test-block/script";

「Control + C」(Windows・Mac共通)で一度監視状態をストップして、再度npm startをします。

npm start

style.scssは実際に表示される部分とエディター側両方のCSSで、エディターだけで読み込むCSSも作成します。

style.editor.scssの名前でファイルを作成します。

src/blocks/test-block/style.editor.scss
.my-block-test-msg {
    color: #1237b0;
}

こちらはindex.jsで読み込みます。

src/blocks/test-block/index.js
import './style.editor.scss';
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';

registerBlockType('my-blocks/test-block', {
    title: __('test block', 'my-blocks'),
    icon: 'welcome-write-blog',
    category: 'text',
    edit: () => {
        return (
            <p className="my-block-test-msg">TEST</p>
        )
    },
    save: () => {
        return (
            <p className="my-block-test-msg">TEST</p>
        )
    }
})

先頭に一行追加しました。

これでdistフォルダにCSSが書き出されるので、PHPで読み込みます。

plugin.php
// 省略
function myb_blocks_register() {
    wp_register_script(
        'my-blocks-editor-script',
        plugins_url( 'dist/editor.js', __FILE__ ),
        array(
            'lodash',
            'wp-blocks',
            'wp-i18n',
            'wp-element',
            'wp-block-editor',
            'wp-components',
            'wp-element',
            'wp-blob',
            'wp-data',
            'wp-hooks',
            'wp-compose',
            'wp-plugins',
            'wp-primitives',
            'wp-rich-text',
            'wp-server-side-render',
        ),
        MYB_VERSION
    );

    wp_register_script(
        'my-blocks-script',
        plugins_url( 'dist/script.js', __FILE__ ),
        array( 'jquery' ),
        MYB_VERSION,
        true
    );
    // 追加
    wp_register_style(
        'my-blocks-editor-style',
        plugins_url( 'dist/editor.css', __FILE__ ),
        array( 'wp-edit-blocks' ),
        MYB_VERSION
    );
    // 追加
    wp_register_style(
        'my-blocks-style',
        plugins_url( 'dist/style.css', __FILE__ ),
        array(),
        MYB_VERSION
    );

    register_block_type(
        'my-blocks/test-block',
        array(
            'script'        => 'my-blocks-script',
            'editor_script' => 'my-blocks-editor-script',
            'style'         => 'my-blocks-style', // 追加
            'editor_style'  => 'my-blocks-editor-style', // 追加
        ),
    );
}
add_action( 'init', 'myb_blocks_register' );
//省略

これで実際の表示は赤い文字、エディター上では青い文字になればOKです。

ESLintを導入する

JavaScriptにエラーや無駄な記述がないかをチェックしてくれるのが、ESLintです。

エラーを防ぎ、コードをスッキリさせることもできるので導入しておきましょう。

npm i -D babel-eslint eslint eslint-loader eslint-plugin-react

エラーが出たら強制的にインストールします。

npm i -D babel-eslint eslint eslint-loader eslint-plugin-react --force

webpack.config.jsに以下のコードを記述します。

webpack.config.js
                   use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                plugins: ["@babel/plugin-proposal-class-properties"],
                                presets: [
                                    '@babel/preset-env',
                                    [
                                        '@babel/preset-react',
                                        {
                                            "pragma": "wp.element.createElement",
                                            "pragmaFrag": "wp.element.Fragment",
                                            "development": isDevelopment()
                                        }
                                    ]
                                ]
                            }
                        },
                        'eslint-loader' // これを追加
                    ]

次に、.eslintrc.jsファイルを作成します。

.eslintrc.js
module.exports = {
    "env": {
        "browser": true,
        "es6": true,
        "node": true,
        "amd": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly",
        "wp": "readonly",
    },
    "parser": "babel-eslint",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "plugins": [
        "react"
    ],
    "rules": {
        "no-console": "warn",
        "no-unused-vars": "warn",
        "react/react-in-jsx-scope": "off",
        "react/display-name": "off",
        "react/prop-types": "off",
    },
    "settings": {
        "react": {
            "version": "detect"
        }
    },
};

これでESLintの設定は完了です。

ビルドする

最後に、ビルドしてZipファイルに必要なファイルをまとめる処理を追加します。

Zipファイルの生成にはgulpを使いますので、gulpをインストールします。

グローバルにインストールが必要なので、まだの場合はグローバルにインストールしてください。

npm i -g gulp

次にプロジェクトにパッケージをいくつかインストールします。

npm i -D gulp gulp-zip del

gulpfile.mjsを作成します。

gulpfile.mjs
import gulp from 'gulp';
import { deleteSync } from 'del';
import zip from 'gulp-zip';

const bundle = () => {
    return gulp.src([
        "**/*",
        "!node_modules/**",
        "!src/**",
        "!gulpfile.js",
        "!package.json",
        "!package-lock.json",
        "!webpack.config.js",
        "!.gitignore",
        "!.eslintrc.js",
        "!README.md",
        "!bundled/**",
    ])
    .pipe(gulp.dest("./temp/my-blocks"));
}

const compreseZIP = () => {
    return gulp
      .src('./temp/my-blocks/**/*', { base: "temp/" })
      .pipe(zip('my-blocks.zip'))
      .pipe(gulp.dest("bundled"));
}

const cleanTemp = (done) => {
    deleteSync('./temp/**/*');
    done();
}

export const build = gulp.series(
    bundle,
    compreseZIP,
    cleanTemp,
);

拡張子をmjsにするとimport文が使えるようになります。(import文にしないとdelでエラーが発生するため)

次に、package.jsonにビルド用のコマンドを追加します。

package.json
  "scripts": {
    "start": "webpack --mode=development --watch",
    "lint": "eslint src",
    "build": "npm run lint && webpack --mode=production && gulp build"
  },

あとはコマンドを実行するだけでbundledフォルダにZipファイルが作成されます。

npm run build

簡単ですね。

複数ブロックを開発

上記の開発環境では、複数のブロックをまとめて開発できます。

例えば、heading、button、sectionブロックを開発した場合は、

src/editor.js
import "./blocks/heading";
import "./blocks/button";
import "./blocks/section";
src/script.js
import "./blocks/heading/script";
import "./blocks/button/script";
import "./blocks/section/script";
plugin.php
    register_block_type(
        'my-blocks/heading',
        array(
            'script'        => 'my-blocks-script',
            'editor_script' => 'my-blocks-editor-script',
            'style'         => 'my-blocks-style',
            'editor_style'  => 'my-blocks-editor-style',
        ),
    );
    register_block_type(
        'my-blocks/button',
        array(
            'script'        => 'my-blocks-script',
            'editor_script' => 'my-blocks-editor-script',
            'style'         => 'my-blocks-style',
            'editor_style'  => 'my-blocks-editor-style',
        ),
    );
    register_block_type(
        'my-blocks/section',
        array(
            'script'        => 'my-blocks-script',
            'editor_script' => 'my-blocks-editor-script',
            'style'         => 'my-blocks-style',
            'editor_style'  => 'my-blocks-editor-style',
        ),
    );

このように追加していくだけです。

まとめ

今回は、オリジナルブロックの開発環境構築を紹介しました。

公式のやり方とは異なりますが、今回紹介した方法の方が楽に開発できます。

これからオリジナルブロックを開発したいという方は参考にしてください。


その他の記事