Contents
@wordpress/scriptsとは
@wordpress/scriptsは、WordPressのブロックエディタやプラグイン開発のためのビルドツールパッケージです。
React・JSXを使ったモダンなJavaScript開発環境をWordPressプロジェクトに簡単に導入できます!
webpackやBabelなどの複雑な設定を内包しており、開発者は設定ファイルを書かずにすぐに開発を始められます。ビルドコマンドや開発用の監視モード、ESLintやJestなどのテスト環境も統合されています
リッチテキストツールバーブロックの作成手順
パッケージインストール
npm プロジェクトの初期化
package.json ファイルを作成します。
npm init -y
@wordpress/scriptsのインストール
(WordPress のブロックエディター用のパッケージ)
npm install @wordpress/scripts @wordpress/blocks @wordpress/i18n @wordpress/block-editor @wordpress/components @wordpress/data react react-dompackage.jsonにスクリプトが追記
{
"name": "my-theme-custom-block",
"version": "1.0.0",
"description": "My first WordPress custom block in a theme",
"main": "build/index.js",
"scripts": {
<strong>"build": "wp-scripts build",
"start": "wp-scripts start"</strong>
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@wordpress/scripts": "^x.x.x"
}
}@wordpress パッケージの型定義ファイルインストール
npm install --save-dev @types/wordpress__blocks
npm install --save-dev @types/wordpress__block-editor @types/wordpress__components
npm install @wordpress/data @wordpress/rich-text
npm install --save-dev @types/react @types/wordpress__rich-textwebpack
- ES6+やTypeScriptのコードを古いブラウザでも動作するES5に変換できます。
- モジュール間の依存関係を自動的に解析し、複数のJavaScriptファイルを1つのファイルにまとめます。
webpack.config.jsの設定
webpack.config.jsは、webpackの設定ファイルです。
webpack.config.js
const defaultConfig = require('@wordpress/scripts/config/webpack.config');
const path = require('path');
module.exports = {
...defaultConfig,
// エントリーポイントの設定
entry: {
lead: path.resolve(__dirname, 'wp-content/themes/originaltheme/src/lead/index.tsx'),
},
// 出力の設定
output: {
path: path.resolve(__dirname, 'wp-content/themes/originaltheme/build'),
filename: '[name].js', // [name] には entry で指定したキーが入る
},
// ファイルの拡張子を省略できるようにする
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
module: {
// ローダーの設定
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
};npm install ts-loader --save-devtsconfig.json
{
"compilerOptions": {
"target": "es5", // 出力するJavaScriptのバージョン
"module": "commonjs", // モジュールシステム
"strict": true, // 厳格な型チェックオプション
"esModuleInterop": true, // ESモジュールとの互換性
"skipLibCheck": true, // ライブラリの型チェックをスキップ
"forceConsistentCasingInFileNames": true, // ファイル名の大文字小文字の一貫性を強制
"jsx": "react", // JSXのサポート
"moduleResolution": "node", // モジュール解決方法
"resolveJsonModule": true, // JSONモジュールのインポートを許可
"outDir": "./build", // 出力ディレクトリ
"rootDir": "./wp-content/themes/originaltheme/src" // ソースファイルのルートディレクトリ
},
"include": [
"wp-content/themes/originaltheme/src/**/*" // コンパイル対象のファイル
],
"exclude": [
"node_modules" // コンパイル対象外のファイル
]
}ブロックのソースファイルを作成
リッチテキストツールバーを使用
インライン単位で文字サイズ文字色を変更できるブロック
index.tsx
import { registerBlockType } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import {
useBlockProps,
RichText,
RichTextToolbarButton,
} from '@wordpress/block-editor';
import {
registerFormatType,
applyFormat,
removeFormat,
type RichTextValue
} from '@wordpress/rich-text';
import React from 'react';
// ブロックの属性の型定義
interface BlockAttributes {
content: string;
}
// フォーマットの型定義
interface FormatProps {
isActive: boolean;
value: RichTextValue;
onChange: (value: RichTextValue) => void;
}
// WPFormat の型定義
interface WPFormatType {
name: string;
title: string;
tagName: string;
className: string;
interactive: boolean;
edit: (props: FormatProps) => JSX.Element;
}
// フォーマット名の定義
const FONT_SIZE_FORMAT = 'custom-format/font-size';
const TEXT_COLOR_FORMAT = 'custom-format/text-color';
// フォントサイズフォーマットの登録
const fontSizeFormat: WPFormatType = {
name: FONT_SIZE_FORMAT,
title: __('Font Size', 'custom-format'),
tagName: 'span',
className: 'custom-font-size',
interactive: false,
edit: ({ isActive, value, onChange }: FormatProps) => {
const fontSizes = ['0.75rem', '1rem', '2rem', '3rem', '4rem'];
// フォントサイズの変更の処理の関数
const onChangeFontSize = (size: string) => {
const newFormat = {
type: FONT_SIZE_FORMAT,
attributes: {
style: `font-size: ${size};`,
},
};
if (isActive) {
onChange(removeFormat(value, FONT_SIZE_FORMAT));
}
onChange(applyFormat(value, newFormat));
};
return (
<React.Fragment>
{fontSizes.map((size) => (
<RichTextToolbarButton
key={size}
icon="editor-textcolor"
title={`${size} ${__('Font Size', 'custom-format')}`}
onClick={() => onChangeFontSize(size)}
isActive={isActive}
/>
))}
</React.Fragment>
);
}
};
// 文字色フォーマットの登録
const textColorFormat: WPFormatType = {
name: TEXT_COLOR_FORMAT,
title: __('Red Text', 'custom-format'),
tagName: 'span',
className: 'custom-text-color',
interactive: false,
edit: ({ isActive, value, onChange }: FormatProps) => {
// 文字色の変更の処理の関数
const onToggleColor = () => {
if (isActive) {
onChange(removeFormat(value, TEXT_COLOR_FORMAT));
} else {
const newFormat = {
type: TEXT_COLOR_FORMAT,
attributes: {
style: 'color: red;',
},
};
onChange(applyFormat(value, newFormat));
}
};
return (
<RichTextToolbarButton
icon="editor-textcolor"
title={__('Red Text', 'custom-format')}
onClick={onToggleColor}
isActive={isActive}
/>
);
}
};
// フォーマットの登録
registerFormatType(FONT_SIZE_FORMAT, fontSizeFormat);
registerFormatType(TEXT_COLOR_FORMAT, textColorFormat);
// ブロックを登録
registerBlockType<BlockAttributes>('customtheme/cardlead', {
title: __('カード記事リード文', 'custom-block'),
icon: 'editor-textcolor',
category: 'text',
attributes: {
content: {
type: 'string',
source: 'html',
selector: 'p',
},
},
edit: ({ attributes, setAttributes }) => {
const { content } = attributes;
const blockProps = useBlockProps({
className: 'my-custom-class',
style: {
borderTop: '8px solid #ffda00',
borderBottom: '8px solid #ffda00',
textAlign: 'center' as 'center',
}
});
return (
<div {...blockProps}>
<RichText
tagName="p"
value={content}
onChange={(newContent: string) => setAttributes({ content: newContent })}
placeholder={__('Select text and choose font size or color...', 'custom-block')}
allowedFormats={[
'core/bold',
'core/italic',
FONT_SIZE_FORMAT,
TEXT_COLOR_FORMAT
]}
/>
</div>
);
},
save: ({ attributes }) => {
const { content } = attributes;
const blockProps = useBlockProps.save({
className: 'my-custom-class',
style: {
borderTop: '8px solid #ffda00',
borderBottom: '8px solid #ffda00',
textAlign: 'center' as 'center',
}
});
return (
<div {...blockProps}>
<RichText.Content
tagName="p"
value={content}
/>
</div>
);
},
});functions.phpで読み込み
functions.php
<?php
// カスタムブロックの登録
function my_theme_custom_block_init() {
// register_block_type()の第一引数は「ドメイン名/ブロック名」でregisterBlockType()の第一引数と一致させる
register_block_type( 'customtheme/cardlead', array(
'editor_script' => 'cardlead-script',
) );
}
add_action( 'init', 'my_theme_custom_block_init' );
// ブロックのスクリプトを読み込む
function my_theme_custom_block_enqueue_assets() {
$asset_file_cardlead = include( get_template_directory() . '/build/cardlead.asset.php' );
wp_enqueue_script(
'cardlead-script',
get_template_directory_uri() . '/build/cardlead.js',
$asset_file_cardlead['dependencies'],
$asset_file_cardlead['version']
);
}
add_action( 'enqueue_block_editor_assets', 'my_theme_custom_block_enqueue_assets' );動的CTAブロック作成手順
動的CTAブロックの内容は、管理画面上でテキスト、ボタン
【CTAに付与されるパラメータ例】
dycta=t101_t203_b101
※上記をCV時URLに付与する
| 文言1 | 文言1 パラメータ | 文言1 排出率 | 文言2 | 文言2 パラメータ | 文言2 排出率 | CTAボタン | CTAボタン パラメータ | CTAボタン 排出率 |
|---|---|---|---|---|---|---|---|---|
| 初回申し込みは無料 | t101 | 30% | 24時間以内に20人が予約しました | t201 | 20% | 今すぐメールでお問合せ | 30% | b101 |
| 無料トライアル | t102 | 30% | 期間限定となってます | t202 | 10% | お気軽にお問い合わせください | 40% | b102 |
| たったの3ステップ申し込みフォーム | t103 | 40% | 今なら限定特典も | t203 | 70% | お申込みはこちら | 30% | b103 |
| … | … | … |
block.json
block.jsonは、WordPressのブロックエディタで使用するカスタムブロックのメタデータを定義するJSONファイルです。
ブロックの名前、説明、カテゴリ、アイコン、対応する属性、使用するJavaScriptやCSSファイルのパスなどを記述します。このファイルを使うことで、
ブロックの設定を一元管理でき、PHPとJavaScriptの両方からブロック情報にアクセスできるようになります。WordPress 5.8以降で推奨される方式で、従来のPHPでの登録方法よりもシンプルで保守性が高いのが特徴です。
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "theme-custom/cta",
"title": "動的CTA",
"category": "widgets",
"icon": "megaphone",
"description": "ABテスト可能な動的CTAブロック",
"supports": {
"html": false
},
"attributes": {
"ctaUrl": {
"type": "string",
"default": "https://10-10-10.jp/mailform/"
},
"text01": {
"type": "array",
"default": [
{ "text": "初回申し込みは無料", "param": "t101", "rate": 30 },
{ "text": "無料トライアル", "param": "t102", "rate": 30 },
{ "text": "たったの3ステップ申し込みフォーム", "param": "t103", "rate": 40 }
]
},
"text02": {
"type": "array",
"default": [
{ "text": "24時間以内に20人が予約しました", "param": "t201", "rate": 20 },
{ "text": "期間限定となってます", "param": "t202", "rate": 10 },
{ "text": "今なら限定特典も", "param": "t203", "rate": 70 }
]
},
"ctaButton": {
"type": "array",
"default": [
{ "text": "今すぐメールでお問合せ", "param": "b101", "rate": 30 },
{ "text": "お気軽にお問い合わせください", "param": "b102", "rate": 40 },
{ "text": "お申込みはこちら", "param": "b103", "rate": 30 }
]
}
},
"textdomain": "theme-custom",
"editorScript": "file:./dynamicCta.js"
}supports html falseは、このブロックのHTMLコードを直接編集する機能を無効にする設定です。trueにするとエディタでHTMLとして編集できますが、動的ブロックでは通常falseにしてビジュアル編集のみに制限します。
attributesは、編集画面でユーザーが変更できるブロックの内容を保存するためのデータ定義です。
editorScriptは、ブロックエディタで読み込むJavaScriptファイルを指定
index.tsx
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
// CTAテキストの型定義
export interface CtaTextOption {
text: string;
param: string;
rate: number;
}
// ブロックの属性型定義
export type DynamicCtaAttributes = {
ctaUrl: string;
text01: CtaTextOption[];
text02: CtaTextOption[];
ctaButton: CtaTextOption[];
}
registerBlockType<DynamicCtaAttributes>('theme-custom/cta', {
edit: Edit,
save: () => null,
});edit.tsx
import { useBlockProps } from '@wordpress/block-editor';
import { TextControl, Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { DynamicCtaAttributes, CtaTextOption } from './index';
const Edit = ({ attributes, setAttributes }: {
attributes: DynamicCtaAttributes;
setAttributes: (attrs: Partial<DynamicCtaAttributes>) => void;
}) => {
const blockProps = useBlockProps();
const addOption = (source: 'text01' | 'text02' | 'ctaButton') => {
setAttributes({
[source]: [
...attributes[source],
{ text: '', param: '', rate: 0 }
]
});
};
const updateOption = (
source: 'text01' | 'text02' | 'ctaButton',
index: number,
field: keyof CtaTextOption,
value: string | number
) => {
const newOptions = [...attributes[source]];
newOptions[index] = { ...newOptions[index], [field]: value };
setAttributes({ [source]: newOptions });
};
const removeOption = (source: 'text01' | 'text02' | 'ctaButton', index: number) => {
setAttributes({
[source]: attributes[source].filter((_, i) => i !== index)
});
};
const renderTable = (source: 'text01' | 'text02' | 'ctaButton', title: string) => {
return (
<div style={{ marginTop: '1.5rem' }}>
<h4>{title}</h4>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ backgroundColor: '#f0f0f0' }}>
<th style={{ border: '1px solid #ddd', padding: '8px', width: '120px' }}>パラメータ</th>
<th style={{ border: '1px solid #ddd', padding: '8px' }}>テキスト</th>
<th style={{ border: '1px solid #ddd', padding: '8px', width: '100px' }}>出現率(%)</th>
<th style={{ border: '1px solid #ddd', padding: '8px', width: '80px' }}>削除</th>
</tr>
</thead>
<tbody>
{attributes[source].map((option, index) => (
<tr key={index}>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
<input
type="text"
value={option.param}
onChange={(e) => updateOption(source, index, 'param', e.target.value)}
style={{ width: '100%' }}
placeholder="例: t101"
/>
</td>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
<input
type="text"
value={option.text}
onChange={(e) => updateOption(source, index, 'text', e.target.value)}
style={{ width: '100%' }}
placeholder="テキストを入力"
/>
</td>
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
<input
type="number"
value={option.rate}
onChange={(e) => updateOption(source, index, 'rate', Number(e.target.value))}
style={{ width: '100%' }}
min="0"
max="100"
/>
</td>
<td style={{ border: '1px solid #ddd', padding: '8px', textAlign: 'center' }}>
<Button isDestructive onClick={() => removeOption(source, index)}>
削除
</Button>
</td>
</tr>
))}
</tbody>
</table>
<Button isPrimary onClick={() => addOption(source)} style={{ marginTop: '0.5rem' }}>
追加
</Button>
</div>
);
};
return (
<div {...blockProps} style={{ border: '1px solid #ccc', padding: '1rem' }}>
<h3>{__('動的CTA設定', 'theme-custom')}</h3>
<TextControl
label={__('CTA URL', 'theme-custom')}
value={attributes.ctaUrl}
onChange={(value) => setAttributes({ ctaUrl: value })}
/>
{renderTable('text01', '文言1 (text01)')}
{renderTable('text02', '文言2 (text02)')}
{renderTable('ctaButton', 'CTAボタン (ctaButton)')}
</div>
);
};
export default Edit;functions.php
// 動的CTAブロックの登録
function register_dynamic_cta_block()
{
register_block_type(get_template_directory() . '/blocks/dynamic-cta', array(
'render_callback' => 'render_dynamic_cta_block'
));
}
add_action('init', 'register_dynamic_cta_block');
// 動的CTAブロックのレンダリング
function render_dynamic_cta_block($attributes)
{
$ctaUrl = isset($attributes['ctaUrl']) ? $attributes['ctaUrl'] : 'https://10-10-10.jp/mailform/';
$text01 = isset($attributes['text01']) ? $attributes['text01'] : [];
$text02 = isset($attributes['text02']) ? $attributes['text02'] : [];
$ctaButton = isset($attributes['ctaButton']) ? $attributes['ctaButton'] : [];
// ランダムにテキストを選択(出現率に基づく)
$selectedText01 = select_random_by_rate($text01);
$selectedText02 = select_random_by_rate($text02);
$selectedButton = select_random_by_rate($ctaButton);
// パラメータを結合
$params = implode('_', array_filter([
$selectedText01['param'],
$selectedText02['param'],
$selectedButton['param']
]));
$finalUrl = $ctaUrl . '?dycta=' . $params;
ob_start();
?>
<!-- デバッグ情報 -->
<!--
========== text01 ==========
<?php foreach ($text01 as $i => $item): ?>
[<?php echo $i; ?>] <?php echo $item['text']; ?> (param: <?php echo $item['param']; ?>, rate: <?php echo $item['rate']; ?>)
<?php endforeach; ?>
========== text02 ==========
<?php foreach ($text02 as $i => $item): ?>
[<?php echo $i; ?>] <?php echo $item['text']; ?> (param: <?php echo $item['param']; ?>, rate: <?php echo $item['rate']; ?>)
<?php endforeach; ?>
========== ctaButton ==========
<?php foreach ($ctaButton as $i => $item): ?>
[<?php echo $i; ?>] <?php echo $item['text']; ?> (param: <?php echo $item['param']; ?>, rate: <?php echo $item['rate']; ?>)
<?php endforeach; ?>
========== 選択された結果 ==========
text01: <?php echo $selectedText01['text']; ?> (<?php echo $selectedText01['param']; ?>)
text02: <?php echo $selectedText02['text']; ?> (<?php echo $selectedText02['param']; ?>)
button: <?php echo $selectedButton['text']; ?> (<?php echo $selectedButton['param']; ?>)
パラメータ: <?php echo $params; ?>
-->
<div class="dynamic-cta">
<div class="cta-text-01"><?php echo esc_html($selectedText01['text']); ?></div>
<div class="cta-text-02"><?php echo esc_html($selectedText02['text']); ?></div>
<a href="<?php echo esc_url($finalUrl); ?>" class="cta-button">
<?php echo esc_html($selectedButton['text']); ?>
</a>
</div>
<?php
return ob_get_clean();
}
function select_random_by_rate($options) {
if (empty($options)) return ['text' => '', 'param' => '', 'rate' => 0];
$total = array_sum(array_column($options, 'rate')); // 確率の合計を計算
$rand = rand(1, $total); //
$sum = 0;
foreach ($options as $option) {
$sum += $option['rate'];
if ($rand <= $sum) {
return $option;
}
}
return $options[0];
}