手把手教你使用React + webpack构建wordpress插件(gutenberg-block), 学不会你打我。
插件预览
后台预览
前台预览
开始
环境准备
首先,确保你的 WordPress 环境已经安装并且运行,还需要 Node.js 和 npm。
创建插件目录
在 WordPress 的 wp-content/plugins 目录下创建一个新的插件目录。
例如:
easeware-ip-address-block
编写插件文件
在插件目录中创建一个 PHP 文件,用于定义插件的元数据和加载必要的 JavaScript 文件
- 例如index.php:
<?php
/*
* Plugin Name: Easeware IP Address
* Description: Automatically identify IP addresses
* Version: 1.0.0
* Author: levi
* Author URI: https://www.supereasy.com/author/levi-qin/
* Date: 2024/07/09
*/
// 确保不直接访问此文件
if (!defined('ABSPATH')) {
exit;
}
// 注册区块和相关脚本
function easeware_ip_address_block()
{
// 注册JavaScript构建文件
wp_register_script(
'ip-address-editor-script',
plugins_url('build/index.js', __FILE__),
array('wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor', 'wp-components'),
filemtime(plugin_dir_path(__FILE__) . 'build/index.js') // 版本为文件最后修改时间
);
// 可选:注册编辑器样式
wp_register_style(
'ip-address-editor-style',
plugins_url('build/index.css', __FILE__),
array(),
filemtime(plugin_dir_path(__FILE__) . 'build/index.css')
);
// 可选:注册前端样式
wp_register_style(
'ip-address-style',
plugins_url('build/index.css', __FILE__),
array(),
filemtime(plugin_dir_path(__FILE__) . 'build/index.css')
);
// 注册区块
register_block_type('easeware-ip-address-block/ip-address', array(
'editor_script' => 'ip-address-editor-script',
'editor_style' => 'ip-address-editor-style',
'style' => 'ip-address-style',
));
}
add_action('init', 'easeware_ip_address_block');
// 注册前端脚本,只在前台加载
function easeware_enqueue_ap_address_scripts()
{
if (!is_admin()) { // 确保仅在前台加载
wp_register_script(
'ip-address-script',
plugins_url('build/ip-address.js', __FILE__),
array(),
filemtime(plugin_dir_path(__FILE__) . 'build/ip-address.js'),
true // 将脚本加载到页面底部
);
if ( has_block( 'ip/ip-address' ) ) { // 确保存在这个block时加载脚本
wp_enqueue_script('ip-address-script');
}
}
}
add_action('wp_enqueue_scripts', 'easeware_enqueue_ap_address_scripts');
关于easeware_ip_address_block和easeware_enqueue_ap_address_scripts
-
easeware_ip_address_block
此函数是用来注册构建脚本和前后台样式。 -
easeware_enqueue_ap_address_scripts
此函数是用来注册前台脚本。
创建 Block 文件
创建 JavaScript 文件来定义你的 block。这里使用 React 和 JSX 语法来创建编辑器界面。
index.js
const { registerBlockType } = wp.blocks;
const el = wp.element.createElement;
const { addFilter } = wp.hooks;
import { edit } from "./edit.js";
import { save } from "./save.js";
import { withCustomClassControls } from "./createHigherOrderComponent.js";
// SVG icon
const addressIcon = el('svg', {
width: 16,
height: 16,
fill: 'currentColor',
class: 'bi bi-list-check',
viewBox: '0 0 16 16'
},
el('path', {
d: 'm10.495 6.92 1.278-.619a.483.483 0 0 0 .126-.782c-.252-.244-.682-.139-.932.107-.23.226-.513.373-.816.53l-.102.054c-.338.178-.264.626.1.736a.476.476 0 0 0 .346-.027ZM7.741 9.808V9.78a.413.413 0 1 1 .783.183l-.22.443a.602.602 0 0 1-.12.167l-.193.185a.36.36 0 1 1-.5-.516l.112-.108a.453.453 0 0 0 .138-.326ZM5.672 12.5l.482.233A.386.386 0 1 0 6.32 12h-.416a.702.702 0 0 1-.419-.139l-.277-.206a.302.302 0 1 0-.298.52l.761.325Z'
}),
el('path', {
d: 'M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0ZM1.612 10.867l.756-1.288a1 1 0 0 1 1.545-.225l1.074 1.005a.986.986 0 0 0 1.36-.011l.038-.037a.882.882 0 0 0 .26-.755c-.075-.548.37-1.033.92-1.099.728-.086 1.587-.324 1.728-.957.086-.386-.114-.83-.361-1.2-.207-.312 0-.8.374-.8.123 0 .24-.055.318-.15l.393-.474c.196-.237.491-.368.797-.403.554-.064 1.407-.277 1.583-.973.098-.391-.192-.634-.484-.88-.254-.212-.51-.426-.515-.741a6.998 6.998 0 0 1 3.425 7.692 1.015 1.015 0 0 0-.087-.063l-.316-.204a1 1 0 0 0-.977-.06l-.169.082a1 1 0 0 1-.741.051l-1.021-.329A1 1 0 0 0 11.205 9h-.165a1 1 0 0 0-.945.674l-.172.499a1 1 0 0 1-.404.514l-.802.518a1 1 0 0 0-.458.84v.455a1 1 0 0 0 1 1h.257a1 1 0 0 1 .542.16l.762.49a.998.998 0 0 0 .283.126 7.001 7.001 0 0 1-9.49-3.409Z'
}),
);
registerBlockType('ip/ip-address', {
title: 'IP Address',
category: 'easeware-blocks',
icon: addressIcon,
attributes: {
buttonText: {
type: 'string',
default: 'Hide my IP address'
},
showBtnIcon: {
type: 'boolean',
default: true
},
isNewTab: {
type: 'boolean',
default: false
},
customButtonClassName: {
type: 'string',
default: ''
},
buttonURL: {
type: 'string',
default: ''
},
backgroundColor: {
type: 'string',
default: ''
},
textColor: {
type: 'string',
default: ''
}
},
edit: edit,
save: save,
});
addFilter(
'editor.BlockEdit',
'easeware-ip-address-block/with-custom-class-controls',
withCustomClassControls
);
edit.js
/**
* IP Address Block Edit Component
*/
export const edit = (props) => {
const {
attributes,
setAttributes,
} = props;
const onEditText = (event) => {
setAttributes({
buttonText: event.target.textContent
});
};
return (
<div className="ip-address-block-btn">
<div className={`${attributes.customButtonClassName} hide-address-btn`} style={{backgroundColor: attributes.backgroundColor}} contentEditable="true" onBlur={onEditText}>
<a className="hide-address-btn-link" target={attributes.isNewTab ? "_blank" : "_self"} href={attributes.buttonURL}>
{ attributes.showBtnIcon &&
(
<span className="icon-container">
<svg width="25" height="33" viewBox="0 0 25 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_7)">
<path d="M12.5001 32.4074C12.3658 32.4074 12.2316 32.371 12.1128 32.3033C-0.123863 25.1797 -0.051578 15.2306 0.0103798 6.44713C0.0103798 5.84353 0.0207061 5.23992 0.0207061 4.64672C0.0207061 4.25126 0.315006 3.91823 0.702243 3.8714C0.77969 3.86099 8.44181 2.91916 12.0302 0.156101C12.309 -0.0572421 12.6911 -0.0572421 12.9699 0.156101C16.5635 2.92436 24.2204 3.86099 24.2979 3.8714C24.6851 3.91823 24.9794 4.25126 24.9794 4.64672C24.9794 5.23992 24.9794 5.83833 24.9897 6.44713C25.0517 15.2254 25.124 25.1797 12.8873 32.2981C12.7685 32.3658 12.6343 32.4022 12.5001 32.4022V32.4074ZM1.56449 5.33359C1.56449 5.70824 1.56449 6.08289 1.55933 6.46275C1.50253 14.7207 1.43541 24.0714 12.5001 30.7215C23.5647 24.0662 23.5027 14.7207 23.4408 6.46275C23.4408 6.08289 23.4356 5.70824 23.4356 5.33359C21.4891 5.04219 15.8819 4.04312 12.5001 1.74317C9.12335 4.04312 3.51616 5.04219 1.56449 5.33359Z" fill="white"/>
<path d="M1.33211 16.3078C0.624756 12.374 0.795141 8.37247 0.795141 4.65196C0.795141 4.65196 8.69993 3.71013 12.5 1.17603V16.3286H1.33211" fill="white"/>
<path d="M23.6679 16.3281H12.5V31.6264C19.9246 27.3023 22.6714 21.875 23.6679 16.3125" fill="white"/>
</g>
<defs>
<clipPath id="clip0_1_7">
<rect width="25" height="32.4074" fill="white"/>
</clipPath>
</defs>
</svg>
</span>
)
}
<span className="button-text" style={{color: attributes.textColor}}>{attributes.buttonText}</span>
</a>
</div>
</div>
);
}
save.js
/**
* IP Address Block Save Component
*/
export const save = (props) => {
const {
attributes
} = props;
return (
<div className='ip-address-block-container'>
<div className='ip-address-block-header'>
<span className='icon-container'>
<img src='wp-content/plugins/easeware-ip-address-block/build/images/warning.svg'/>
</span>
<span className='ip-address-block-header-title'>Your IP Address is leaking your secrets...</span>
</div>
<div className='ip-address-block-content'>
<div className='ip-address-block-content-wrapper'>
<div className='ip-address-block-content-item info-wrapper item-first'>
<div className='ip-address-info-item'>
<span className='ip-address-info-item-label'>Your IP Address:</span>
<span className='ip-address-info-item-value'>
<span className='loading-spinner-gray'></span>
<span className='ip-address-text'></span>
</span>
</div>
<div className='ip-address-info-item'>
<span className='ip-address-info-item-label'>Internet service provider:</span>
<span className='ip-address-info-item-value'>
<span className='loading-spinner-gray'></span>
<span className='isp-text'></span>
</span>
</div>
<div className='ip-address-info-item'>
<span className='ip-address-info-item-label'>Location:</span>
<span className='ip-address-info-item-value'>
<span className='loading-spinner-gray'></span>
<img className='location-icon'/>
<span className='location-text'></span>
</span>
</div>
</div>
<div className='ip-address-block-content-item desktop-btn-block'>
<div className={`${attributes.customButtonClassName} hide-address-btn`} style={{backgroundColor: attributes.backgroundColor}}>
<a className='hide-address-btn-link' data-target={attributes.isNewTab ? '_blank' : '_self'} href={attributes.buttonURL}>
{ attributes.showBtnIcon && (
<span className='icon-container'>
<img width='25px' height='33px' src='wp-content/plugins/easeware-ip-address-block/build/images/hide-my-ip-address.svg'/>
</span>
)}
<span style={{color: attributes.textColor}}>
{attributes.buttonText}
</span>
</a>
</div>
<div className='ip-address-desc-block'>
<div className='ip-address-desc-icon'>
<img src='wp-content/plugins/easeware-ip-address-block/build/images/unlock.svg'/>
</div>
<div className='ip-address-desc-text'>Your online activity can be seen by your internet service provider and anyone else spying on your connection</div>
</div>
</div>
</div>
<div className='ip-address-map-wrapper'>
<a href='' className='ip-address-block-view-map-link'>View Large Map</a>
<div id='map'></div>
</div>
<div className='ip-address-block-content-item m-btn-block'>
<div className={`${attributes.customButtonClassName} hide-address-btn`} style={{backgroundColor: attributes.backgroundColor}}>
<a className='hide-address-btn-link' data-target={attributes.isNewTab ? '_blank' : '_self'} href={attributes.buttonURL}>
{ attributes.showBtnIcon && (
<span className='icon-container'>
<img width='25px' height='33px' src='wp-content/plugins/easeware-ip-address-block/build/images/hide-my-ip-address.svg'/>
</span>
)}
<span style={{color: attributes.textColor}}>
{attributes.buttonText}
</span>
</a>
</div>
<div className='ip-address-desc-block'>
<div className='ip-address-desc-icon'>
<img src='wp-content/plugins/easeware-ip-address-block/build/images/unlock.svg'/>
</div>
<div className='ip-address-desc-text'>Your online activity can be seen by your internet service provider and anyone else spying on your connection</div>
</div>
</div>
</div>
</div>
);
}
createHigherOrderComponent.js
/**
* IP Address Block CreateHigherOrderComponent
*/
const { createHigherOrderComponent } = wp.compose;
const {
InspectorControls,
BlockControls,
URLInputButton
} = wp.blockEditor;
const {
PanelBody,
Button,
ToggleControl,
TextControl,
Toolbar,
ColorPalette
} = wp.components;
const {
Fragment,
} = wp.element;
export const withCustomClassControls = createHigherOrderComponent(function (BlockEdit) {
return function (props) {
const {
name,
attributes,
setAttributes,
} = props;
const { backgroundColor, textColor } = attributes;
const colors = [
{ name: 'Black', color: '#000000' },
{ name: 'Dark gray', color: '#28303d' },
{ name: 'Green', color: '#d1e4dd' },
{ name: 'Blue', color: '#d1dfe4' },
{ name: 'Purple', color: '#d1d1e4' },
{ name: 'Red', color: '#e4d1d1' },
{ name: 'Orange', color: '#e4dad1' },
{ name: 'Yellow', color: '#eeeadd' },
{ name: 'White', color: '#fff' },
];
if (name !== 'button/ip-address') {
return <BlockEdit {...props} />;
}
const addCustomClass = function (classes) {
setAttributes({
customButtonClassName: classes.join(' ')
});
};
const onChangeLinkRel = (newURL) => {
setAttributes({ buttonURL: newURL });
};
return (
<Fragment>
<BlockEdit {...props} />
<BlockControls>
<Toolbar>
<URLInputButton
url={attributes.buttonURL}
onChange={(newURL) => {
setAttributes({ buttonURL: newURL });
}}
/>
</Toolbar>
</BlockControls>
<InspectorControls>
<PanelBody title="Button icon" initialOpen={true}>
<ToggleControl
checked={ !! attributes.showBtnIcon }
label={ __(
'Show button icon',
'button-icon-block'
) }
onChange={ () =>
setAttributes( {
showBtnIcon: ! attributes.showBtnIcon,
} )
}
/>
</PanelBody>
<PanelBody title="Default button style" initialOpen={true}>
<div className="address-button-container">
<Button
className="se-button-red-v1 editor-sidebar-button-style"
onClick={() => {
addCustomClass(['se-button-red-v1', 'se-custom-button']);
}}
>
Hide my IP address
</Button>
<Button
className="se-button-red-v2 editor-sidebar-button-style"
onClick={() => {
addCustomClass(['se-button-red-v2', 'se-custom-button']);
}}
>
Hide my IP address
</Button>
<Button
className="se-button-darkblue-v1 editor-sidebar-button-style"
onClick={() => {
addCustomClass(['se-button-darkblue-v1', 'se-custom-button']);
}}
>
Hide my IP address
</Button>
<Button
className="se-button-darkblue-v2 editor-sidebar-button-style"
onClick={() => {
addCustomClass(['se-button-darkblue-v2', 'se-custom-button']);
}}
>
Hide my IP address
</Button>
</div>
</PanelBody>
<PanelBody title={__('Color Settings', 'text-domain')} initialOpen={false}>
<div className="color-settings-container">
<strong>{__('Background Color', 'text-domain')}</strong>
<ColorPalette
colors={colors}
value={backgroundColor}
onChange={(newColor) => setAttributes({ backgroundColor: newColor })}
/>
<strong>{__('Text Color', 'text-domain')}</strong>
<ColorPalette
colors={colors}
value={textColor}
onChange={(newColor) => setAttributes({ textColor: newColor })}
/>
</div>
</PanelBody>
<PanelBody title="Link settings" initialOpen={true}>
<ToggleControl
checked={ !! attributes.isNewTab }
label={ __(
'Open in new tab',
'new-tab-block'
) }
onChange={ () =>
setAttributes( {
isNewTab: ! attributes.isNewTab,
} )
}
/>
<TextControl
label={__('IP Address Link Rel', 'ip-address-text-domain')}
value={attributes.buttonURL}
onChange={onChangeLinkRel}
/>
</PanelBody>
</InspectorControls>
</Fragment>
);
};
}, 'withCustomClassControls');
注意
关于registerBlockType函数中第一个参数的配置属性
title
Block 的标题是在 WordPress 编辑器的区块选择器中显示的名称。它通常是一个简短且具有描述性的名称,用于帮助用户识别和选择不同的 Blocks。
category
每个 Block 都属于一个特定的分类,这有助于在编辑器的区块选择器中对 Blocks 进行分组。WordPress 提供了几个默认分类,例如 common(常用)、formatting(格式化)、layout(布局)等,你也可以创建自定义分类。
其他属性
Icon
为 Block 指定一个图标,这在编辑器的区块选择器中显示,提供了视觉识别
Keywords
提供一组与 Block 相关的关键词,以便在编辑器中通过搜索快速找到该 Block。
Description
提供关于 Block 的更详细的说明,有助于用户理解其用途
Attributes
定义 Block 的特性和行为。这些可以包括各种设置,如文本内容、选择项、颜色设置等。
注册 Block
使用 registerBlockType 函数注册你的 block。在这个函数中,你需要指定 block 名称、属性和编辑器界面。
构建
使用 webpack 进行构建
初始化项目
在你的插件目录下,初始化npm项目(如果尚未初始化):
npm init -y
安装Webpack和Babel
安装Webpack以及用于转换ES6和React JSX代码的Babel加载器:
npm install --save-dev webpack webpack-cli @babel/core babel-loader @babel/preset-env @babel/preset-react
配置Babel
在项目根目录创建一个.babelrc文件,添加以下内容:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
配置Webpack
创建一个名为webpack.config.js的文件在项目根目录,并添加以下基础Webpack配置:
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
"ip-address": './src/ipAddresss.js'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
},
module: {
rules: [{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
]
}
};
更新package.json脚本
"scripts": {
"build": "webpack --mode production"
}
创建入口文件
确保你的插件有一个入口文件,如src/index.js,这是你开始编写区块代码的地方。
运行构建
npm run build
这将生成build/index.js,这是您在WordPress插件中注册和使用的脚本。
PS D:\project\benniao-all\lnmp-redis-dev.supereasy.com\www\dev.supereasy.com\public_html\wp-content\plugins\easeware-ip-address-block> npm run build
> easeware-ip-address-block@1.0.0 build
> webpack --mode production
asset index.js 7.37 KiB [compared for emit] [minimized] (name: main)
./src/index.js 10.3 KiB [built] [code generated]
webpack 5.93.0 compiled successfully in 1247 ms
插件使用
启用插件
在wordpress后台启用插件
-
左侧菜单选择Plugins->Installed Plugins
-
在插件列表页面安装本插件
使用插件
-
可以使用快捷方式/+IP调出插件
-
或者在所有插件中选择本插件
最后
以上就是完整的插件开发流程,真正的手把手教学!
官方文档
文档地址:https://developer.wordpress.org/block-editor/reference-guides/components/