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

Setting Up Vite

First, install Vite in your project:

npm install -D vite

# or for TypeScript
npm install -D vite typescript

Create Vite configuration file vite.config.ts:

import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig(() => {
	return {
		root: 'assets',   // set assets/ as the root directory for Vite to resolve imports
		base: 'assets',   // base URL for assets

		build: {
			manifest: true,  // generate manifest.json - essential for Nette Assets
			outDir: path.resolve(__dirname, 'www/assets'),  // must match Nette Assets 'path' config
			assetsDir: '', // Don't create additional nested directories for assets
		},
	};
});

In your configuration:

assets:
	mapping:
		default:
			type: vite
			path: assets

That's it! Nette Assets automatically handles everything else.

Entry Points and Dependencies

Modern JavaScript applications often split code into multiple files. An “entry point” is your main file that imports other modules, CSS, and assets.

Example entry point assets/main.js:

import './style.css'
import { initApp } from './app.js'
import { setupNavigation } from './components/navigation.js'

// Initialize your application
document.addEventListener('DOMContentLoaded', () => {
	initApp()
	setupNavigation()
})

In modern JavaScript development, CSS is often imported directly in JavaScript files rather than included separately in HTML. This approach allows bundlers like Vite to process and bundle CSS and provide hot reloading for CSS changes.

Add entry point to vite.config.ts:

export default defineConfig(() => {
	return {
		build: {
			rollupOptions: {
				input: {
					main: path.resolve(__dirname, 'assets/main.js'),
				},
			},
		},
	};
});

When you reference this entry point in your template:

{asset 'main.js'}

Nette Assets automatically generates all necessary HTML tags:

<!-- Main JavaScript bundle -->
<script src="/assets/main-4a8f9c7.js" type="module" crossorigin></script>

<!-- Vendor chunks (if any) preloaded for performance -->
<link rel="modulepreload" href="/assets/vendor-8c7fa9b.js" crossorigin>

<!-- Extracted CSS -->
<link rel="stylesheet" href="/assets/main-2b9c8d7.css" crossorigin>

All dependencies are handled automatically – you write one line, get complete asset management.

Production Builds

In production, Vite generates optimized bundles with hashed filenames:

npm run build

Vite generates:

  • Minified JavaScript bundles
  • Extracted and optimized CSS
  • Hashed filenames for cache-busting
  • A manifest file mapping source files to built files

Development Workflow

Development mode is an optional feature that provides significant benefits for modern web development. The main benefit is Hot Module Replacement (HMR), which works for both JavaScript and CSS. This means you can see changes instantly in your browser without losing application state – forms stay filled, modals remain open, and your debugging session continues uninterrupted.

To enable it, activate the devServer option in your configuration:

assets:
	mapping:
		default:
			type: vite
			devServer: true

Additionally, update your vite.config.ts:

export default defineConfig(() => {
	return {
		build: {
			// ...
		},

		server: {
			host: 'localhost',    // the host on which your development site runs
			cors: true,           // enable CORS for cross-origin requests
		},
	};
});

The CORS configuration is important because your PHP application (typically running on a different port like 80 or 443) needs to load assets from Vite's dev server running on port 5173. Without CORS enabled, browsers would block these cross-origin requests.

Start Vite's development server:

npm run dev

When option devServer is enabled and your application runs in debug mode, Nette Assets automatically connects to Vite's dev server. It detects the current URL and replaces the host with port 5173 to load assets directly from the dev server:

{asset 'main.js'}
{* In development: <script src="http://localhost:5173/assets/main.js" type="module"></script> *}

This seamless integration means you don't need to manually switch between development and production asset URLs – Nette Assets handles this automatically based on your environment.

Working with Public Files

Files in Vite's assets/public/ directory aren't processed by Vite but are copied as-is to the output. The ViteMapper handles these seamlessly:

{* References a file from public/ directory *}
{asset 'favicon.ico'}

{* Also works with nested paths *}
{asset 'images/logo.png'}

For public files, you can use FilesystemMapper features like automatic extension detection:

assets:
	mapping:
		default:
			type: vite
			path: assets
			extension: [webp, jpg, png]  # Try WebP first, fallback to JPG/PNG
			versioning: true             # Add cache-busting parameters

Dynamic Imports

Vite automatically splits code for optimal loading:

// Dynamically import heavy components
const loadChart = () => import('./components/chart.js')

button.addEventListener('click', async () => {
	const { Chart } = await loadChart()
	new Chart(data)
})

Dynamic imports create separate chunks loaded on demand. However, {asset 'main.js'} does not automatically preload these dynamic imports. This is intentional – preloading all possible dynamic imports could hurt performance by downloading unused code.

If you want to preload specific dynamic imports that you know will be needed, use the {preload} tag:

{* Main entry point *}
{asset 'main.js'}

{* Preload critical dynamic imports *}
{preload 'components/chart.js'}

This gives you fine-grained control over which resources are preloaded based on your application's actual needs.

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}!`)
}

Just reference your TypeScript files:

{asset 'assets/main.ts'}
version: 1.0