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" />Use asset() for images, SVG sprites, fonts, and other files managed by Vite:
<?= $this->Html->image($this->Vite->asset('src/logo.png'), ['alt' => 'Site logo']) ?>
<svg aria-hidden="true">
<use href="<?= h($this->Vite->asset('src/icons.svg') . '#image-up') ?>"></use>
</svg>In development, this returns the source URL on the configured Vite server. In
production, it looks up the exact manifest key and returns the built URL, such as
/build/assets/icons-a1b2c3.svg. Non-entry manifest assets are supported. Missing
production entries throw ManifestException instead of returning an unhashed URL.
The method returns a raw URL and does not render tags, modify view blocks, or add
the HMR client. Escape the result with h() when writing an HTML attribute.
Append SVG fragments after resolving the asset. CakePHP base paths and loaded
plugin paths are respected, without adding file timestamps to hashed URLs.
It accepts the same configuration array, ViteConfig object, or named
configuration as script() and css():
<?= $this->Html->image($this->Vite->asset('src/logo.png', ['config' => 'admin'])) ?>Files must be included in the Vite build manifest. Import them from a JavaScript
entry, or emit generated assets from a Vite plugin with a stable
originalFileName:
// Inside a Vite plugin's buildStart hook:
this.emitFile({
type: 'asset',
name: 'icons.svg',
originalFileName: 'src/icons.svg',
source: generatedSprite,
});This produces a src/icons.svg manifest entry with Vite's content-hashed output
filename. Files copied unchanged from Vite's public directory are not manifest
assets.
External SVG <use> references require the same origin as the page. When the
Vite dev server uses a different origin, proxy it through the application origin
or use a configuration with forceProductionMode: true and an existing build for
the sprite.
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') ?>By default, script() and css() append tags to view blocks for rendering in layouts.
Set block => false to return tags as a string for inline output:
<?php
// Output directly instead of buffering to view blocks
echo $this->Vite->script(['files' => ['src/main.js'], 'block' => false]);
echo $this->Vite->css(['files' => ['src/style.css'], 'block' => false]);
?>This is useful when you need to render assets directly in elements or partials:
<!-- In an element file -->
<?= $this->Vite->css(['files' => ['src/component.css'], 'block' => false]) ?>
<div class="component">
<!-- Component content -->
</div>Vite Client Deduplication:
In development mode, the @vite/client script is automatically deduplicated. Multiple script() calls (e.g., in layout, elements, components) will only output the Vite client once:
// Layout
<?php $this->Vite->script(['files' => ['src/app.js']]); ?> <!-- Includes @vite/client -->
// Element
<?php $this->Vite->script(['files' => ['src/element.js']]); ?> <!-- No duplicate client -->Disable Dependent CSS Injection:
In production mode, script() automatically injects dependent CSS from JS entries. Use cssBlock => false to disable this:
<?php
// Only output scripts, skip dependent CSS injection
$this->Vite->script(['files' => ['src/main.js'], 'cssBlock' => false]);
?>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.