Step-by-Step Guide to building a custom real-time audit dashboard block for Gutenberg using Svelte standalone templates
Project Setup: SvelteKit and WordPress Environment
This guide details the construction of a custom Gutenberg block for WordPress, leveraging SvelteKit for its frontend logic and templating. The block will display real-time audit data, necessitating a robust build process and integration strategy. We’ll begin by establishing a SvelteKit project and configuring it to serve as a WordPress plugin asset builder.
First, initialize a new SvelteKit project. We’ll opt for a minimal setup, focusing on the core Svelte capabilities. Navigate to your desired development directory and run:
npm create svelte@latest my-audit-block cd my-audit-block npm install
Next, we need to configure SvelteKit to output static assets that WordPress can enqueue. Modify your svelte.config.js to point the adapter to a directory that will eventually reside within your WordPress plugin’s assets folder. For this example, we’ll assume a structure where the Svelte build output is placed in ../wp-content/plugins/my-custom-audit-plugin/assets/js relative to the Svelte project root. This requires a custom adapter or a slight modification to the default static adapter.
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
// Default output directory for adapter-static is 'build'
// We'll adjust this during the build script or by specifying a different path here.
// For simplicity, let's assume a relative path for now.
// In a real-world scenario, you'd likely use a more sophisticated build script
// or a custom adapter to handle the WordPress plugin structure.
pages: '../wp-content/plugins/my-custom-audit-plugin/assets/js/build/pages',
assets: '../wp-content/plugins/my-custom-audit-plugin/assets/js/build/assets'
}),
// Ensure paths are correctly configured for WordPress enqueueing
paths: {
base: '/wp-content/plugins/my-custom-audit-plugin/assets/js/build',
}
}
};
export default config;
Create the necessary directory structure for your WordPress plugin. In your WordPress installation’s wp-content/plugins/ directory, create a new folder named my-custom-audit-plugin. Inside this, create another folder named assets/js. This is where our Svelte build output will be placed.
Gutenberg Block Registration and Svelte Component Integration
Now, let’s define the Gutenberg block in PHP. This PHP file will register the block type and enqueue the JavaScript bundle generated by SvelteKit. Create a file named my-custom-audit-plugin.php in your plugin directory (wp-content/plugins/my-custom-audit-plugin/).
<?php
/**
* Plugin Name: My Custom Audit Block
* Description: A custom Gutenberg block to display real-time audit data using Svelte.
* Version: 1.0.0
* Author: Your Name
* License: GPL-2.0-or-later
* Text Domain: my-custom-audit-block
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register the Gutenberg block.
*/
function my_custom_audit_block_register() {
// Automatically load dependencies and version.
$asset_file = include( plugin_dir_path( __FILE__ ) . 'assets/js/build/asset.json'); // Assuming asset.json is generated by SvelteKit build
wp_register_script(
'my-custom-audit-block-editor-script',
plugins_url( 'assets/js/build/' . $asset_file['file']['editor'], __FILE__ ), // Path to editor script
$asset_file['dependencies'],
$asset_file['version']
);
wp_register_script(
'my-custom-audit-block-frontend-script',
plugins_url( 'assets/js/build/' . $asset_file['file']['frontend'], __FILE__ ), // Path to frontend script
array(), // No external dependencies for frontend script
$asset_file['version']
);
wp_register_style(
'my-custom-audit-block-editor-style',
plugins_url( 'assets/js/build/' . $asset_file['file']['editor_style'], __FILE__ ), // Path to editor CSS
array(),
$asset_file['version']
);
wp_register_style(
'my-custom-audit-block-frontend-style',
plugins_url( 'assets/js/build/' . $asset_file['file']['frontend_style'], __FILE__ ), // Path to frontend CSS
array(),
$asset_file['version']
);
register_block_type( 'my-custom-audit-block/audit-dashboard', array(
'editor_script' => 'my-custom-audit-block-editor-script',
'editor_style' => 'my-custom-audit-block-editor-style',
'script' => 'my-custom-audit-block-frontend-script',
'style' => 'my-custom-audit-block-frontend-style',
'render_callback' => 'my_custom_audit_block_render', // Optional: for server-side rendering if needed
) );
}
add_action( 'init', 'my_custom_audit_block_register' );
/**
* Optional: Render callback for server-side rendering.
* For a real-time dashboard, client-side rendering with JS is more appropriate.
*/
function my_custom_audit_block_render( $attributes ) {
// This function would typically return HTML for server-side rendering.
// For a dynamic, real-time dashboard, we rely on the JavaScript to handle rendering.
// We can enqueue the frontend script here if it wasn't already done in register_block_type.
return '<div id="my-audit-dashboard-root"></div>';
}
The PHP code registers the block type and enqueues the necessary scripts and styles. Crucially, it relies on an asset.json file generated by the SvelteKit build process. This file maps logical script/style names to their actual filenames (which often include cache-busting hashes). We’ll configure SvelteKit to generate this.
SvelteKit Configuration for Asset Manifest
To generate the asset.json file, we need to adapt the SvelteKit build process. The default @sveltejs/adapter-static doesn’t directly produce a WordPress-compatible asset manifest. We can achieve this by creating a custom build script or by modifying the adapter’s output. A common approach is to use a plugin that generates this manifest. For this example, we’ll simulate the generation of asset.json by creating a simple script that runs after the Svelte build.
First, let’s define the structure of our Svelte application. Create a src/routes/ directory. Inside, create a file for the block’s editor interface (e.g., src/routes/editor.svelte) and another for the frontend display (e.g., src/routes/frontend.svelte). We’ll also need a main entry point for the editor script.
Create src/main.js as the entry point for the editor script:
// src/main.js
import App from './routes/editor.svelte';
// This script is intended for the Gutenberg editor.
// It will mount the Svelte component into the block's edit function.
// The actual rendering in the frontend will be handled by a separate script.
// For simplicity, we'll assume a global registration mechanism or a specific hook.
// In a real-world scenario, you'd use wp.blocks.registerBlockType and its edit/save functions.
// Example of how you might register a block (simplified):
// import { registerBlockType } from '@wordpress/blocks';
// import Edit from './routes/editor.svelte'; // Assuming Edit component is exported
// registerBlockType('my-custom-audit-block/audit-dashboard', {
// edit: Edit,
// save: () => null, // For dynamic blocks, save returns null
// });
// For this example, we'll just export the App component.
export default App;
Create src/routes/editor.svelte for the block’s editor view:
<!-- src/routes/editor.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
export let attributes = {};
export let setAttributes = () => {};
const dispatch = createEventDispatcher();
function updateTitle(event) {
setAttributes({ title: event.target.value });
}
// Dispatch an update event to the parent block component
dispatch('update', { attributes });
</script>
<div class="audit-block-editor">
<h3>Audit Dashboard Settings</h3>
<label for="block-title">Block Title:</label>
<input
id="block-title"
type="text"
value={attributes.title || 'Audit Dashboard'}
on:input={updateTitle}
/>
<p>This is the editor view of the audit dashboard block.</p>
</div>
<style>
.audit-block-editor {
padding: 10px;
border: 1px dashed #ccc;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"] {
width: 100%;
padding: 8px;
margin-bottom: 10px;
box-sizing: border-box;
}
</style>
Create src/routes/frontend.svelte for the block’s frontend display:
<!-- src/routes/frontend.svelte -->
<script>
import { onMount } from 'svelte';
export let attributes = {};
let auditData = [];
let isLoading = true;
let error = null;
async function fetchAuditData() {
try {
// Replace with your actual API endpoint for audit data
const response = await fetch('/wp-json/my-audit-plugin/v1/audit-logs');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
auditData = await response.json();
} catch (e) {
error = e.message;
console.error("Failed to fetch audit data:", e);
} finally {
isLoading = false;
}
}
onMount(() => {
fetchAuditData();
// Set up polling for real-time updates
const intervalId = setInterval(fetchAuditData, 15000); // Poll every 15 seconds
// Clean up interval on component destroy
return () => clearInterval(intervalId);
});
</script>
<div class="audit-dashboard-frontend">
<h3>{attributes.title || 'Audit Dashboard'}</h3>
{#if isLoading}
<p>Loading audit data...</p>
{:else if error}
<p class="error">Error loading data: {error}</p>
{:else if auditData.length === 0}
<p>No audit data available.</p>
{:else}
<table>
<thead>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{#each auditData as log (log.id)}
<tr>
<td>{new Date(log.timestamp).toLocaleString()}</td>
<td>{log.user_login}</td>
<td>{log.action}</td>
<td>{log.details}</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
<style>
.audit-dashboard-frontend {
padding: 15px;
border: 1px solid #eee;
background-color: #f9f9f9;
}
h3 {
margin-top: 0;
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
.error {
color: red;
font-weight: bold;
}
</style>
To make the SvelteKit build output compatible with WordPress enqueueing and to generate the asset.json, we’ll create a custom build script. Add a new file, build.js, at the root of your SvelteKit project:
// build.js
import fs from 'fs-extra';
import path from 'path';
import { build } from 'vite'; // Vite is used by SvelteKit for building
const __dirname = path.resolve();
const pluginDir = path.join(__dirname, '../wp-content/plugins/my-custom-audit-plugin');
const svelteBuildDir = path.join(__dirname, 'build');
const pluginAssetsDir = path.join(pluginDir, 'assets/js');
async function runBuild() {
// 1. Run SvelteKit's build process
console.log('Running SvelteKit build...');
await build({
// Vite configuration can be passed here if needed,
// but SvelteKit's svelte.config.js should handle most of it.
// Ensure adapter-static is configured to output to 'build' directory.
});
console.log('SvelteKit build complete.');
// 2. Copy built assets to the WordPress plugin directory
console.log('Copying assets to WordPress plugin directory...');
await fs.copy(svelteBuildDir, pluginAssetsDir, {
overwrite: true,
filter: (src) => {
// Exclude source maps if not needed for production
return !src.endsWith('.map');
}
});
console.log('Assets copied.');
// 3. Generate asset.json for WordPress
console.log('Generating asset.json...');
const assetManifest = {
version: '1.0.0',
dependencies: [], // Add any external JS dependencies here if needed
file: {
editor: '',
frontend: '',
editor_style: '',
frontend_style: ''
}
};
// Find the generated JS and CSS files.
// The exact filenames will depend on Vite's hashing.
const files = await fs.readdir(pluginAssetsDir);
files.forEach(file => {
if (file.endsWith('.js') && file.includes('editor')) {
assetManifest.file.editor = file;
} else if (file.endsWith('.js') && file.includes('frontend')) {
assetManifest.file.frontend = file;
} else if (file.endsWith('.css') && file.includes('editor')) {
assetManifest.file.editor_style = file;
} else if (file.endsWith('.css') && file.includes('frontend')) {
assetManifest.file.frontend_style = file;
}
});
// Ensure we found the necessary files
if (!assetManifest.file.editor || !assetManifest.file.frontend) {
console.error('Could not find generated editor or frontend JS files. Check build output.');
process.exit(1);
}
await fs.writeJson(path.join(pluginDir, 'assets/js/build/asset.json'), assetManifest);
console.log('asset.json generated successfully.');
// 4. Clean up the intermediate 'build' directory if desired
// await fs.remove(svelteBuildDir);
// console.log('Intermediate build directory cleaned.');
console.log('Build process completed successfully!');
}
runBuild().catch(err => {
console.error('Build process failed:', err);
process.exit(1);
});
Modify your package.json to include this build script:
{
"name": "my-audit-block",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "node build.js", // This will now run our custom build script
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/adapter-static": "^2.0.0", // Or your chosen adapter
"@sveltejs/kit": "^1.0.0",
"fs-extra": "^11.0.0", // For file system operations
"path": "^0.12.7",
"svelte": "^3.0.0",
"vite": "^4.0.0"
},
"type": "module"
}
Now, when you run npm run build, SvelteKit will compile your Svelte components, and the build.js script will copy the output to the correct plugin directory and generate the asset.json file. Ensure your WordPress installation is accessible from where you are running the build command, or adjust the paths in build.js accordingly.
WordPress REST API Endpoint for Audit Data
For the frontend Svelte component to fetch real-time data, we need a WordPress REST API endpoint. Add the following code to your plugin’s main PHP file (my-custom-audit-plugin.php):
// Add this to my-custom-audit-plugin.php
/**
* Register REST API route for audit logs.
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'my-audit-plugin/v1', '/audit-logs', array(
'methods' => 'GET',
'callback' => 'my_custom_audit_plugin_get_audit_logs',
'permission_callback' => '__return_true', // Adjust permissions as needed
) );
} );
/**
* Callback function to retrieve audit logs.
* This is a placeholder; replace with actual data retrieval logic.
*/
function my_custom_audit_plugin_get_audit_logs( WP_REST_Request $request ) {
// In a real-world scenario, you would query a database table
// or a logging service to get audit data.
// For demonstration, we'll return some mock data.
$mock_logs = array(
array(
'id' => 1,
'timestamp' => current_time( 'mysql' ),
'user_login' => 'admin',
'action' => 'post_published',
'details' => 'Post "My First Post" was published.',
),
array(
'id' => 2,
'timestamp' => date( 'Y-m-d H:i:s', strtotime('-5 minutes') ),
'user_login' => 'editor',
'action' => 'post_updated',
'details' => 'Post "About Us" was updated.',
),
array(
'id' => 3,
'timestamp' => date( 'Y-m-d H:i:s', strtotime('-10 minutes') ),
'user_login' => 'admin',
'action' => 'user_login',
'details' => 'User "editor" logged in.',
),
);
// You might want to paginate, filter, or sort these logs in a real application.
$response = new WP_REST_Response( $mock_logs, 200 );
$response->set_headers( array( 'Cache-Control' => 'no-cache' ) ); // Prevent caching for real-time data
return $response;
}
This REST API endpoint, accessible at /wp-json/my-audit-plugin/v1/audit-logs, will serve the audit data to our Svelte frontend component. Remember to adjust the permission_callback for production environments to restrict access to authorized users.
Block Editor Integration and Usage
To integrate the Svelte component into the Gutenberg editor, we need to modify the PHP registration slightly or use a separate JavaScript file that bridges WordPress’s block API with our Svelte component. The current PHP setup assumes the Svelte build process outputs an `editor.js` file that can be directly enqueued. Let’s refine the PHP to correctly register the block using the WordPress `@wordpress/blocks` package.
First, ensure your SvelteKit build outputs a file specifically for the editor. In svelte.config.js, you might want to configure the output path more precisely or use a custom entry point for the editor script. For simplicity, let’s assume the `build.js` script correctly places an `editor.js` (or similar) file and updates asset.json.
The PHP registration in my-custom-audit-plugin.php already points to editor_script. The key is how the Svelte component is mounted. The src/main.js file should ideally export a function that registers the block with WordPress.
// src/main.js (Revised for direct WordPress registration)
import { registerBlockType } from '@wordpress/blocks';
import EditorComponent from './routes/editor.svelte';
import attributes from './block.json'; // We'll create this file
// Register the block
registerBlockType('my-custom-audit-block/audit-dashboard', {
edit: ({ attributes, setAttributes }) => {
// Pass props to the Svelte component
return new EditorComponent({
target: document.createElement('div'), // Temporary element
props: { attributes, setAttributes }
});
},
save: () => null, // For dynamic blocks, save returns null, rendering is handled by PHP or JS
});
Create a src/block.json file to define block attributes and metadata:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-custom-audit-block/audit-dashboard",
"version": "1.0.0",
"title": "Audit Dashboard",
"category": "widgets",
"icon": "chart-bar",
"description": "Displays real-time audit data.",
"attributes": {
"title": {
"type": "string",
"default": "Audit Dashboard"
}
},
"editorScript": "file:./build/editor.js",
"editorStyle": "file:./build/editor.css",
"style": "file:./build/frontend.css",
"render": "file:./build/frontend.js"
}
Update your svelte.config.js to correctly handle the block.json and ensure the build output paths align with what WordPress expects. The adapter-static needs to be configured to output files in a way that the `editorScript`, `editorStyle`, `style`, and `render` paths in block.json can resolve correctly relative to the plugin’s root.
// svelte.config.js (Revised for block.json and output structure)
import adapter from '@sveltejs/adapter-static';
import fs from 'fs-extra';
import path from 'path';
const __dirname = path.resolve();
const pluginDir = path.join(__dirname, '../wp-content/plugins/my-custom-audit-plugin');
const pluginAssetsDir = path.join(pluginDir, 'assets/js');
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
pages: pluginAssetsDir + '/build/pages', // Output pages here
assets: pluginAssetsDir + '/build/assets' // Output assets here
}),
// This base path is crucial for WordPress to correctly locate assets
// It should point to the root of your built assets within the plugin.
paths: {
base: '/wp-content/plugins/my-custom-audit-plugin/assets/js/build',
}
}
};
// Custom Vite plugin to copy block.json and generate asset manifest
function wordpressPlugin() {
return {
name: 'wordpress-plugin-builder',
enforce: 'post', // Run after SvelteKit's build
async writeBundle() {
const svelteBuildDir = path.join(__dirname, 'build'); // Default SvelteKit build output
// 1. Copy block.json to the plugin directory
await fs.copy(path.join(__dirname, 'src/block.json'), path.join(pluginDir, 'block.json'));
// 2. Generate asset.json
const assetManifest = {
version: '1.0.0',
dependencies: [],
file: {
editor: '',
frontend: '',
editor_style: '',
frontend_style: ''
}
};
const files = await fs.readdir(svelteBuildDir);
files.forEach(file => {
if (file.endsWith('.js') && file.includes('editor')) assetManifest.file.editor = file;
if (file.endsWith('.js') && file.includes('frontend')) assetManifest.file.frontend = file;
if (file.endsWith('.css') && file.includes('editor')) assetManifest.file.editor_style = file;
if (file.endsWith('.css') && file.includes('frontend')) assetManifest.file.frontend_style = file;
});
if (!assetManifest.file.editor || !assetManifest.file.frontend) {
console.error('Could not find generated editor or frontend JS files.');
return;
}
await fs.writeJson(path.join(pluginAssetsDir, 'build/asset.json'), assetManifest);
console.log('asset.json generated for WordPress.');
}
};
}
// Add the custom Vite plugin to SvelteKit's config
config.kit.vite = {
plugins: [
wordpressPlugin()
]
};
export default config;
With these changes, running npm run build will now:
- Compile Svelte components using Vite.
- Output the built files (JS, CSS) into the
build/directory. - Copy
block.jsonto the plugin’s root directory. - Generate
asset.jsonin the plugin’sassets/js/build/directory. - The PHP file will then use
block.jsonandasset.jsonto register the block correctly.
To use the block, navigate to the WordPress post editor, search for “Audit Dashboard,” and insert the block. The editor view will show the Svelte component defined in src/routes/editor.svelte, allowing you to set the block’s title. The frontend view will render the src/routes/frontend.svelte component, fetching and displaying audit data.
Styling and Real-time Considerations
Styling for both the editor and frontend can be managed within the Svelte components using <style> tags. These styles will be automatically processed and included in the generated CSS files. For the frontend, the frontend.svelte component includes basic table styling. Ensure these styles are appropriate for your theme or use CSS variables for better theme compatibility.
The real-time aspect is handled by the setInterval function in frontend.svelte, which polls the REST API endpoint every 15 seconds. For more advanced real-time needs (e.g., WebSockets), you would need to integrate a WebSocket server and client library, which is beyond the scope of this basic setup but could be achieved by modifying the Svelte component and potentially adding a PHP WebSocket server.
Ensure your WordPress REST API is accessible and that the /wp-json/my-audit-plugin/v1/audit-logs endpoint is functioning correctly. Debugging can be done using browser developer tools to inspect network requests and console logs.