Nairametrics Platform Evolution - Roadmap Proposal

Radicle WordPress Architecture

A comprehensive guide to the Radicle WordPress stack architecture, configuration, development workflow, and Hot Module Replacement (HMR).

Last Updated: 2025-10-11


📋 Table of Contents


Overview & Technology Stack

Radicle is a modern WordPress stack from Roots that combines several powerful tools to create a Laravel-powered WordPress development environment.

Core Components

┌─────────────────────────────────────────────────────────┐
│                    RADICLE STACK                         │
├─────────────────────────────────────────────────────────┤
│  Bedrock    → WordPress boilerplate + Composer          │
│  Acorn      → Laravel framework for WordPress           │
│  Sage       → Modern theme framework using Blade        │
│  Bud.js     → Modern asset build tool (webpack)         │
│  Tailwind   → Utility-first CSS framework               │
│  Alpine.js  → Lightweight JavaScript framework          │
└─────────────────────────────────────────────────────────┘

Technology Stack

  • PHP 8.2+ with Composer dependency management
  • Node.js with Yarn/NPM for frontend tooling
  • Laravel components (Blade, Collections, Service Container)
  • Bud.js for asset compilation and Hot Module Replacement
  • TailwindCSS for utility-first styling
  • Alpine.js for lightweight interactivity
  • Blade templating engine
  • TypeScript for type-safe JavaScript

What Makes This Different

This project uses a radically different approach to WordPress development:

Configuration over Code:

  • Settings live in config/ directory, not procedural PHP
  • Service providers bootstrap features from configuration
  • Separation of concerns: config, logic, templates, and assets

Modern Development:

  • Laravel patterns in WordPress (Service Container, Collections, Blade)
  • Component-based architecture with reusable Blade components
  • Hot Module Replacement for instant feedback
  • Modern build tooling with TypeScript and Tailwind

Better Organization:

  • WordPress core as a Composer dependency in public/wp/
  • Source files in resources/, compiled output in public/dist/
  • Application logic in app/, following Laravel conventions
  • Environment-based configuration with .env files

Directory Structure

radicle/
├── app/                          # Laravel-style application code
│   ├── Providers/               # Service providers (bootstrap features)
│   │   ├── AssetsServiceProvider.php
│   │   ├── BlocksServiceProvider.php
│   │   ├── PostTypesServiceProvider.php
│   │   └── ThemeServiceProvider.php
│   ├── View/                    # View-related classes
│   │   └── Composers/           # Share data with views
│   │       ├── App.php          # Global data (all views)
│   │       ├── Post.php         # Post-specific data
│   │       └── Comments.php     # Comments data
│   └── helpers.php              # Global helper functions
│
├── config/                      # Laravel-style configuration
│   ├── app.php                 # Application settings
│   ├── assets.php              # Asset loading config
│   ├── blade-icons.php         # Icon system config
│   ├── filesystems.php         # File storage
│   ├── post-types.php          # Custom post types
│   ├── prettify.php            # URL rewriting
│   ├── theme.php               # Theme features, menus, sidebars
│   └── view.php                # Blade template settings
│
├── database/                    # Database operations
│   ├── migrations/             # Database migrations
│   └── seeders/                # Database seeders
│
├── public/                      # WEB ROOT (document root)
│   ├── content/                # wp-content replacement
│   │   ├── plugins/            # WordPress plugins
│   │   ├── themes/             # WordPress themes
│   │   │   └── radicle/        # Theme stub (minimal)
│   │   │       ├── index.php   # Single entry point
│   │   │       └── style.css   # Theme metadata only
│   │   ├── mu-plugins/         # Must-use plugins
│   │   └── uploads/            # Media uploads
│   ├── dist/                   # COMPILED ASSETS (auto-generated)
│   │   ├── css/
│   │   │   ├── app.[hash].css
│   │   │   └── editor.[hash].css
│   │   ├── js/
│   │   │   ├── app.[hash].js
│   │   │   ├── editor.[hash].js
│   │   │   └── runtime.[hash].js
│   │   ├── images/             # Optimized images
│   │   ├── entrypoints.json    # Asset manifest
│   │   ├── manifest.json       # Full asset manifest
│   │   └── theme.json          # Gutenberg config
│   ├── wp/                     # WordPress core (Composer managed)
│   │   ├── wp-admin/
│   │   ├── wp-includes/
│   │   └── ...
│   ├── index.php               # Application entry point
│   └── wp-config.php           # WordPress config (Bedrock)
│
├── resources/                   # SOURCE FILES (not served directly)
│   ├── fonts/                  # Font files
│   ├── images/                 # Source images
│   │   └── icons/              # SVG icons
│   ├── scripts/                # JavaScript/TypeScript source
│   │   ├── app.ts              # Main frontend entry
│   │   ├── editor.ts           # Block editor entry
│   │   └── editor/             # Block editor scripts
│   ├── styles/                 # CSS source
│   │   ├── app.css             # Main frontend styles
│   │   └── editor.css          # Block editor styles
│   └── views/                  # BLADE TEMPLATES
│       ├── blocks/             # Custom Gutenberg blocks
│       ├── components/         # Reusable UI components
│       ├── forms/              # Form templates
│       ├── layouts/            # Base layouts
│       │   └── app.blade.php   # Main layout
│       ├── partials/           # Template partials
│       ├── sections/           # Page sections (header, footer)
│       ├── utils/              # Utility templates
│       ├── 404.blade.php       # 404 error page
│       ├── index.blade.php     # Blog index
│       ├── single.blade.php    # Single post
│       ├── page.blade.php      # Page template
│       └── ...                 # Other templates
│
├── routes/                      # Application routes (experimental)
│
├── storage/                     # Cache, logs, compiled views
│   ├── framework/              # Framework cache
│   │   ├── cache/              # Application cache
│   │   └── views/              # Compiled Blade templates
│   └── logs/                   # Application logs
│
├── vendor/                      # Composer dependencies (auto-generated)
├── node_modules/                # NPM dependencies (auto-generated)
│
├── .env                         # Environment configuration (NOT in git)
├── .env.example                 # Example environment file
├── bud.config.ts                # Bud.js build configuration
├── composer.json                # PHP dependencies
├── package.json                 # Node dependencies
├── tailwind.config.ts           # Tailwind configuration
└── wp-cli.yml                   # WP-CLI configuration

Important Paths

Path URL Description
public/ / Web root
public/wp/ /wp/ WordPress core
public/content/ /content/ Media & plugins
public/dist/ /dist/ Compiled assets

Core Configuration Files

1. composer.json - PHP Dependencies

Manages PHP dependencies and WordPress core installation.

{
  "require": {
    "php": ">=8.2",
    "roots/wordpress": "6.6.1",
    "roots/acorn": "^4.0",
    "blade-ui-kit/blade-icons": "^1.5",
    "roots/bedrock-autoloader": "^1.0"
  },
  "extra": {
    "acorn": {
      "providers": [
        "App\\Providers\\AssetsServiceProvider",
        "App\\Providers\\BlocksServiceProvider",
        "App\\Providers\\ThemeServiceProvider",
        "App\\Providers\\PostTypesServiceProvider"
      ]
    },
    "installer-paths": {
      "public/content/plugins/{$name}/": ["type:wordpress-plugin"],
      "public/content/themes/{$name}/": ["type:wordpress-theme"]
    },
    "wordpress-install-dir": "public/wp"
  }
}

Key Features:

  • WordPress core lives in public/wp/
  • Service Providers are auto-registered via extra.acorn.providers
  • Plugins/themes installed via Composer go to public/content/
  • Autoloader follows PSR-4 standard

2. bud.config.ts - Build Configuration

Configures asset compilation, bundling, and WordPress integration.

export default async (bud: Bud) => {
  bud
    .proxy(`http://radicle.test`) // Development proxy URL
    .serve(`http://localhost:4000`) // Dev server port
    .watch([bud.path(`resources/views`), bud.path(`app`)])

    // Entry points for compilation
    .entry(`app`, [`@scripts/app`, `@styles/app`])
    .entry(`editor`, [`@scripts/editor`, `@styles/editor`])

    .copyDir(`images`) // Copy static assets
    .setPublicPath(`/dist/`) // Output directory

    // WordPress Block Editor (Gutenberg) integration
    .wpjson.setSettings({
      /* theme.json settings */
    });
};

Entry Points:

  • app: Main frontend bundle (JavaScript + CSS)
  • editor: WordPress block editor styles and scripts

Build Output:

public/dist/
├── js/
│   ├── app.[hash].js
│   ├── editor.[hash].js
│   └── runtime.[hash].js
├── css/
│   ├── app.[hash].css
│   └── editor.[hash].css
└── images/

Aliases:

  • @scriptsresources/scripts/
  • @stylesresources/styles/
  • @imagesresources/images/

3. .env - Environment Configuration

Stores environment-specific configuration and sensitive data.

# Database Configuration
DB_NAME='radicle'
DB_USER='radicle'
DB_PASSWORD='radicle'
DB_HOST='localhost'
DB_PREFIX='wp_'

# WordPress URLs
WP_ENV='development'                    # development, staging, production
WP_HOME='http://localhost:8000'         # Your site URL
WP_SITEURL="${WP_HOME}/wp"             # WordPress core URL

# Security Keys (generate at: https://roots.io/salts.html)
AUTH_KEY='generateme'
SECURE_AUTH_KEY='generateme'
LOGGED_IN_KEY='generateme'
NONCE_KEY='generateme'
AUTH_SALT='generateme'
SECURE_AUTH_SALT='generateme'
LOGGED_IN_SALT='generateme'
NONCE_SALT='generateme'

# Acorn Features
ACORN_ENABLE_EXPIRIMENTAL_ROUTER='True'

Environment Modes:

  • development: Debug enabled, error reporting on
  • staging: Similar to production, but with some debug features
  • production: Debug off, error reporting off, caching enabled

⚠️ Security: Never commit .env to version control!


Request Lifecycle

Complete Request Flow

┌──────────────────────────────────────────────────────────────────┐
│ 1. HTTP REQUEST                                                   │
│    User visits: http://localhost:8000/sample-page                │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 2. WEB SERVER                                                     │
│    • Routes request to public/index.php                           │
│    • Document root is public/                                     │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 3. BEDROCK BOOTSTRAP (public/index.php)                           │
│    • Load .env variables via vlucas/phpdotenv                    │
│    • Set WordPress constants (DB_NAME, WP_HOME, etc.)            │
│    • Require vendor/autoload.php (Composer autoloader)           │
│    • Set ABSPATH to public/wp/                                   │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 4. WORDPRESS BOOTSTRAP (public/wp/wp-blog-header.php)             │
│    • Load WordPress core files                                   │
│    • Connect to database                                         │
│    • Load activated plugins                                      │
│    • Load active theme (Radicle)                                 │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 5. ACORN BOOTSTRAP (Laravel)                                      │
│    • Initialize Acorn application                                │
│    • Load service container                                      │
│    • Register service providers:                                 │
│      ├─ ThemeServiceProvider                                     │
│      ├─ AssetsServiceProvider                                    │
│      ├─ BlocksServiceProvider                                    │
│      └─ PostTypesServiceProvider                                 │
│    • Boot all providers                                          │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 6. WORDPRESS ROUTING                                              │
│    • Determine post type/page based on URL                       │
│    • Apply template hierarchy:                                   │
│      page-{slug}.blade.php                                       │
│      page-{id}.blade.php                                         │
│      page.blade.php                                              │
│      singular.blade.php                                          │
│      index.blade.php                                             │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 7. SAGE TEMPLATE LOADER                                           │
│    • WordPress tries to load: page.php                           │
│    • Sage intercepts and looks for: page.blade.php              │
│    • Found at: resources/views/page.blade.php                   │
│    • Theme stub delegates to Acorn:                             │
│      echo view(app('sage.view'), app('sage.data'))->render();   │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 8. BLADE TEMPLATE ENGINE                                          │
│    • Find appropriate Blade template                             │
│    • Compile Blade → PHP (if not cached)                         │
│    • Cache compiled template in storage/framework/views/         │
│    • Execute compiled PHP template                               │
│    • Process @extends, @include, @yield directives               │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 9. VIEW COMPOSERS RUN                                             │
│    • App\View\Composers\App runs (applies to all views)         │
│    • Adds data to view: siteName, containerClasses, etc.        │
│    • Other composers run based on view name                     │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 10. ASSET INJECTION                                               │
│    • AssetsServiceProvider reads entrypoints.json                │
│    • Enqueues compiled CSS/JS with correct paths                 │
│    • wp_head() outputs <link> and <script> tags                  │
│    • wp_footer() outputs footer scripts                          │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 11. HTML RESPONSE                                                 │
│    • Complete HTML page generated                                │
│    • Sent to browser                                             │
│    • Assets loaded from /dist/                                   │
└──────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────┐
│ 12. CLIENT-SIDE (Browser)                                         │
│    • Parse HTML                                                  │
│    • Load CSS from /dist/css/                                    │
│    • Execute JavaScript from /dist/js/                           │
│    • Initialize Alpine.js                                        │
│    • Page interactive                                            │
└──────────────────────────────────────────────────────────────────┘

Cache Locations

Blade Templates:

storage/framework/views/
└── [hash].php  (compiled Blade templates)

Acorn Cache:

storage/framework/cache/
├── config.php    (compiled configuration)
└── [other caches]

WordPress Object Cache:

public/content/cache/  (if plugin installed)

Service Providers

Service providers are the central place to bootstrap application features. They run early in the WordPress lifecycle and organize your application’s initialization logic.

How Service Providers Work

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class MyServiceProvider extends ServiceProvider
{
    /**
     * Register services
     * Runs FIRST, before all providers are booted
     */
    public function register()
    {
        // Register bindings in the service container
        // Set up configuration
        // Register WordPress hooks
    }

    /**
     * Bootstrap services
     * Runs SECOND, after all providers are registered
     */
    public function boot()
    {
        // Code that depends on other providers
        // Additional WordPress hooks
    }
}

Service providers are auto-loaded from composer.jsonextra.acorn.providers:

  1. register() method runs early in WordPress lifecycle
  2. boot() method runs after all providers are registered
  3. Uses Laravel Collections for elegant array manipulation
  4. Reads configuration from config/ files

ThemeServiceProvider

Location: app/Providers/ThemeServiceProvider.php

Handles theme features, menus, sidebars, and image sizes.

<?php

namespace App\Providers;

use Illuminate\Support\Collection;
use Roots\Acorn\Sage\SageServiceProvider;

class ThemeServiceProvider extends SageServiceProvider
{
    public function register()
    {
        parent::register();

        // Register theme features
        add_action('after_setup_theme', function (): void {
            // Add theme support from config
            Collection::make(config('theme.support'))
                ->map(fn ($params, $feature) =>
                    is_array($params) ? [$feature, $params] : [$params])
                ->each(fn ($params) => add_theme_support(...$params));

            // Remove theme support
            Collection::make(config('theme.remove'))
                ->each(fn ($params) => remove_theme_support(...$params));

            // Register navigation menus
            register_nav_menus(config('theme.menus'));

            // Register custom image sizes
            Collection::make(config('theme.image_sizes'))
                ->each(fn ($params, $name) => add_image_size($name, ...$params));
        }, 20);

        // Register sidebars/widget areas
        add_action('widgets_init', function (): void {
            Collection::make(config('theme.sidebar.register'))
                ->each(fn ($instance) => register_sidebar(
                    array_merge(config('theme.sidebar.config'), $instance)
                ));
        });
    }
}

What it does:

  • ✅ Reads configuration from config/theme.php
  • ✅ Registers theme support (post-thumbnails, html5, etc.)
  • ✅ Registers navigation menus
  • ✅ Registers sidebars/widget areas
  • ✅ Registers custom image sizes
  • ✅ Uses Laravel Collections for elegant code

AssetsServiceProvider

Location: app/Providers/AssetsServiceProvider.php

Automatically enqueues compiled assets from the build process.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use function Roots\bundle;

class AssetsServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Enqueue frontend assets
        add_action('wp_enqueue_scripts', function (): void {
            // Automatically reads from public/dist/entrypoints.json
            bundle('app')->enqueue();

            // Optional: Remove WordPress default styles
            remove_action('wp_body_open', 'wp_global_styles_render_svg_filters');
        }, 100);

        // Enqueue block editor assets
        add_action('enqueue_block_editor_assets', function (): void {
            bundle('editor')->enqueue();
        }, 100);

        // Add type="module" to .mjs scripts
        add_filter('script_loader_tag', function (string $tag): string {
            if (str_contains($tag, '.mjs"') && !str_contains($tag, 'type="module"')) {
                return str_replace(' src=', ' type=module src=', $tag);
            }
            return $tag;
        }, 10, 2);

        // Use theme.json from dist/ directory
        add_filter('theme_file_path', function (string $path, string $file): string {
            if ($file === 'theme.json') {
                return public_path() . '/dist/theme.json';
            }
            return $path;
        }, 10, 2);
    }
}

What it does:

  • ✅ Automatically enqueues compiled assets from public/dist/
  • ✅ Reads asset manifest (entrypoints.json)
  • ✅ Handles cache-busting hashes
  • ✅ Manages dependencies automatically
  • ✅ Integrates with Gutenberg block editor

The Magic:

bundle('app')->enqueue();

This one line:

  1. Reads public/dist/entrypoints.json
  2. Finds all assets for the ‘app’ entry
  3. Enqueues CSS files with wp_enqueue_style()
  4. Enqueues JS files with wp_enqueue_script()
  5. Handles dependencies automatically
  6. Adds integrity hashes

PostTypesServiceProvider

Location: app/Providers/PostTypesServiceProvider.php

Registers custom post types and taxonomies from configuration.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class PostTypesServiceProvider extends ServiceProvider
{
    public function register()
    {
        add_action('init', function (): void {
            // Register custom post types from config
            collect(config('post-types.register'))
                ->each(fn ($args, $name) => register_post_type($name, $args));

            // Register taxonomies from config
            collect(config('post-types.taxonomies'))
                ->each(fn ($args, $name) => register_taxonomy($name, ...$args));
        });
    }
}

What it does:

  • ✅ Reads configuration from config/post-types.php
  • ✅ Registers custom post types
  • ✅ Registers custom taxonomies

BlocksServiceProvider

Location: app/Providers/BlocksServiceProvider.php

Registers custom Gutenberg blocks.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class BlocksServiceProvider extends ServiceProvider
{
    public function boot()
    {
        add_action('acorn/blocks.register', function () {
            // Register custom Gutenberg blocks
            // Blocks are defined in resources/views/blocks/
        });
    }
}

What it does:

  • ✅ Registers custom Gutenberg blocks
  • ✅ Blocks use Blade templates (not React JSX)

Blade Templating System

Blade is Laravel’s powerful templating engine that replaces traditional PHP templates with cleaner, more expressive syntax.

Template Hierarchy

WordPress template hierarchy works with .blade.php extension:

WordPress Template Hierarchy → Blade Templates

Single Post:
single-{post-type}-{slug}.php   →   single-{post-type}-{slug}.blade.php
single-{post-type}.php          →   single-{post-type}.blade.php
single.php                      →   single.blade.php
singular.php                    →   singular.blade.php
index.php                       →   index.blade.php

Page:
page-{slug}.php                 →   page-{slug}.blade.php
page-{id}.php                   →   page-{id}.blade.php
page.php                        →   page.blade.php
singular.php                    →   singular.blade.php
index.php                       →   index.blade.php

Archive:
archive-{post-type}.php         →   archive-{post-type}.blade.php
archive.php                     →   archive.blade.php
index.php                       →   index.blade.php

Category:
category-{slug}.php             →   category-{slug}.blade.php
category-{id}.php               →   category-{id}.blade.php
category.php                    →   category.blade.php
archive.php                     →   archive.blade.php
index.php                       →   index.blade.php

Blade Syntax Reference

{{-- Comments (not rendered in HTML) --}}

{{-- Echo escaped output (safe) --}}
{{ $variable }}
{{ get_the_title() }}

{{-- Echo raw HTML (be careful!) --}}
{!! $html !!}

{{-- Conditionals --}}
@if ($show)
    <p>Visible</p>
@elseif ($maybe)
    <p>Maybe visible</p>
@else
    <p>Not visible</p>
@endif

@unless ($hide)
    <p>Shown unless hide is true</p>
@endunless

@isset($variable)
    <p>Variable is set</p>
@endisset

@empty($variable)
    <p>Variable is empty</p>
@endempty

{{-- Loops --}}
@foreach ($items as $item)
    <p>{{ $item }}</p>
@endforeach

@forelse ($items as $item)
    <p>{{ $item }}</p>
@empty
    <p>No items</p>
@endforelse

@while (have_posts())
    @php(the_post())
    <p>{{ get_the_title() }}</p>
@endwhile

@for ($i = 0; $i < 10; $i++)
    <p>Iteration {{ $i }}</p>
@endfor

{{-- Include other templates --}}
@include('partials.sidebar')
@include('partials.content', ['post' => $post])

{{-- Conditional includes --}}
@includeIf('partials.optional')
@includeWhen($condition, 'partials.conditional')
@includeUnless($condition, 'partials.unless')

{{-- Layout inheritance --}}
@extends('layouts.app')

@section('content')
    <p>Page content</p>
@endsection

@section('sidebar')
    <p>Sidebar content</p>
@endsection

{{-- In parent layout --}}
@yield('content')
@yield('sidebar', '<p>Default sidebar</p>')

{{-- PHP execution --}}
@php
    $items = get_posts();
    $count = count($items);
@endphp

{{-- Or single line --}}
@php(the_post())
@php(wp_head())

{{-- Blade components --}}
<x-alert type="success">
    Operation successful!
</x-alert>

<x-article-card :post="$post" class="featured" />

Layout Example

Base Layout: resources/views/layouts/app.blade.php

<!doctype html>
<html @php(language_attributes())>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    @php(do_action('get_header'))
    @php(wp_head())
</head>

<body @php(body_class())>
    @php(wp_body_open())

    <div id="app">
        {{-- Skip to content link --}}
        <a class="sr-only focus:not-sr-only" href="#main">
            {{ __('Skip to content') }}
        </a>

        {{-- Header --}}
        @include('sections.header')

        {{-- Main content area --}}
        <main id="main" class="min-h-screen">
            @yield('content')
        </main>

        {{-- Footer --}}
        @include('sections.footer')
    </div>

    @php(do_action('get_footer'))
    @php(wp_footer())
</body>

</html>

Page Template: resources/views/page.blade.php

@extends('layouts.app')

@section('content')
    @while (have_posts())
        @php(the_post())

        <article @php(post_class())>
            <header>
                <h1>{{ get_the_title() }}</h1>
            </header>

            <div class="prose prose-lg max-w-none">
                @php(the_content())
            </div>
        </article>
    @endwhile
@endsection

Asset Pipeline & Build Process

How Assets Flow

┌─────────────────────────────────────────────────────────────┐
│ 1. SOURCE FILES (resources/)                                 │
├─────────────────────────────────────────────────────────────┤
│   scripts/                                                   │
│   ├── app.ts              → Main frontend JavaScript        │
│   ├── editor.ts           → Block editor JavaScript         │
│   └── modules/            → JavaScript modules              │
│                                                              │
│   styles/                                                    │
│   ├── app.css             → Main frontend CSS (Tailwind)    │
│   └── editor.css          → Block editor CSS                │
│                                                              │
│   images/                                                    │
│   └── *.svg, *.png        → Images and icons                │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 2. BUD.JS BUILD TOOL                                         │
├─────────────────────────────────────────────────────────────┤
│   Processes:                                                 │
│   • TypeScript → JavaScript (via SWC compiler)              │
│   • PostCSS → CSS (Tailwind, Autoprefixer, Minify)          │
│   • Image optimization                                      │
│   • Code splitting and tree-shaking                         │
│   • Cache-busting hashes                                    │
│   • Source maps (development)                               │
│   • Minification (production)                               │
│                                                              │
│   Generates:                                                 │
│   • entrypoints.json  (asset manifest)                      │
│   • manifest.json     (full manifest)                       │
│   • theme.json        (Gutenberg config)                    │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 3. COMPILED OUTPUT (public/dist/)                            │
├─────────────────────────────────────────────────────────────┤
│   css/                                                       │
│   ├── app.e1b9e6.css       (hashed for cache-busting)       │
│   └── editor.c0c299.css                                     │
│                                                              │
│   js/                                                        │
│   ├── runtime.215685.js    (webpack runtime)                │
│   ├── app.a02500.js        (your app code)                  │
│   └── editor.56f8b5.js     (block editor code)              │
│                                                              │
│   images/                  (optimized)                       │
│                                                              │
│   entrypoints.json         (tells WP what to load)          │
│   manifest.json            (full asset list)                │
│   theme.json               (Gutenberg configuration)        │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 4. ASSETS SERVICE PROVIDER                                   │
├─────────────────────────────────────────────────────────────┤
│   add_action('wp_enqueue_scripts', function() {             │
│       bundle('app')->enqueue();                             │
│   });                                                        │
│                                                              │
│   Reads entrypoints.json and automatically enqueues assets  │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ 5. WORDPRESS RENDERS HTML                                    │
├─────────────────────────────────────────────────────────────┤
│   <head>                                                     │
│       <link rel="stylesheet" href="/dist/css/app.e1b9e6.css">│
│   </head>                                                    │
│   <body>                                                     │
│       ...content...                                          │
│       <script src="/dist/js/runtime.215685.js"></script>    │
│       <script src="/dist/js/app.a02500.js"></script>        │
│   </body>                                                    │
└─────────────────────────────────────────────────────────────┘

Build Commands

# Development build (watch mode with HMR)
yarn dev

# Production build (optimized and minified)
yarn build

# Clean build cache
npx bud clean

# Check for build tool updates
npx bud upgrade

entrypoints.json Example

{
  "app": {
    "js": ["js/runtime.215685.js", "js/app.a02500.js"],
    "css": ["css/app.e1b9e6.css"],
    "dependencies": []
  },
  "editor": {
    "js": ["js/runtime.215685.js", "js/editor.56f8b5.js"],
    "css": ["css/editor.c0c299.css"],
    "dependencies": ["react", "wp-blocks", "wp-i18n"]
  }
}

Hot Module Replacement (HMR)

Hot Module Replacement allows you to see code changes in the browser without a full page reload. This dramatically speeds up development by preserving application state and providing instant feedback.

What is HMR?

Hot Module Replacement updates your code in the browser without requiring a full page reload:

Instant feedback - See changes in milliseconds
Preserved state - Forms, scroll position, app state stay intact
Better DX - Stay in flow without waiting for reloads
Faster iteration - Test changes 10-20x faster

HMR Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Terminal 1: WordPress Server                                     │
├─────────────────────────────────────────────────────────────────┤
│ $ php -S localhost:8080 -t public/                              │
│                                                                  │
│ Serves: WordPress, PHP, Database                                │
│ URL: http://localhost:8080                                      │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│ Terminal 2: Bud.js Development Server (HMR)                      │
├─────────────────────────────────────────────────────────────────┤
│ $ yarn dev                                                       │
│                                                                  │
│ Serves: Asset compilation + WebSocket server                    │
│ Dev Server: http://localhost:4000                               │
│ Proxy URL: http://localhost:8080 (WordPress)                    │
│                                                                  │
│ Watches:                                                        │
│   - resources/scripts/**/*.ts                                   │
│   - resources/styles/**/*.css                                   │
│   - resources/views/**/*.blade.php                              │
│   - app/**/*.php                                                │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│ Browser: http://localhost:4000                                   │
├─────────────────────────────────────────────────────────────────┤
│ 1. Opens http://localhost:4000 (Bud.js proxy)                   │
│ 2. Bud.js proxies requests to http://localhost:8080 (WordPress) │
│ 3. WordPress returns HTML with <script> tags                    │
│ 4. Scripts include HMR client (@roots/wordpress-hmr)            │
│ 5. HMR client opens WebSocket to localhost:4000                 │
│ 6. Listens for update notifications                             │
└─────────────────────────────────────────────────────────────────┘

How to Use HMR

Step 1: Start WordPress Server

cd ~/Projects/radicle
php -S localhost:8080 -t public/

Step 2: Start Bud.js HMR Server

cd ~/Projects/radicle
yarn dev

Step 3: Visit the Bud.js Dev Server URL

✅ CORRECT:  http://localhost:4000
❌ WRONG:    http://localhost:8080

Why? The Bud.js server at :4000 proxies requests to WordPress at :8080 AND injects the HMR client script.

What Gets Hot-Reloaded?

✅ Full HMR Support (No Page Reload)

CSS/Styles:

  • Changes to resources/styles/app.css hot-reload instantly
  • Tailwind class changes update without refresh
  • CSS swaps out in < 100ms

JavaScript Modules:

  • TypeScript/JavaScript module changes can hot-swap
  • State preserved (if module accepts HMR)
  • Updates in 1-2 seconds

⚠️ Partial HMR Support (Browser Refresh Required)

Blade Templates:

  • Bud.js detects changes → triggers browser refresh
  • Fast but not “hot” (still requires page reload)
  • Why? PHP templates run server-side

Service Providers (PHP):

  • Bud.js detects changes → triggers browser refresh
  • Requires WordPress to reinitialize

❌ No HMR Support (Manual Action Required)

Configuration Files:

  • Changes to config/*.php require: wp acorn optimize:clear
  • Why? Config files are cached by Laravel/Acorn

Composer Dependencies:

  • Requires restart of PHP server and clear caches

HMR Configuration

The HMR configuration is in bud.config.ts:

export default async (bud: Bud) => {
  const wpHome = process.env.WP_HOME ?? `http://localhost:8080`;

  bud
    // PROXY: WordPress server URL
    .proxy(wpHome)

    // SERVE: HMR development server URL
    .serve(`http://localhost:4000`)

    // WATCH: File patterns to monitor for changes
    .watch([
      bud.path(`resources/views`), // Blade templates
      bud.path(`app`), // PHP service providers
    ])

    // ENTRY POINTS
    .entry(`app`, [`@scripts/app`, `@styles/app`])
    .entry(`editor`, [`@scripts/editor`, `@styles/editor`]);
};

HMR Client Implementation

The HMR client is automatically included in your bundles. In your entry file:

// resources/scripts/app.ts

// At the end of your entry file:
if (import.meta.webpackHot) {
  import.meta.webpackHot.accept(console.error);
}

This tells the HMR client to:

  • Accept hot updates for this module
  • Log any errors to console
  • Reconnect if WebSocket drops

Ports & URLs

Service URL Purpose
WordPress http://localhost:8080 PHP server (backend)
Bud.js HMR http://localhost:4000 Dev server (visit this!)
WebSocket ws://localhost:4000 HMR communication

Configuration System

Configuration files live in the config/ directory and are loaded by Acorn. All configuration files return PHP arrays.

config/theme.php

Theme features, menus, sidebars, and image sizes.

<?php

return [
    /**
     * Navigation menus
     */
    'menus' => [
        'primary_navigation' => __('Primary Navigation', 'radicle'),
        'footer_navigation' => __('Footer Navigation', 'radicle'),
    ],

    /**
     * Custom image sizes
     */
    'image_sizes' => [
        'article-thumb' => [400, 250, true],
        'article-large' => [1200, 675, true],
        'article-featured' => [1600, 900, true],
    ],

    /**
     * Sidebars/widget areas
     */
    'sidebar' => [
        'register' => [
            ['name' => __('Primary Sidebar', 'radicle'), 'id' => 'sidebar-primary'],
            ['name' => __('Footer', 'radicle'), 'id' => 'sidebar-footer']
        ],
        'config' => [
            'before_widget' => '<section class="widget %1$s %2$s">',
            'after_widget' => '</section>',
            'before_title' => '<h3>',
            'after_title' => '</h3>'
        ],
    ],

    /**
     * Theme support
     */
    'support' => [
        'html5' => [
            'caption',
            'comment-form',
            'comment-list',
            'gallery',
            'search-form',
            'script',
            'style',
        ],
        'align-wide',
        'title-tag',
        'post-thumbnails',
        'responsive-embeds',
        'editor-styles',
        'wp-block-styles',
    ],

    /**
     * Remove theme support
     */
    'remove' => [
        'block-templates',
        'core-block-patterns',
    ],
];

Access via:

config('theme.menus');           // Returns menus array
config('theme.support');         // Returns support array
config('theme.sidebar.register'); // Returns sidebar array

config/post-types.php

Custom post types and taxonomies.

<?php

return [
    /**
     * Register custom post types
     */
    'register' => [
        'portfolio' => [
            'public' => true,
            'labels' => [
                'name' => __('Portfolio'),
                'singular_name' => __('Portfolio Item'),
            ],
            'menu_icon' => 'dashicons-portfolio',
            'supports' => ['title', 'editor', 'thumbnail'],
            'has_archive' => true,
            'show_in_rest' => true,
        ],
    ],

    /**
     * Register taxonomies
     */
    'taxonomies' => [
        // Define custom taxonomies here
    ],
];

config/assets.php

Asset loading configuration.

<?php

return [
    'defer' => true,              // Defer JavaScript loading
    'async' => false,             // Async JavaScript loading
];

View Composers

View composers allow you to share data with views automatically without manually passing it each time.

Example: App Composer

Location: app/View/Composers/App.php

<?php

namespace App\View\Composers;

use Roots\Acorn\View\Composer;

class App extends Composer
{
    /**
     * Views this composer applies to
     * '*' = all views
     */
    protected static $views = ['*'];

    /**
     * Data to pass to views
     */
    public function with()
    {
        return [
            'siteName' => $this->siteName(),
            'siteDescription' => $this->siteDescription(),
            'currentYear' => date('Y'),
        ];
    }

    public function siteName()
    {
        return get_bloginfo('name', 'display');
    }

    public function siteDescription()
    {
        return get_bloginfo('description', 'display');
    }
}

Result: ALL views now have access to:

{{ $siteName }}
{{ $siteDescription }}
{{ $currentYear }}

Example: Post Composer

<?php

namespace App\View\Composers;

use Roots\Acorn\View\Composer;

class Post extends Composer
{
    /**
     * Only applies to these views
     */
    protected static $views = [
        'single',
        'partials.content-single',
    ];

    public function with()
    {
        return [
            'author' => $this->author(),
            'readTime' => $this->readTime(),
        ];
    }

    public function author()
    {
        return get_the_author_meta('display_name');
    }

    public function readTime()
    {
        $content = get_the_content();
        $word_count = str_word_count(strip_tags($content));
        $reading_time = ceil($word_count / 200);

        return $reading_time . ' min read';
    }
}

Blade Components

Blade components are reusable UI elements that can accept props and slots.

Creating a Component

File: resources/views/components/alert.blade.php

@props([
    'type' => 'info'
])

@php
$classes = [
    'alert',
    'p-4',
    'rounded-lg',
    'border',
    match($type) {
        'success' => 'bg-green-100 border-green-500 text-green-800',
        'error' => 'bg-red-100 border-red-500 text-red-800',
        'warning' => 'bg-yellow-100 border-yellow-500 text-yellow-800',
        default => 'bg-blue-100 border-blue-500 text-blue-800',
    }
];
@endphp

<div {{ $attributes->class($classes) }}>
    {{ $slot }}
</div>

Usage:

<x-alert type="success">
    Post saved successfully!
</x-alert>

<x-alert type="error" class="mb-4">
    An error occurred.
</x-alert>

Component with Props

File: resources/views/components/article-card.blade.php

@props([
    'post',
    'showExcerpt' => true,
    'showAuthor' => false,
])

<article {{ $attributes->class(['article-card', 'bg-white', 'rounded-lg', 'shadow']) }}>
    @if (has_post_thumbnail($post))
        <a href="{{ get_permalink($post) }}">
            {{ get_the_post_thumbnail($post, 'article-thumb', ['class' => 'rounded-t-lg']) }}
        </a>
    @endif

    <div class="p-4">
        <h3 class="text-xl font-bold mb-2">
            <a href="{{ get_permalink($post) }}">
                {{ get_the_title($post) }}
            </a>
        </h3>

        @if ($showExcerpt)
            <p class="text-gray-600 mb-4">
                {{ get_the_excerpt($post) }}
            </p>
        @endif

        @if ($showAuthor)
            <p class="text-sm text-gray-500">
                By {{ get_the_author_meta('display_name', $post->post_author) }}
            </p>
        @endif
    </div>
</article>

Usage:

{{-- Basic usage --}}
<x-article-card :post="$post" />

{{-- With props --}}
<x-article-card
    :post="$post"
    :showAuthor="true"
    :showExcerpt="false"
    class="featured"
/>

{{-- In a loop --}}
@foreach ($posts as $post)
    <x-article-card :post="$post" />
@endforeach

Development Workflow

Local Development Setup

# 1. Clone repository (if needed)
git clone <repository-url>
cd radicle

# 2. Install dependencies
composer install
yarn install

# 3. Configure environment
cp .env.example .env
# Edit .env with your database credentials and URLs

# 4. Create database
mysql -u root -p
CREATE DATABASE radicle;
CREATE USER 'radicle'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON radicle.* TO 'radicle'@'localhost';
FLUSH PRIVILEGES;

# 5. Build assets
yarn build

# 6. Start development servers
# Terminal 1:
php -S localhost:8080 -t public/

# Terminal 2:
yarn dev

Daily Development

# Terminal 1: PHP Server
php -S localhost:8080 -t public/

# Terminal 2: Asset compilation with HMR
yarn dev

# Visit: http://localhost:4000

Making Changes

Template Changes:

# Edit Blade templates
resources/views/page.blade.php

# Changes are reflected immediately (no compilation needed)
# Browser refreshes automatically

Style Changes:

# Edit CSS files
resources/styles/app.css

# Bud.js auto-compiles → public/dist/css/app.[hash].css
# HMR injects changes (no page reload)

JavaScript Changes:

# Edit TypeScript files
resources/scripts/app.ts

# Bud.js auto-compiles → public/dist/js/app.[hash].js
# HMR updates module (preserves state)

Configuration Changes:

# Edit config files
config/theme.php

# Requires cache clear:
wp acorn optimize:clear

PHP Code Changes:

# Edit service providers or app code
app/Providers/ThemeServiceProvider.php

# Requires cache clear:
wp acorn optimize:clear

Production Deployment

# 1. Build optimized assets
yarn build

# 2. Install production dependencies only
composer install --no-dev --optimize-autoloader

# 3. Optimize Acorn cache
wp acorn optimize

# 4. Cache icons (if using Blade Icons)
wp acorn icons:cache

# 5. Deploy to server
# (Copy files, excluding node_modules, .git, tests, etc.)

Troubleshooting

HMR Not Working

Symptoms: Changes don’t appear, or page always reloads

Checklist:

  1. Are you visiting the correct URL?

    ✅ CORRECT: http://localhost:4000 (Bud.js proxy)
    ❌ WRONG:   http://localhost:8080 (WordPress direct)
    
  2. Is the HMR server running?

    # Check Terminal 2 - should show:
    ➜ App    http://localhost:4000
    ➜ Proxy  http://localhost:8080
    
  3. Check browser console for errors:

    // Look for WebSocket connection:
    [HMR] Waiting for update signal from WDS...
    [WDS] Hot Module Replacement enabled.
    
  4. Is the WordPress server running?

    # Check Terminal 1 - should be running:
    php -S localhost:8080 -t public/
    
  5. Check .env configuration:

    # .env should have:
    WP_HOME='http://localhost:8080'
    WP_ENV='development'
    

WebSocket Connection Failed

Symptoms: Console shows WebSocket connection to 'ws://localhost:4000/' failed

Solutions:

  1. Firewall blocking port 4000:

    # Temporarily allow port (Nobara/Fedora):
    sudo firewall-cmd --add-port=4000/tcp
    
  2. Port already in use:

    # Find what's using port 4000:
    lsof -i :4000
    
    # Kill the process or change port in bud.config.ts:
    .serve(`http://localhost:5000`)  // Different port
    
  3. Restart Bud.js:

    # Ctrl+C to stop, then:
    yarn dev
    

Changes Not Detected

Symptoms: File changes don’t trigger rebuild

Solutions:

  1. File not in watch list:

    // bud.config.ts
    bud.watch([
      bud.path(`resources/views`),
      bud.path(`app`),
      bud.path(`config`), // ADD THIS if editing configs
    ]);
    
  2. Too many files (inotify limit on Linux):

    # Increase file watch limit:
    echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
    sudo sysctl -p
    
  3. Clear Bud.js cache:

    npx bud clean
    yarn dev
    

Styles Not Applying

Symptoms: CSS changes compile but don’t appear in browser

Solutions:

  1. Browser cache:

    • Hard refresh: Ctrl + Shift + R (Linux/Windows)
    • Or open DevTools → Network tab → Disable cache
  2. Check CSS specificity:

    /* Your CSS might be overridden by more specific rules */
    /* Check in browser DevTools → Elements → Styles */
    
  3. Verify compilation:

    # Check if CSS file exists:
    ls -la public/dist/css/
    
    # Should show: app.[hash].css
    

PHP Changes Require Restart

Symptoms: PHP code changes don’t apply even after browser refresh

Solutions:

  1. OPcache is enabled:

    # Check if OPcache is on:
    php -i | grep opcache.enable
    
    # Disable for development:
    php -S localhost:8080 -t public/ -d opcache.enable=0
    
  2. Clear Acorn cache:

    wp acorn optimize:clear
    wp acorn view:clear
    
  3. Restart PHP server:

    # In Terminal 1:
    Ctrl+C
    php -S localhost:8080 -t public/
    

Build Errors

Symptoms: yarn dev or yarn build fails

Solutions:

  1. Clear caches and reinstall:

    npx bud clean
    rm -rf node_modules
    yarn install
    
  2. Check Node.js version:

    node --version  # Should be 16+
    
  3. Check for syntax errors in source files:

    • Review error messages for file paths
    • Fix TypeScript/JavaScript syntax errors
    • Fix CSS syntax errors

Practical Examples

Example 1: Add a Custom Post Type

Step 1: Edit config/post-types.php

<?php

return [
    'register' => [
        'portfolio' => [
            'public' => true,
            'labels' => [
                'name' => __('Portfolio', 'radicle'),
                'singular_name' => __('Portfolio Item', 'radicle'),
                'add_new_item' => __('Add New Portfolio Item', 'radicle'),
            ],
            'menu_icon' => 'dashicons-portfolio',
            'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
            'has_archive' => true,
            'rewrite' => ['slug' => 'portfolio'],
            'show_in_rest' => true,
        ],
    ],
];

Step 2: Clear cache

wp acorn optimize:clear

Step 3: Create template

{{-- resources/views/single-portfolio.blade.php --}}
@extends('layouts.app')

@section('content')
    @while (have_posts())
        @php(the_post())

        <article class="portfolio-item">
            <h1>{{ get_the_title() }}</h1>

            @if (has_post_thumbnail())
                {{ the_post_thumbnail('large') }}
            @endif

            <div class="content">
                @php(the_content())
            </div>
        </article>
    @endwhile
@endsection

Done! The PostTypesServiceProvider automatically registers it.

Example 2: Create a Reusable Button Component

Step 1: Create component file

File: resources/views/components/button.blade.php

@props([
    'variant' => 'primary',
    'size' => 'md',
    'href' => null,
])

@php
$tag = $href ? 'a' : 'button';

$classes = [
    'btn',
    'inline-flex',
    'items-center',
    'justify-center',
    'font-medium',
    'rounded-lg',
    'transition-colors',
    match($variant) {
        'primary' => 'bg-blue-600 hover:bg-blue-700 text-white',
        'secondary' => 'bg-gray-600 hover:bg-gray-700 text-white',
        'outline' => 'border-2 border-blue-600 text-blue-600 hover:bg-blue-50',
        default => 'bg-blue-600 hover:bg-blue-700 text-white',
    },
    match($size) {
        'sm' => 'px-3 py-1.5 text-sm',
        'md' => 'px-4 py-2 text-base',
        'lg' => 'px-6 py-3 text-lg',
        default => 'px-4 py-2 text-base',
    }
];
@endphp

<{{ $tag }}
    @if($href) href="{{ $href }}" @endif
    {{ $attributes->class($classes) }}
>
    {{ $slot }}
</{{ $tag }}>

Step 2: Use it anywhere

{{-- In any template --}}

<x-button>
    Click Me
</x-button>

<x-button variant="secondary" size="lg">
    Large Button
</x-button>

<x-button variant="outline" href="/contact">
    Contact Us
</x-button>

<x-button variant="primary" class="w-full">
    Full Width Button
</x-button>

Example 3: Add Navigation Menu

Step 1: Edit config/theme.php

return [
    'menus' => [
        'primary_navigation' => __('Primary Navigation', 'radicle'),
        'footer_navigation' => __('Footer Navigation', 'radicle'),
        'mobile_navigation' => __('Mobile Navigation', 'radicle'),  // Add this
    ],
    // ... rest of config
];

Step 2: Clear cache

wp acorn optimize:clear

Step 3: Use in template

{{-- resources/views/sections/header.blade.php --}}

<nav class="mobile-menu">
    @if (has_nav_menu('mobile_navigation'))
        {!! wp_nav_menu([
            'theme_location' => 'mobile_navigation',
            'menu_class' => 'mobile-nav-items',
            'container' => false,
        ]) !!}
    @endif
</nav>

No need to touch PHP code! Service provider automatically reads config.


CLI Commands Reference

WP-CLI Commands

# Acorn (Laravel Artisan for WordPress)
wp acorn optimize          # Compile and cache config/routes
wp acorn optimize:clear    # Clear all caches
wp acorn view:clear        # Clear Blade template cache
wp acorn icons:cache       # Cache Blade Icons

# Standard WP-CLI
wp db create               # Create database
wp core install            # Install WordPress
wp plugin list             # List plugins
wp theme list              # List themes
wp user create             # Create user
wp rewrite flush           # Flush rewrite rules

Composer Commands

composer install                          # Install dependencies
composer update                           # Update all dependencies
composer require vendor/package           # Add dependency
composer require --dev vendor/package     # Add dev dependency
composer remove vendor/package            # Remove dependency
composer dump-autoload                    # Regenerate autoloader

Yarn/NPM Commands

yarn install               # Install dependencies
yarn dev                   # Development build with HMR
yarn build                 # Production build
yarn add package           # Add dependency
yarn remove package        # Remove dependency

Bud.js Commands

npx bud dev                # Start HMR development server
npx bud build              # Build (development mode)
npx bud build production   # Build (production mode)
npx bud clean              # Clean build cache
npx bud upgrade            # Check for updates
npx bud --help             # View all commands

Additional Resources

Official Documentation

Community & Support

  • SETUP_GUIDE.md - Initial setup and installation
  • README.md - Project overview
  • roadmap/ - Project roadmap and planning documents

Security Considerations

Environment Variables

  • ✅ Keep .env out of version control (use .gitignore)
  • ✅ Use different keys for each environment
  • ✅ Generate secure keys: https://roots.io/salts.html
  • ✅ Never commit database credentials

File Permissions

# Set correct permissions
chmod 644 .env
chmod 755 public/
chmod 775 storage/

WordPress Core

  • ✅ WordPress core managed by Composer
  • ✅ Easy to update: composer update roots/wordpress
  • ✅ Core files isolated in public/wp/
  • ✅ No accidental core file edits

Dependencies

# Check for security vulnerabilities
composer audit
npm audit

# Fix vulnerabilities
composer update
npm audit fix

Key Takeaways

The Magic Single Line

The entire theme is bootstrapped by ONE line in the WordPress theme stub:

// public/content/themes/radicle/index.php
<?php
echo view(app('sage.view'), app('sage.data'))->render();

This delegates everything to Acorn/Sage, which handles:

  • Template resolution
  • Data passing
  • Blade compilation
  • Asset enqueuing
  • Everything else!

Core Principles

  1. Separation of Concerns

    • Configuration → config/
    • Logic → app/Providers/
    • Templates → resources/views/
    • Assets → resources/scripts/ & resources/styles/
  2. Laravel Patterns in WordPress

    • Service Providers for bootstrapping
    • Blade templates for views
    • Configuration files for settings
    • Collections for data manipulation
    • Service container for dependency injection
  3. Modern Tooling

    • Composer for PHP dependencies
    • NPM/Yarn for JavaScript dependencies
    • Bud.js for asset compilation
    • TypeScript for type safety
    • Tailwind for utility-first CSS
  4. Better Developer Experience

    • Hot Module Replacement (HMR)
    • Automatic asset management
    • Component-based architecture
    • Cleaner, more expressive templates
    • Better code organization

Last Updated: 2025-10-11
Project: Radicle WordPress Stack (Bedrock + Sage + Acorn + Bud.js)


Weight: 5