Vite Integration
Modern JavaScript applications require sophisticated build tools. Nette Assets provides first-class integration with Vite, the next-generation frontend build tool. Get lightning-fast development with Hot Module Replacement (HMR) and optimized production builds with zero configuration hassle.
- Zero configuration – automatic bridge between Vite and PHP templates
- Complete dependency management – one tag handles all assets
- Hot Module Replacement – instant JavaScript and CSS updates
- Optimized production builds – code splitting and tree shaking
Nette Assets integrates seamlessly with Vite, so you get all these benefits while writing your templates as usual.
Setting Up Vite
Let's set up Vite step by step. Don't worry if you're new to build tools – we'll explain everything!
Step 1: Install Vite
First, install Vite and the Nette plugin in your project:
npm install -D vite @nette/vite-plugin
This installs Vite and a special plugin that helps Vite work perfectly with Nette.
Step 2: Project Structure
The standard approach is to place source asset files in an assets/
folder in your project root, and the compiled
versions in www/assets/
:
web-project/ ├── assets/ ← source files (SCSS, TypeScript, source images) │ ├── public/ ← static files (copied as-is) │ │ └── favicon.ico │ ├── images/ │ │ └── logo.png │ ├── app.js ← main entry point │ └── style.css ← your styles └── www/ ← public directory (document root) ├── assets/ ← compiled files will go here └── index.php
The assets/
folder contains your source files – the code you write. Vite will process these files and put the
compiled versions in www/assets/
.
Step 3: Configure Vite
Create a vite.config.ts
file in your project root. This file tells Vite where to find your source files and where
to put the compiled ones.
The Nette Vite plugin comes with smart defaults that make configuration simple. It assumes your front-end source files are in
the assets/
directory (option root
) and compiled files go to www/assets/
(option
outDir
). You only need to specify the entry point:
import { defineConfig } from 'vite';
import nette from '@nette/vite-plugin';
export default defineConfig({
plugins: [
nette({
entry: 'app.js',
}),
],
});
If you want to specify another directory name to build your assets, you will need to change a few options:
export default defineConfig({
root: 'assets', // root directory of source assets
build: {
outDir: '../www/assets', // where compiled files go
},
// ... other config ...
});
The outDir
path is considered relative to root
, which is why there's ../
at
the beginning.
Step 4: Configure Nette
Tell Nette Assets about Vite in your common.neon
:
assets:
mapping:
default:
type: vite # tells Nette to use the ViteMapper
path: assets
Step 5: Add scripts
Add these scripts to your package.json
:
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
Now you can:
npm run dev
– start development server with hot reloadingnpm run build
– create optimized production files
Entry Points
An entry point is the main file where your application starts. From this file, you import other files (CSS, JavaScript modules, images), creating a dependency tree. Vite follows these imports and bundles everything together.
Example entry point assets/app.js
:
// Import styles
import './style.css'
// Import JavaScript modules
import netteForms from 'nette-forms';
import naja from 'naja';
// Initialize your application
netteForms.initOnLoad();
naja.initialize();
In the template you can insert an entry point as follows:
{asset 'app.js'}
Nette Assets automatically generates all necessary HTML tags – JavaScript, CSS, and any other dependencies.
Multiple Entry Points
Larger applications often need separate entry points:
export default defineConfig({
plugins: [
nette({
entry: [
'app.js', // public pages
'admin.js', // admin panel
],
}),
],
});
Use them in different templates:
{* In public pages *}
{asset 'app.js'}
{* In admin panel *}
{asset 'admin.js'}
Important: Source vs Compiled Files
It's crucial to understand that on production you can only load:
- Entry points defined in
entry
- Files from the
assets/public/
directory
You cannot load using {asset}
arbitrary files from assets/
– only assets referenced by
JavaScript or CSS files. If your file is not referenced anywhere it will not be compiled. If you want to make Vite aware of other
assets, you can move them to the public folder.
Please note that by default, Vite will inline all assets smaller than 4KB, so you will not be able to reference these files directly. (See Vite documentation).
{* ✓ This works - it's an entry point *}
{asset 'app.js'}
{* ✓ This works - it's in assets/public/ *}
{asset 'favicon.ico'}
{* ✗ This won't work - random file in assets/ *}
{asset 'components/button.js'}
Development Mode
Development mode is completely optional but provides significant benefits when enabled. The main advantage is Hot Module Replacement (HMR) – see changes instantly without losing application state, making the development experience much smoother and faster.
Vite is a modern build tool that makes development incredibly fast. Unlike traditional bundlers, Vite serves your code directly to the browser during development, which means instant server start no matter how large your project and lightning-fast updates.
Starting Development Server
Run the development server:
npm run dev
You'll see:
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
Keep this terminal open while developing.
The Nette Vite plugin automatically detects when:
- Vite dev server is running
- Your Nette application is in debug mode
When both conditions are met, Nette Assets loads files from the Vite dev server instead of the compiled directory:
{asset 'app.js'}
{* In development: <script src="http://localhost:5173/app.js" type="module"></script> *}
{* In production: <script src="/assets/app-4a8f9c7.js" type="module"></script> *}
No configuration needed – it just works!
Working on Different Domains
If your development server runs on something other than localhost
(like myapp.local
), you might
encounter CORS (Cross-Origin Resource Sharing) issues. CORS is a security feature in web browsers that blocks requests between
different domains by default. When your PHP application runs on myapp.local
but Vite runs on
localhost:5173
, the browser sees these as different domains and blocks the requests.
You have two options to solve this:
Option 1: Configure CORS
The simplest solution is to allow cross-origin requests from your PHP application:
export default defineConfig({
// ... other config ...
server: {
cors: {
origin: 'http://myapp.local', // your PHP app URL
},
},
});
Option 2: Run Vite on your domain
The other solution is to make Vite run on the same domain as your PHP application.
export default defineConfig({
// ... other config ...
server: {
host: 'myapp.local', // same as your PHP app
},
});
Actually, even in this case, you need to configure CORS because the dev server runs on the same hostname but on a different port. However, in this case, CORS is automatically configured by the Nette Vite plugin.
HTTPS Development
If you develop on HTTPS, you need certificates for your Vite development server. The easiest way is using a plugin that generates certificates automatically:
npm install -D vite-plugin-mkcert
Here's how to configure it in vite.config.ts
:
import mkcert from 'vite-plugin-mkcert';
export default defineConfig({
// ... other config ...
plugins: [
mkcert(), // generates certificates automatically and enables https
nette(),
],
});
Note that if you're using the CORS configuration (Option 1 from above), you need to update the origin URL to use
https://
instead of http://
.
Production Builds
Create optimized production files:
npm run build
Vite will:
- Minify all JavaScript and CSS
- Split code into optimal chunks
- Generate hashed filenames for cache-busting
- Create a manifest file for Nette Assets
Example output:
www/assets/
├── app-4f3a2b1c.js # Your main JavaScript (minified)
├── app-7d8e9f2a.css # Extracted CSS (minified)
├── vendor-8c4b5e6d.js # Shared dependencies
└── .vite/
└── manifest.json # Mapping for Nette Assets
The hashed filenames ensure browsers always load the latest version.
Public Folder
Files in assets/public/
directory are copied to the output without processing:
assets/
├── public/
│ ├── favicon.ico
│ ├── robots.txt
│ └── images/
│ └── og-image.jpg
├── app.js
└── style.css
Reference them normally:
{* These files are copied as-is *}
<link rel="icon" href={asset 'favicon.ico'}>
<meta property="og:image" content={asset 'images/og-image.jpg'}>
For public files, you can use FilesystemMapper features:
assets:
mapping:
default:
type: vite
path: assets
extension: [webp, jpg, png] # Try WebP first
versioning: true # Add cache-busting
In the vite.config.ts
configuration you can change the public folder using the publicDir
option.
Dynamic Imports
Vite automatically splits code for optimal loading. Dynamic imports allow you to load code only when it's actually needed, reducing the initial bundle size:
// Load heavy components on demand
button.addEventListener('click', async () => {
let { Chart } = await import('./components/chart.js')
new Chart(data)
})
Dynamic imports create separate chunks that are loaded only when needed. This is called “code splitting” and it's one of Vite's most powerful features. When you use dynamic imports, Vite automatically creates separate JavaScript files for each dynamically imported module.
The {asset 'app.js'}
tag does not automatically preload these dynamic chunks. This is intentional
behavior – we don't want to download code that might never be used. The chunks are downloaded only when the dynamic import is
executed.
However, if you know that certain dynamic imports are critical and will be needed soon, you can preload them:
{* Main entry point *}
{asset 'app.js'}
{* Preload critical dynamic imports *}
{preload 'components/chart.js'}
This tells the browser to download the chart component in the background, so it's ready immediately when needed.
TypeScript Support
TypeScript works out of the box:
// assets/main.ts
interface User {
name: string
email: string
}
export function greetUser(user: User): void {
console.log(`Hello, ${user.name}!`)
}
Reference TypeScript files normally:
{asset 'main.ts'}
For full TypeScript support, install it:
npm install -D typescript
Additional Vite Configuration
Here are some useful Vite configuration options with detailed explanations:
export default defineConfig({
// Root directory containing source assets
root: 'assets',
// Folder whose contents are copied to output directory as-is
// Default: 'public' (relative to 'root')
publicDir: 'public',
build: {
// Where to put compiled files (relative to 'root')
outDir: '../www/assets',
// Empty output directory before building?
// Useful to remove old files from previous builds
emptyOutDir: true,
// Subdirectory within outDir for generated chunks and assets
// This helps organize the output structure
assetsDir: 'static',
rollupOptions: {
// Entry point(s) - can be a single file or array of files
// Each entry point becomes a separate bundle
input: [
'app.js', // main application
'admin.js', // admin panel
],
},
},
server: {
// Host to bind the dev server to
// Use '0.0.0.0' to expose to network
host: 'localhost',
// Port for the dev server
port: 5173,
// CORS configuration for cross-origin requests
cors: {
origin: 'http://myapp.local',
},
},
css: {
// Enable CSS source maps in development
devSourcemap: true,
},
plugins: [
nette(),
],
});
That's it! You now have a modern build system integrated with Nette Assets.