Bon, soyons honnêtes : développer un plugin WordPress en 2026 avec du jQuery et du PHP vanilla, c’est un peu comme conduire une 2CV sur l’autoroute ! Aujourd’hui, on va voir comment créer un vrai plugin moderne avec TypeScript et React 18, histoire de rejoindre enfin le 21ème siècle. Et croyez-moi, une fois que vous aurez goûté au hot reload et aux composants React dans l’admin WordPress, vous ne pourrez plus revenir en arrière.
Architecture et configuration du plugin TypeScript + React
Développer un plugin WordPress avec TypeScript et React nécessite une architecture bien structurée. On va détailler ensemble comment organiser le code, configurer les outils et faire communiquer PHP avec JavaScript.
Structure des dossiers et fichiers essentiels
La structure d’un plugin moderne TypeScript + React suit une organisation précise qui sépare clairement les sources du code compilé :
mon-plugin/
├── src/
│ ├── components/
│ ├── hooks/
│ ├── types/
│ ├── admin.tsx
│ └── frontend.tsx
├── build/
├── assets/
│ ├── css/
│ └── images/
├── includes/
│ └── class-plugin.php
├── webpack.config.js
├── tsconfig.json
├── package.json
└── mon-plugin.php
Le dossier src/ contient tout notre code TypeScript et React. C’est là qu’on développe nos composants, nos hooks personnalisés et nos types. Le dossier build/ accueille les fichiers compilés qui seront chargés par WordPress. Cette séparation est cruciale pour maintenir un code propre.
Configuration Webpack et Babel pour l’environnement de développement
La configuration Webpack est le cœur de notre environnement de développement. Voici un webpack.config.js optimisé pour TypeScript et React :
const path = require('path');
module.exports = {
entry: {
admin: './src/admin.tsx',
frontend: './src/frontend.tsx'
},
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
};
La propriété externals est importante : elle indique à Webpack de ne pas inclure React dans le bundle final, car WordPress le fournit déjà. Le package.json doit inclure les dépendances essentielles comme @types/wordpress__blocks, @types/wordpress__components, typescript et ts-loader.
Intégration TypeScript avec l’API WordPress
Pour que TypeScript comprenne les API WordPress, il faut configurer le tsconfig.json correctement :
{
"compilerOptions": {
"target": "es2018",
"lib": ["dom", "es2018"],
"module": "esnext",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node"
},
"include": ["src/**/*"]
}
Côté PHP, l’intégration se fait avec wp_enqueue_script et wp_localize_script pour passer des données à React :
wp_enqueue_script(
'mon-plugin-admin',
plugin_dir_url(__FILE__) . 'build/admin.js',
['wp-element', 'wp-components'],
'1.0.0',
true
);
wp_localize_script('mon-plugin-admin', 'monPluginData', [
'apiUrl' => rest_url('wp/v2/'),
'nonce' => wp_create_nonce('wp_rest')
]);
Cette approche permet à React d’accéder aux données WordPress de façon type-safe grâce à TypeScript.
Développement des composants React dans l’admin WordPress
Maintenant qu’on a notre environnement configuré, on va pouvoir créer nos premiers composants React dans l’admin WordPress. Et je dois dire que React 18 apporte vraiment de nouvelles possibilités intéressantes !
Pour commencer, créons notre premier composant avec les hooks modernes. Voici un exemple pratique avec useState et useEffect :
import React, { useState, useEffect } from 'react';
interface Post {
id: number;
title: { rendered: string };
content: { rendered: string };
}
const PostList: React.FC = () => {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
wp.apiFetch({ path: '/wp/v2/posts' })
.then((data: Post[]) => {
setPosts(data);
setLoading(false);
});
}, []);
if (loading) return <div>Chargement...</div>;
return (
<div className="wrap">
<h1>Mes Articles</h1>
{posts.map(post => (
<div key={post.id} className="postbox">
<h3>{post.title.rendered}</h3>
</div>
))}
</div>
);
};
L’intégration avec l’API REST WordPress se fait naturellement avec wp.apiFetch. Cette fonction est déjà disponible dans l’admin WordPress et gère automatiquement les nonces et l’authentification.
Pour créer une page d’administration, on utilise wp_add_menu_page côté PHP :
add_action('admin_menu', function() {
add_menu_page(
'Mon Plugin',
'Mon Plugin',
'manage_options',
'mon-plugin',
'render_admin_page'
);
});
function render_admin_page() {
echo '<div id="mon-plugin-admin"></div>';
}
Et côté JavaScript, on render notre composant React dans cette div :
import { createRoot } from 'react-dom/client';
const container = document.getElementById('mon-plugin-admin');
if (container) {
const root = createRoot(container);
root.render(<App />);
}
Pour les formulaires avec validation TypeScript, j’utilise souvent cette approche :
interface FormData {
title: string;
content: string;
}
const PostForm: React.FC = () => {
const [formData, setFormData] = useState<FormData>({
title: '',
content: ''
});
const [errors, setErrors] = useState<Partial<FormData>>({});
const validateForm = (): boolean => {
const newErrors: Partial<FormData> = {};
if (!formData.title.trim()) {
newErrors.title = 'Le titre est obligatoire';
}
if (formData.title.length > 100) {
newErrors.title = 'Le titre ne peut pas dépasser 100 caractères';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
try {
await wp.apiFetch({
path: '/wp/v2/posts',
method: 'POST',
data: formData
});
// Success handling
} catch (error) {
console.error('Erreur:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.title}
onChange={(e) => setFormData({...formData, title: e.target.value})}
/>
{errors.title && <span className="error">{errors.title}</span>}
</form>
);
};
La communication entre React et WordPress passe principalement par les données localisées. Dans votre fichier PHP principal :
wp_localize_script('mon-plugin-admin', 'monPluginData', [
'apiUrl' => rest_url('wp/v2/'),
'nonce' => wp_create_nonce('wp_rest'),
'currentUser' => wp_get_current_user(),
'permissions' => [
'canEdit' => current_user_can('edit_posts'),
'canDelete' => current_user_can('delete_posts')
]
]);
Côté React, on peut accéder à ces données via l’objet global :
declare global {
interface Window {
monPluginData: {
apiUrl: string;
nonce: string;
currentUser: any;
permissions: {
canEdit: boolean;
canDelete: boolean;
};
};
}
}
const { permissions } = window.monPluginData;
if (permissions.canEdit) {
// Afficher les boutons d'édition
}
Pour la gestion d’état plus complexe, useContext est votre ami :
interface AppContextType {
user: any;
permissions: any;
updateUser: (user: any) => void;
}
const AppContext = React.createContext<AppContextType | null>(null);
export const AppProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
const [user, setUser] = useState(window.monPluginData.currentUser);
return (
<AppContext.Provider value={{
user,
permissions: window.monPluginData.permissions,
updateUser: setUser
}}>
{children}
</AppContext.Provider>
);
};
Bon, je dois admettre que les premières fois, j’ai eu du mal avec la gestion des nonces et des permissions. Mais une fois qu’on comprend le principe, ça devient fluide !
Outils de développement et workflow moderne
Maintenant qu’on a notre plugin qui fonctionne, parlons des outils qui vont nous faire gagner un temps fou au quotidien. Parce que développer sans hot reload en 2026, c’est comme coder avec un clavier cassé : techniquement possible, mais pourquoi s’infliger ça ?
Hot reload et environnement de développement
Le hot reload, c’est LA fonctionnalité qui change la vie quand on développe avec React. Plus besoin de rafraîchir la page à chaque modification ! Pour l’intégrer dans notre environnement WordPress, on va configurer webpack-dev-server avec quelques ajustements spécifiques.
D’abord, modifions notre webpack.config.js pour inclure le serveur de développement :
const config = {
// ... configuration existante
devServer: {
hot: true,
port: 8080,
allowedHosts: 'all',
headers: {
'Access-Control-Allow-Origin': '*',
},
devMiddleware: {
writeToDisk: true,
},
},
};
Ensuite, on va créer nos npm scripts dans le package.json. Ces scripts vont orchestrer tout notre workflow de développement :
{
"scripts": {
"dev": "wp-env start && webpack serve --mode=development",
"build": "webpack --mode=production",
"watch": "webpack --mode=development --watch",
"start:env": "wp-env start",
"stop:env": "wp-env stop"
}
}
Pour l’environnement de développement, wp-env est devenu incontournable. Ce petit outil Docker fourni par WordPress crée un environnement local propre en quelques secondes. On configure tout ça dans un fichier .wp-env.json :
{
"core": "WordPress/WordPress#6.4",
"plugins": ["."],
"themes": ["WordPress/twentytwentyfour"],
"port": 8888,
"env": {
"development": {
"plugins": [".", "https://downloads.wordpress.org/plugin/query-monitor.zip"]
}
}
}
Comme ça, npm run dev lance tout automatiquement : l’environnement WordPress ET le serveur de développement avec hot reload. C’est beau, non ?
Tests unitaires avec Jest et React Testing Library
Bon, je vais être honnête : les tests, c’est pas toujours fun à écrire. Mais quand votre plugin fonctionne sur 20 sites différents et que vous devez faire une mise à jour… là, vous bénirez vos tests !
Pour configurer Jest avec TypeScript et React Testing Library, on commence par installer les dépendances :
npm install --save-dev jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom @testing-library/user-event
Ensuite, on crée notre configuration Jest dans jest.config.js :
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
moduleNameMapping: {
'\\.(css|less|scss)$': 'identity-obj-proxy',
},
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{ts,tsx}',
'<rootDir>/src/**/*.{test,spec}.{ts,tsx}'
],
};
Voici un exemple concret de test pour un de nos composants React :
import { render, screen, fireEvent } from '@testing-library/react';
import { PostList } from '../components/PostList';
import { Post } from '../types';
const mockPosts: Post[] = [
{ id: 1, title: 'Test Post', content: 'Content', status: 'publish' },
{ id: 2, title: 'Draft Post', content: 'Draft content', status: 'draft' }
];
describe('PostList Component', () => {
test('renders posts correctly', () => {
render(<PostList posts={mockPosts} />);
expect(screen.getByText('Test Post')).toBeInTheDocument();
expect(screen.getByText('Draft Post')).toBeInTheDocument();
});
test('filters posts by status', async () => {
render(<PostList posts={mockPosts} />);
const filterSelect = screen.getByRole('combobox');
fireEvent.change(filterSelect, { target: { value: 'publish' } });
expect(screen.getByText('Test Post')).toBeInTheDocument();
expect(screen.queryByText('Draft Post')).not.toBeInTheDocument();
});
});
Pour les outils de qualité de code, on intègre ESLint et Prettier avec les configurations WordPress. Dans .eslintrc.js :
module.exports = {
extends: [
'@wordpress/eslint-config/recommended',
'@wordpress/eslint-config/typescript',
],
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
};
Et voilà ! Avec ça, vous avez un workflow de développement qui tient la route. Hot reload pour la productivité, tests pour la sérénité, et des outils de qualité pour un code propre.
