Skip to content

Build theme and plugin assets with Vite in Pollora: asset containers, the Vite integration, and how to reference the compiled CSS and JavaScript files.

The Pollora framework provides a powerful asset management system with support for ViteJS integration and Asset Containers.

The Asset class simplifies adding CSS and JavaScript assets with chainable methods:

$css = Asset::add('my-theme', 'css/screen.min.css')
->dependencies(['bootstrap'])
->version('1.0')
->media('all')
->toFrontend();
$js = Asset::add('theme-script', 'js/mythemescript.js')
->dependencies(['jquery'])
->version('1.0')
->loadInFooter()
->loadStrategy('defer')
->toFrontend()
->localize('myData', ['key' => 'value']);
## Asset Containers
Asset Containers allow you to group and manage assets with specific configurations:
### Root Container for Native Laravel Assets
The framework automatically registers a `root` container for native Laravel assets. This container is configured to work with Laravel's default asset structure and Vite configuration:
```php
// The root container is automatically registered and set as default
// It looks for assets in the standard Laravel locations:
// - resources/css/app.css
// - resources/js/app.js

To use native Laravel assets with the root container:

Asset::add('root/styles', 'resources/css/app.css')
->container('root')
->toBackend()
->useVite();
Asset::add('root/scripts', 'resources/js/app.js')
->container('root')
->toBackend()
->useVite();

The root container configuration:

  • Hot file: public/hot
  • Build directory: build
  • Manifest path: manifest.json
  • Base path: Empty (assets are at the root level)

The framework declares the containers of the theme (theme), of each plugin (plugin.<slug>) and of each module (module.<slug>) itself, on the layout @pollora/vite-config builds to. To add your own, declare it with Asset::addContainer(), for example in a service provider:

Asset::addContainer('my-package', [
'hot_file' => public_path('my-package.hot'), // exists while the dev server runs
'build_directory' => 'build/my-package', // where Vite built, relative to the public directory
'manifest_path' => 'manifest.json', // in the build directory
'base_path' => 'resources/assets/', // what Asset::add('…', 'app.js') is relative to
]);

To use ViteJS for asset management:

Asset::add('my-vite-asset', 'path/to/asset.js')
->container('theme') // Optional: specify a container
->useVite()
->toFrontend();

Vite builds ES modules, so on the front end (toFrontend()) and in the admin (toBackend()) a Vite script is enqueued as a WordPress script module (wp_enqueue_script_module), not a classic script. WordPress then places it the way it places its own modules — always after its import map:

ThemeWhere the Vite script is printed
Block themein the <head>, or in the footer with loadInFooter()
Classic themein the footer, whatever loadInFooter() says

Printed before the import map, a module voids it in Firefox and Safari, and every WordPress module on the page — the navigation block’s, for one — fails with @wordpress/interactivity was a bare specifier.

A module only depends on modules and takes no localized data or inline script. What the asset declares with dependencies(), localize() or inline() goes on a classic companion script, {handle}-data, printed in the head: it runs before the module, which the browser defers — so inline code runs before the module too, whatever its position.

toEditor(), toLoginScreen() and toCustomizer() are unchanged: WordPress prints no script modules there, so a Vite script stays a classic <script type="module">.

The following methods are available for configuring assets:

  • add(string $handle, string $path): Add a new asset.
  • container(string $containerName): Specify the asset container to use.
  • dependencies(array $dependencies): Specify asset dependencies.
  • version(string $version): Set the asset version.
  • media(string $media): Set the media type for styles.
  • loadInFooter(): Load the script in the footer (for a Vite script, only a block theme reads it — see above).
  • loadStrategy(string $strategy): Set the script load strategy (e.g., ‘defer’).
  • toFrontend(), toBackend(), toLoginScreen(), toCustomizer(), toEditor(): Specify where to load the asset.
  • useVite(): Use ViteJS for this asset.

Use the localize(string $objectName, array $data) method to localize JavaScript:

Asset::add('handle', 'path/to/asset.js')
->localize('myData', ['key' => 'value']);

Add inline content for CSS or JavaScript using the inline(string $content) method:

Asset::add('my-theme', 'css/screen.min.css')
->inline('.panel-main { border: 1px solid blue; }');
Asset::add('typekit', 'https://use.typekit.net/fdsjhizo.js')
->inline('try{Typekit.load({ async: true });}catch(e){}');

The framework automatically sets root as the default container for native Laravel assets. This means if you don’t specify a container, the root container will be used:

// These two are equivalent when root is the default container
Asset::add('app-styles', 'resources/css/app.css')
->useVite()
->toBackend();
Asset::add('app-styles', 'resources/css/app.css')
->container('root')
->useVite()
->toBackend();

For theme-specific assets, you should explicitly specify the theme container:

Asset::add('theme/script', Theme::path('assets/app.js'))
->container('theme')
->toFrontend()
->useVite();

You can use Asset Containers for non-Vite assets as well:

Asset::add('plugin/legacy-script', 'js/legacy.js')
->container('plugin')
->toBackend();

This allows for consistent asset management across your entire WordPress setup, regardless of whether you’re using ViteJS or traditional asset inclusion methods.

You can easily reference assets using the Asset facade:

use Pollora\Support\Facades\Asset;
// Reference an image
$imageUrl = Asset::url('assets/images/logo.png');
// Reference a CSS file
$cssUrl = Asset::url('assets/css/app.css');
// Reference a JavaScript file
$jsUrl = Asset::url('assets/js/app.js');
// Reference a font file
$fontUrl = Asset::url('assets/fonts/myfont.woff2');

By default, the framework uses the root container for native Laravel assets. If you want to explicitly reference assets from a different container, use the from() method:

use Pollora\Support\Facades\Asset;
// Reference an image from the "admin" container
$imageUrl = Asset::url('assets/images/logo.png')->from('admin');
// Reference a CSS file from the "theme" container
$cssUrl = Asset::url('assets/css/styles.css')->from('theme');
// Reference native Laravel assets from the root container
$appCss = Asset::url('resources/css/app.css')->from('root');

You can also use the Asset facade directly within your Blade templates for dynamic asset referencing:

<img src="{{ Asset::url('assets/images/logo.png') }}">
<img src="{{ Asset::url('assets/images/admin-logo.png')->from('admin') }}">
<link rel="stylesheet" href="{{ Asset::url('assets/css/app.css') }}">
<script src="{{ Asset::url('assets/js/app.js') }}"></script>

The Asset facade leverages an internal AssetFile class that dynamically resolves the correct URL based on the specified asset path and container. The default container is now set to root, which handles native Laravel assets.

If no container is specified, the framework falls back to the default container automatically. This means native Laravel assets can be referenced without specifying a container.

// Using the default root container for Laravel assets
$appStyles = Asset::url('resources/css/app.css');
// Using a specific container for theme assets
$themeImage = Asset::url('assets/images/logo.png')->from('theme');
// Switching to another container
$sharedCss = Asset::url('assets/css/shared-styles.css')->from('shared');

This updated approach provides greater flexibility and maintains clarity in your asset management workflow. It ensures that assets are always correctly referenced, regardless of their container or context.

It is essential to declare assets in a Service Provider rather than a hook class. The service provider ensures the assets are properly enqueued via the WordPress wp_enqueue_scripts hook.

By following these guidelines, you can ensure consistent and efficient asset management across your entire WordPress setup, leveraging the power of ViteJS and Pollora’s asset management system.

Without Laravel: a classic theme, a plugin or Bedrock

Section titled “Without Laravel: a classic theme, a plugin or Bedrock”

The asset classes are the package pollora/assets, which the framework requires. It also runs in a WordPress project that is not a Pollora application: a classic theme, a plugin, a Bedrock site. There, Pollora\Asset\Asset is the same facade, forwarding to the same asset manager wired to WordPress, and you declare the containers yourself:

use Pollora\Asset\Asset;
Asset::addContainer('theme', [
'hot_file' => get_stylesheet_directory().'/hot',
'build_directory' => 'build',
'manifest_path' => 'manifest.json',
'base_path' => 'resources/assets/',
'public_path' => get_stylesheet_directory(),
'public_url' => get_stylesheet_directory_uri(),
]);
Asset::add('my-theme/app', 'app.js')->container('theme')->useVite()->toFrontend();

The first four keys are the ones above. public_path and public_url are the only addition: a Pollora application has one public directory for every container (public_path(), and the application’s URL), where a classic theme or a plugin is its own root and says where it is. Laravel’s Vite ignores both keys.

Build the theme or the plugin with @pollora/vite-config and its standalone layout, which writes build/ and a hot file inside the folder, with relative URLs so the build works wherever WordPress serves it from (a subdirectory install, Bedrock’s web/app):

vite.config.js
import { defineConfig } from 'vite';
import pollora from '@pollora/vite-config';
export default defineConfig({
plugins: [pollora({ type: 'theme', layout: 'standalone' })],
});

The behaviour is the one described on this page: the entry is a script module at the hashed URL in production, and comes from the dev server, with the Vite client, while npm run dev runs. A container without a build resolves nothing and wp-admin names the missing manifest, instead of failing on every page.