A Vite.js integration for CakePHP 5.0+ applications. Seamlessly switch between development and production modes with automatic asset tag generation.
Note
This project is a spiritual successor to passchn/cakephp-vite, rewritten with modern PHP and a service-oriented architecture. There is no affiliation with the original project.
- 🚀 Automatic Mode Detection: Seamlessly switches between development and production based on your environment
- 🎯 Zero Configuration: Works out of the box with sensible defaults
- 📦 Plugin Support: Load assets from CakePHP plugins with ease
- đź”§ Flexible Configuration: Customize dev server URLs, manifest paths, and more
- 🎨 CSS Extraction: Automatically includes CSS dependencies from JavaScript entries in production
- ⚡ HMR Support: Full Hot Module Replacement support in development mode
- 🏎️ Modulepreload Support: Automatic preloading of dependencies for faster load times in code-split applications
- âś… Type-Safe: 100% type coverage with PHPStan level 8
- đź§Ş Well Tested: 95%+ code coverage with comprehensive unit and integration tests
- PHP 8.2 or higher
- CakePHP 5.0 or higher
- Vite 2.0 or higher (installed via npm/yarn/pnpm)
Install via Composer:
composer require josbeir/cakephp-viteLoad the plugin:
bin/cake plugin load CakeViteCreate or update your vite.config.js:
import { defineConfig } from 'vite';
export default defineConfig({
root: 'webroot',
base: '/',
build: {
manifest: true,
outDir: '../webroot',
rollupOptions: {
input: {
main: 'webroot/src/main.js'
}
}
},
server: {
origin: 'http://localhost:3000'
}
});In your src/View/AppView.php:
public function initialize(): void
{
parent::initialize();
$this->loadHelper('CakeVite.Vite');
}In your layout file (e.g., templates/layout/default.php):
<!DOCTYPE html>
<html>
<head>
<?= $this->Html->charset() ?>
<title><?= $this->fetch('title') ?></title>
<?php $this->Vite->script(['files' => ['src/main.js']]); ?>
<?php $this->Vite->css(['files' => ['src/style.css']]); ?>
<?= $this->fetch('css') ?>
<?= $this->fetch('script') ?>
</head>
<body>
<?= $this->fetch('content') ?>
</body>
</html>Start Vite dev server:
npm run dev # or: viteBuild for production:
npm run build # or: vite buildCreate config/app_vite.php in your application for custom settings:
<?php
return [
'CakeVite' => [
'devServer' => [
'url' => 'http://localhost:3000',
'entries' => [
'script' => ['src/main.js'],
'style' => ['src/style.css'],
],
],
],
];Note
Config entries vs. Helper files parameter:
devServer.entries: Default files for development mode only. Used when helper is called withoutfilesoption.filesparameter: Per-call override that works in both development and production. In dev, it overrides config entries. In production, it filters manifest entries.
// Uses config default (src/main.js) in development
$this->Vite->script();
// Override for this specific call (works in dev and production)
$this->Vite->script(['files' => ['src/admin.js']]);Note
The plugin will automatically detect and load config/app_vite.php if it exists for backwards compatibility.
All available configuration options:
<?php
return [
'CakeVite' => [
'devServer' => [
// Development server URL
'url' => env('VITE_DEV_SERVER_URL', 'http://localhost:3000'),
// Hostname hints for development mode detection
'hostHints' => ['localhost', '127.0.0.1', '.test', '.local'],
// Default entries for development mode
'entries' => [
'script' => ['src/main.js'],
'style' => ['src/style.css'],
],
],
'build' => [
// Path to Vite's manifest.json
'manifestPath' => WWW_ROOT . 'manifest.json',
// Output directory (relative to webroot)
'outDirectory' => false, // or 'dist', 'build', etc.
],
// Modulepreload support for faster loading (production only)
// Options: 'none', 'link-tag'
'preload' => env('VITE_PRELOAD_MODE', 'link-tag'),
// Persistent caching (production performance optimization)
'cache' => [
'config' => env('VITE_CACHE_CONFIG', false), // CakePHP cache config name
'development' => false,
],
// Force production mode (ignores environment detection)
'forceProductionMode' => false,
// Plugin name for loading plugin assets (null for app assets)
'plugin' => null,
// Cookie/query parameter name to force production mode
'productionModeHint' => 'vprod',
// View block names for script and CSS tags
'viewBlocks' => [
'script' => 'script',
'css' => 'css',
],
],
];You can use environment variables for configuration:
# .env
VITE_DEV_SERVER_URL=http://localhost:5173Then reference in your config:
'devServer' => [
'url' => env('VITE_DEV_SERVER_URL', 'http://localhost:3000'),
],DDEV requires some additional configuration to properly expose the Vite dev server. Here's how to set it up:
1. Configure .ddev/config.yaml to expose port 5173 (or something else):
web_extra_exposed_ports:
- name: node-vite
container_port: 5173
http_port: 5172
https_port: 51732. Create config/app_vite.php using DDEV environment variables:
<?php
return [
'CakeVite' => [
'devServer' => [
'hostHints' => [env('DDEV_HOSTNAME', '')],
'url' => env('DDEV_PRIMARY_URL') . ':5173',
],
],
];3. Update vite.config.js to use the DDEV URL:
import { defineConfig } from 'vite';
export default defineConfig({
root: 'webroot',
base: '/',
build: {
manifest: true,
outDir: 'webroot/build',
rollupOptions: {
input: {
main: 'webroot/src/main.js'
}
}
},
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
origin: `${process.env.DDEV_PRIMARY_URL.replace(/:\d+$/, "")}:5173`,
cors: {
origin: /https?:\/\/([A-Za-z0-9\-\.]+)?(\.ddev\.site)(?::\d+)?$/,
},
}
});Tip
DDEV automatically sets environment variables like DDEV_HOSTNAME and DDEV_PRIMARY_URL. Run ddev exec printenv | grep DDEV to see all available variables.
CakeVite supports both string shorthand and array syntax for loading assets:
String Shorthand (Simple):
// Single file - quick and easy
<?php $this->Vite->script('src/main.js'); ?>
<?php $this->Vite->css('src/style.css'); ?>Array Syntax (Full Featured):
// Single file with options
<?php $this->Vite->script(['files' => ['src/main.js']]); ?>
// Multiple files
<?php
$this->Vite->script([
'files' => [
'src/main.js',
'src/admin.js',
]
]);
?>
// With custom attributes
<?php
$this->Vite->script([
'files' => ['src/main.js'],
'attributes' => ['defer' => true]
]);
?>Tip
Both script() and css() methods are void - they append tags to view blocks. Use <?= $this->fetch('script') ?> and <?= $this->fetch('css') ?> to render them in your layout.
In development, CakeVite automatically:
- Connects to your Vite dev server
- Includes the Vite client for HMR
- Loads modules as ES modules
- Provides instant hot reloading
// In your template
<?php $this->Vite->script(['files' => ['src/main.js']]); ?>Output in development:
<script type="module" src="http://localhost:3000/@vite/client"></script>
<script type="module" src="http://localhost:3000/src/main.js"></script>In production, CakeVite automatically:
- Reads the build manifest
- Resolves hashed filenames
- Includes dependent CSS files
- Handles legacy browser support
// Same code in template
<?php $this->Vite->script(['files' => ['src/main.js']]); ?>Output in production:
<script type="module" src="/assets/main-a1b2c3d4.js"></script>
<link rel="stylesheet" href="/assets/main-e5f6g7h8.css" />Load assets from a CakePHP plugin:
// Load from MyPlugin
<?php
$this->Vite->pluginScript('MyPlugin', true, ['files' => ['src/plugin.js']]);
$this->Vite->pluginCss('MyPlugin', true, ['files' => ['src/plugin.css']]);
?>Or configure globally:
'CakeVite' => [
'plugin' => 'MyPlugin',
],Load multiple files at once:
<?php
$this->Vite->script([
'files' => [
'src/main.js',
'src/admin.js',
'src/analytics.js',
]
]);
?>Filter production assets by pattern:
<?php
// Only load files matching 'admin'
$this->Vite->script([
'filter' => 'admin'
]);
?>Add custom HTML attributes to generated tags:
<?php
$this->Vite->script([
'files' => ['src/main.js'],
'attributes' => [
'defer' => true,
'data-turbo-track' => 'reload',
]
]);
?>Output:
<script type="module" defer data-turbo-track="reload" src="..."></script>Use custom view blocks for scripts and styles:
<?php
// Append to 'custom_scripts' block instead of default 'script'
$this->Vite->script([
'files' => ['src/main.js'],
'block' => 'custom_scripts'
]);
?>
<!-- In layout -->
<?= $this->fetch('custom_scripts') ?>CakeVite supports modulepreload to improve load times for applications with code splitting. Preloading hints to the browser which modules will be needed soon, allowing parallel downloads.
Enabled by Default:
<?php
// Preloading is enabled by default in production
$this->Vite->script(['files' => ['src/main.js']]);
?>Output in production:
<!-- Dependencies are preloaded before the main script -->
<link rel="modulepreload" href="/assets/vendor-def456.js">
<link rel="modulepreload" href="/assets/utils-ghi789.js">
<script type="module" src="/assets/app-abc123.js"></script>Disable Preloading:
<?php
// Disable preloading for a specific call
$this->Vite->script([
'files' => ['src/main.js'],
'preload' => 'none'
]);
?>Configure Globally:
// config/app_vite.php
return [
'CakeVite' => [
// Options: 'none', 'link-tag'
'preload' => 'none', // Disable preloading globally
],
];Environment Variable:
# .env
VITE_PRELOAD_MODE=none # or 'link-tag'Note
- Preloading only works in production mode (development mode uses dev server, no manifest)
- Uses
rel="modulepreload"for ES modules - Automatically deduplicates URLs to prevent redundant preloads
link-headermode is reserved for future HTTP/2 header-based preloading- Requires Vite's
build.modulePreloadto be enabled (default). If you've disabled it in your Vite config, preloading won't work as import dependencies aren't tracked in the manifest.
Performance Benefits:
- Reduces load time by downloading dependencies in parallel
- Particularly beneficial for code-split applications
- Browser can start downloading imports while parsing main script
Enable persistent caching of the manifest file in production to eliminate file I/O overhead on every request.
Enable Caching:
// config/app_vite.php
return [
'CakeVite' => [
'cache' => [
'config' => 'default', // Use any CakePHP cache config
],
],
];Environment Variable:
# .env
VITE_CACHE_CONFIG=defaultCache Invalidation:
- Automatic: cache key includes manifest file mtime
- When manifest rebuilds, cache automatically invalidates
Cache in Development:
'cache' => [
'config' => 'default',
'development' => true, // Enable caching in dev mode
],Tip
Enabling caching could improve manifest read performance, particularly when using memory-based solutions such as Redis that avoid file I/O operations.
Use named configurations for different parts of your application (admin panel, marketing site, etc.).
Define Named Configs:
// config/app_vite.php
return [
'CakeVite' => [
'devServer' => ['url' => 'http://localhost:3000'],
'build' => ['manifestPath' => WWW_ROOT . 'manifest.json'],
'configs' => [
'admin' => [
'devServer' => ['url' => 'http://localhost:3001'],
'build' => [
'outDirectory' => 'admin',
'manifestPath' => WWW_ROOT . 'admin' . DS . 'manifest.json',
],
],
'marketing' => [
'devServer' => ['url' => 'http://localhost:3002'],
'build' => ['outDirectory' => 'marketing'],
],
],
],
];Use in Templates:
<!-- Use 'admin' config -->
<?php $this->Vite->script(['files' => ['src/admin.ts'], 'config' => 'admin']); ?>
<?php $this->Vite->css(['files' => ['src/admin.css'], 'config' => 'admin']); ?>
<!-- Use 'marketing' config -->
<?php $this->Vite->script(['config' => 'marketing']); ?>Configuration Inheritance:
- Named configs inherit from default config
- Override only what you need
- Useful for multi-tenant or multi-section applications
Check if running in development mode:
<?php if ($this->Vite->isDev()): ?>
<!-- Development-only content -->
<div class="dev-toolbar">Development Mode</div>
<?php endif; ?>CakeVite automatically detects the environment based on:
- Force Production Mode:
forceProductionModeconfiguration - Production Hint: Cookie or query parameter (
vprodby default) - Host Hints: Matches hostname against development patterns
- Default: Falls back to production for safety
- Connects to Vite dev server
- Serves assets from configured dev server URL
- Includes Vite client for HMR
- No manifest file required
- Reads
manifest.jsongenerated by Vite - Maps entry points to hashed output files
- Automatically includes CSS dependencies
- Supports legacy browser builds
Run the test suite:
composer testRun with coverage:
composer test-coverageCheck code standards:
composer cs-check
composer cs-fixRun static analysis:
composer phpstanContributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Write tests for your changes
- Ensure all quality checks pass (
composer cs-check && composer phpstan && composer test) - Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.