Vite + PawaJS

Use Laravel’s official Vite integration to bundle PawaJS components, enable Hot Module Replacement, and ship optimized production assets.

Why use Vite instead of the CDN?

The CDN is perfect for quick prototypes. Vite becomes the better choice when you need:

Component organization

Split logic into multiple files and import them cleanly.

Hot Module Replacement

Instant feedback while editing components.

Tree-shaking & minification

Smaller production bundles.

TypeScript support

Full IDE autocomplete and type checking.

1. Install PawaJS

Add PawaJS as a regular dependency in your Laravel project.

bash

                 
npm install pawajs 

        

2. Set up the entry file

Update resources/js/app.js (or app.ts) to import and start PawaJS.

js

                 
// resources/js/app.js
import './bootstrap';

// Import all your PawaJS components
import './components/counter';
import './components/todo-list';
import './components/user-card';

import { pawaStartApp } from 'pawajs';

// Start the application once the DOM is ready
document.addEventListener('DOMContentLoaded', () => {
    const app = document.getElementById('app');
    if (app) {
        pawaStartApp(app);
    }
}); 

        

Important: Always call pawaStartApp after the DOM is ready. Using DOMContentLoaded is the safest approach with Laravel + Vite.

3. Organize components

Keep components in a dedicated folder for clarity.

bash

                 
resources/js/
├── app.js
├── bootstrap.js
└── components/
    ├── counter.js
    ├── todo-list.js
    └── user-card.js 

        

Example component:

js

                 
// resources/js/components/counter.js
import { html, $state, useInsert, RegisterComponent } from 'pawajs';

export const Counter = ({ initial }) => {
    const count = $state(initial());

    const increment = () => count.value++;
    const decrement = () => count.value--;

    useInsert({ count, increment, decrement });

    return html`
        <div class="inline-flex items-center gap-3 rounded-lg border px-4 py-2">
            <button on-click="decrement()" class="btn btn-sm">−</button>
            <span class="font-mono text-lg min-w-[2ch] text-center">@{count.value}</span>
            <button on-click="increment()" class="btn btn-sm">+</button>
        </div>
    `;
};

RegisterComponent(Counter); 

        

4. Update the Blade layout

Use Laravel’s @vite directive. Remove the CDN script.

html

                 
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@yield('title', config('app.name'))</title>

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="antialiased">
    <div id="app">
        @yield('content')
    </div>
</body>
</html> 

        

5. Use components in Blade

Once registered, components are available as custom elements.

html

                 
@extends('layouts.app')

@section('content')
<div class="max-w-2xl mx-auto py-12 px-4">
    <h1 class="text-3xl font-bold mb-8">Dashboard</h1>

    <div class="space-y-6">
        <div>
            <h2 class="text-lg font-medium mb-2">Simple Counter</h2>
            <counter :initial="0"></counter>
        </div>

        <div>
            <h2 class="text-lg font-medium mb-2">Starting at 10</h2>
            <counter :initial="10"></counter>
        </div>
    </div>
</div>
@endsection 

        

6. Development workflow

Run the usual Laravel + Vite development servers:

bash

                 
# Terminal 1 – Laravel
php artisan serve

# Terminal 2 – Vite (with HMR)
npm run dev 

        

Or use the convenient Composer script that starts both:

bash

                 
composer run dev 

        

7. Production builds

When deploying, build the assets once:

bash

                 
npm run build 

        

Laravel’s @vite directive will automatically point to the versioned files in public/build.

Advanced Tips

Lazy registration (optional)

If you have many components, you can register them only when needed:

js

                 
// Dynamically import a component
const { Counter } = await import('./components/counter.js');
RegisterComponent(Counter); 

        

TypeScript

PawaJS works great with TypeScript. Just rename files to .ts and type your props:

ts

                 
import { html, $state, useInsert, RegisterComponent } from 'pawajs';

interface CounterProps {
    initial?: () => number;
}

const Counter = ({ initial = () => 0 }: CounterProps) => {
    const count = $state(initial());
    // ...
}; 

        

Shared state across components

Create a simple store file and import it wherever needed:

js

                 
// resources/js/stores/ui.js
import { $state } from 'pawajs';

export const sidebarOpen = $state(false);
export const theme = $state('light','theme'); 

        

Common Pitfalls

  • Forgetting to import the component fileRegisterComponent only runs if the module is imported in app.js.
  • Calling pawaStartApp too early — Always wait for DOMContentLoaded or place the script at the end of <body>.
  • Mixing CDN and Vite — Don’t load both the CDN script and the Vite-bundled version. Choose one.
  • Props as functions — Remember that props arrive as getters. Always call them: initial().
CDN Installation Guide with laravel→ Form handling example with laravel→