# Assets & Vite

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

Source: https://pollora.dev/theming/assets-vite/

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

## Basic Usage
The `Asset` class simplifies adding CSS and JavaScript assets with chainable methods:
```php
$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:

```php
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)

### Configuring Custom Asset Containers

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:

```php
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
]);
```

## ViteJS Integration

To use ViteJS for asset management:

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

### Vite entries are script modules

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:

| Theme | Where the Vite script is printed |
|---|---|
| Block theme | in the `<head>`, or in the footer with `loadInFooter()` |
| Classic theme | in 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">`.

### Configuration Options

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.

### Localizing JavaScript

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

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

### Inline Content

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

```php
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){}');
```

### Default Container

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:

```php
// 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:

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

### Non-Vite Assets

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

```php
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.

## Accessing Assets

You can easily reference assets using the `Asset` facade:

```php
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');
```

### Using a Specific Asset Container

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:

```php
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');
```

### Inline Usage in Blade Templates

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

```blade
<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>
```

### How It Works

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.

### Examples

```php
// 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.

## Important: Use of the Service Provider

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

The asset classes are the package [`pollora/assets`](https://github.com/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:

```php
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`):

```js
// 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.
