Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/linters/.editorconfig-checker.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"Exclude": [
"^composer\\.lock$",
"^runtime/",
"^tests/Debug/fixtures/",
"^vendor/"
]
}
3 changes: 0 additions & 3 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,3 @@ jobs:
codespell-config: .github/linters/.codespellrc
editorconfig-checker-config: .github/linters/.editorconfig-checker.json
markdownlint-config: .github/linters/.markdown-lint.yml
prettier-config: .prettierrc.json
prettier-ignore-path: .prettierignore
yamllint-targets: .github
12 changes: 0 additions & 12 deletions .prettierignore

This file was deleted.

22 changes: 0 additions & 22 deletions .prettierrc.json

This file was deleted.

2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## 0.2.2 Under development

- feat: provide a declarative Vite debugger panel through `php-forge/debug`.

## 0.2.1 August 25, 2026

- feat: add `create()` factories for renderers and Vite configurations, and use them in examples and tests.
Expand Down
44 changes: 30 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,8 @@
composer require php-forge/vite:^0.2
```

HTML output is generated with [`ui-awesome/html`](https://github.com/ui-awesome/html) while asset resolution remains
independent from its representation.

The consuming application owns Vite and every JavaScript dependency. Configure Vite to write a build manifest:

```js
import {defineConfig} from 'vite';

export default defineConfig({
build: {
manifest: true,
},
});
```
The consuming application owns Vite and every JavaScript dependency. Production resolution needs Vite's build
manifest; see [Installation](docs/installation.md) for the `vite.config.js` settings and the resulting manifest path.

## Quick start

Expand Down Expand Up @@ -99,8 +87,36 @@ echo HtmlRenderer::create()->render($vite->resolve());
- [Manifest resolution](docs/manifest.md)
- [Usage examples](docs/examples.md)
- [Security and CSP](docs/security.md)
- [Debugger integration](docs/debugging.md)
- [Testing guide](docs/testing.md)

## Debugger integration

`Vite::resolve()` emits `AssetsResolved` through an optional PSR-14 dispatcher. The asset resolution path never
imports a debug contract or calls a collector, so installing the package does not activate a debugger and a call
without a dispatcher emits no events. The collector and panel that do implement those contracts live apart, in
`PHPForge\Vite\Debug`, owned by this package.

See [Debugger integration](docs/debugging.md) for the Yii2 and Yii3 wiring. It remains an unreleased prototype.

<details>
<summary>Yii2</summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/images/yii2-dark.png">
<source media="(prefers-color-scheme: light)" srcset="docs/images/yii2-light.png">
<img src="docs/images/yii2-light.png" alt="Vite panel in Yii2">
</picture>
</details>

<details>
<summary>Yii3</summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/images/yii3-dark.png">
<source media="(prefers-color-scheme: light)" srcset="docs/images/yii3-light.png">
<img src="docs/images/yii3-light.png" alt="Vite panel in Yii3">
</picture>
</details>

## Package information

[![PHP](https://img.shields.io/badge/%3E%3D8.3-777BB4.svg?style=for-the-badge&logo=php&logoColor=white)](https://www.php.net/releases/8.3/en.php)
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"prefer-stable": true,
"require": {
"php": ">=8.3",
"ui-awesome/html": "^0.6"
"ui-awesome/html": "^0.6",
"php-forge/debug": "^0.1@dev",
"psr/event-dispatcher": "^1.0"
},
"require-dev": {
"infection/infection": "^0.35",
Expand Down
35 changes: 10 additions & 25 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,11 @@ $pageAssets = $vite->resolve('resources/js/admin.js');
$combinedAssets = $vite->resolve(['resources/js/app.js', 'resources/js/admin.js']);
```

`Vite::create()` is an additive construction shortcut. The public constructor remains available for dependency-injection
containers and accepts the same arguments.
`Vite::create()` is a shortcut; the public constructor takes the same arguments and suits DI containers.

Default entrypoints belong to the facade because they apply equally to development and production. Duplicate entrypoints
are removed while preserving the first occurrence. At least one entrypoint must be available when `Vite::resolve()` is
called.

An explicit argument to `resolve()` replaces the facade's default entrypoints for that call. Entrypoint identifiers are
Vite manifest keys or development source paths, not filesystem paths.
An explicit argument to `resolve()` replaces the facade's default entrypoints for that call. Duplicates are removed
keeping the first occurrence, and at least one entrypoint must be available. Identifiers are Vite manifest keys or
development source paths, never filesystem paths.

## Manifest loading and cache

Expand Down Expand Up @@ -107,14 +103,8 @@ $html = HtmlRenderer::create()->render(
);
```

`HtmlRenderOptions::create()` starts with the default policy. Use `withNonce()`, `withSeparator()`, the four per-asset
attribute modifiers, and `withAttributeProvider()` to replace individual values. Every modifier returns a new policy and
leaves the original instance unchanged.

`HtmlRenderer` maps the neutral asset objects to `ui-awesome/html` `Script` and `Link` elements. Applications that consume
`AssetCollection` directly do not depend on the renderer's markup structure.

See [Security and CSP](security.md) for the attribute and nonce policy.
Every `HtmlRenderOptions` modifier returns a new policy and leaves the original unchanged. See
[Security and CSP](security.md) for the full attribute and nonce policy.

## Neutral asset collection

Expand All @@ -125,9 +115,8 @@ See [Security and CSP](security.md) for the attribute and nonce policy.
- `ModulePreload` with a public `url`;
- `InlineModule` with public application-owned JavaScript `source`.

The collection is countable and iterable. Use `all()`, `moduleScripts()`, `stylesheets()`, `modulePreloads()`, or
`inlineModules()` to obtain ordered lists. `append()` and `prepend()` return new deduplicated collections without mutating the
original instance.
The collection is countable and iterable. `all()`, `moduleScripts()`, `stylesheets()`, `modulePreloads()` and
`inlineModules()` return ordered lists; `append()` and `prepend()` return new deduplicated collections.

## Exception hierarchy

Expand All @@ -148,10 +137,6 @@ ViteException
Configuration and rendering exceptions extend `InvalidArgumentException`. Manifest exceptions extend `RuntimeException`.
Callers can catch one specific failure, a category base class, or the common marker interface.

## Next steps
---

- 📚 [Installation guide](installation.md)
- 📦 [Manifest resolution](manifest.md)
- 💡 [Usage examples](examples.md)
- 🔒 [Security and CSP](security.md)
- 🧪 [Testing guide](testing.md)
[← Back to documentation](../README.md#documentation)
99 changes: 99 additions & 0 deletions docs/debugging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Vite diagnostics

## PSR-14, and nothing else

`Vite` emits `PHPForge\Vite\Event\AssetsResolved` through an optional `Psr\EventDispatcher\EventDispatcherInterface`.
The application classes never import a debug contract or call a collector, and installing the contracts does not
activate a debugger.

The event carries the configuration, the **actual per-call entrypoints**, the returned assets, and the manifest a
successful resolver already used. The collector never loads a manifest or runs an inline provider. Each successful
resolution becomes a component row; repeated calls are recorded in order, not deduplicated into one. Failed resolutions
do not invent completion events.

## Development wiring

The package ships one collector and one panel, `PHPForge\Vite\Debug\ViteCollector` and
`PHPForge\Vite\Debug\VitePanel`; the host never reimplements collection or presentation. What differs between
frameworks is only how the dispatcher reaches the `PHPForge\Vite\Vite` service.

### Yii3, one flag and no application code

`yii3/debug` registers the collector and panel behind a flag and attaches the collector as a listener; the container
autowires `Psr\EventDispatcher\EventDispatcherInterface` into `Vite`, so the application adds nothing else.

```php
return [
'yii3/debug' => [
'extensions' => [
'vite' => true,
],
],
];
```

Enabling the flag without `php-forge/vite` installed fails with an explicit container error.

### Yii2, one registration

Yii2 has no framework-native PSR-14 dispatcher, and its DI container does not autowire optional constructor arguments.
`Vite` emits exactly one event type, so `ViteCollector` is its own single-listener dispatcher and the application
writes no PSR-14 code.

Inside the existing `YII_DEBUG` configuration guard:

```php
use PHPForge\Vite\Debug\{ViteCollector, VitePanel};

$viteCollector = new ViteCollector();

// Keep the component ID already used by the application.
$config['components']['vite']['__construct()']['eventDispatcher'] = $viteCollector;
$config['modules']['debug']['collectors']['vite'] = $viteCollector;
$config['modules']['debug']['panels']['vite'] = new VitePanel();
```

If the application already owns a real PSR-14 dispatcher, register the collector as a listener on it and inject that
dispatcher instead; never replace a populated dispatcher with an empty one.

`yii2-extensions/debug` no longer ships a Vite collector or panel, so the `vite` ID is free: the module wraps the
portable objects in its generic adapters and groups them under Extensions. Retain the existing module bootstrap,
routing, access rules, and asset configuration.

Inject the dispatcher into the **actual** Vite service, not into a duplicate diagnostic-only one. Omitting it is safe:
resolution behaves normally and the panel simply stays empty.

## Lifecycle and errors

Register the collector once and let the host drive it. `startup()` enables the listener without discarding current
observations if called twice; `shutdown()` disables it and clears references. Events outside that window are ignored.

An active but unused collector captures `['components' => []]`; disabled collection captures `null`.

Attach the event only to trusted listeners: it carries real application data, so never point an unrestricted event
dumper at it. Listener exceptions propagate per PSR-14; they are never swallowed and never retried. The supplied
listener only buffers the event.

## Compatibility

Hosts previously shipped their own Vite collector and panel; both were removed, so the `vite` ID is free for the
provider-owned objects an application registers itself. An extension now declares its own ID, icon and title. Captures
written by the removed host collector still render, because the payload shape is unchanged.

`eventDispatcher` is an optional trailing argument: calls without one behave normally and emit no events. The branch
aliases still describe an unreleased linked prototype, not a published release.

## How this is verified

- The full PHPUnit suite runs through this package's own autoloader, without Debug Core installed.
- `python3 tools/check-provider-consumer.py` (in `php-forge/debug`) exports the package and its locked production
dependencies into a temporary mirror, installs with Packagist and plugins disabled, then exercises real resolution,
cleanup and replay with no debugger or framework present.
- `DEBUG_UI_SEED_FIXTURES=0 npx playwright test e2e/provider-events.spec.js` (in Debug Core) checks persisted values
after a changed request, including an empty Vite capture, accessibility, and both themes and viewport sizes.

Reference: [PSR-14](https://www.php-fig.org/psr/psr-14/).

---

[← Back to documentation](../README.md#documentation)
75 changes: 14 additions & 61 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,9 @@

## Matching Vite build configuration

The plain PHP, Yii2, and Yii3 production examples below target Vite 5 or later and assume this application-owned
configuration:

```js
import {defineConfig} from 'vite';

export default defineConfig({
build: {
outDir: 'public/build',
manifest: '.vite/manifest.json',
rollupOptions: {
input: 'resources/js/app.js',
},
},
});
```

Because `build.manifest` is relative to `build.outDir`, this configuration writes
`<project-root>/public/build/.vite/manifest.json`. Each PHP example resolves that same file through the path mechanism of
its application or framework.
Every example below targets Vite 5 or later and assumes the application-owned `vite.config.js` from
[Installation](installation.md), which writes `<project-root>/public/build/.vite/manifest.json`. Each example resolves
that same file through the path mechanism of its application or framework.

## Plain PHP

Expand Down Expand Up @@ -75,43 +58,22 @@ The example methods belong to the consuming application; they are not package AP

## Yii2 integration

Register the facade as an application component so Yii2 owns its lazy construction and lifecycle. The package does not
access `Yii::getAlias()`, the service locator, or `yii\web\View`:
Register the facade as a component so Yii2 owns its lazy construction. `__construct()` is Yii2 container syntax and its
values reach the framework-independent constructor unchanged; it is the same `$configuration` built above. The package never
touches `Yii::getAlias()`, the service locator, or `yii\web\View`:

```php
use PHPForge\Vite\Configuration\DevelopmentConfiguration;
use PHPForge\Vite\Configuration\ProductionConfiguration;
use PHPForge\Vite\Html\HtmlRenderer;
use PHPForge\Vite\Vite;

$config = [
'components' => [
'vite' => [
'class' => Vite::class,
'__construct()' => [
'configuration' => YII_ENV === 'dev'
? DevelopmentConfiguration::create(
devServerUrl: 'http://localhost:5173',
)
: ProductionConfiguration::create(
manifestPath: dirname(__DIR__) . '/public/build/.vite/manifest.json',
assetBaseUrl: '/build',
),
'entrypoints' => ['resources/js/app.js'],
],
],
$config['components']['vite'] = [
'class' => PHPForge\Vite\Vite::class,
'__construct()' => [
'configuration' => $configuration,
'entrypoints' => ['resources/js/app.js'],
],
];

/** @var Vite $vite */
$vite = Yii::$app->get('vite');

echo HtmlRenderer::create()->render($vite->resolve());
echo HtmlRenderer::create()->render(Yii::$app->get('vite')->resolve());
```

The `__construct()` entry is Yii2 container syntax. Its values are passed to the framework-independent constructor, and
the concrete manifest path is resolved entirely by the consuming application.

## Yii3 integration

Resolve the framework aliases in the application's dependency-injection configuration, then inject the same `Vite` class:
Expand Down Expand Up @@ -173,15 +135,6 @@ $vite = Vite::create($configuration, entrypoints: ['resources/js/app.jsx']);
Providers run in their configured order before `@vite/client` and the entrypoint scripts. The application owns the provider
code and the matching Vite plugin dependency.

## Optional Foxy usage

[`php-forge/foxy`](https://github.com/php-forge/foxy) may be used independently by a consuming project to coordinate its
Composer and JavaScript dependencies. It is not installed, invoked, or configured by this package.

## Next steps
---

- 📚 [Installation guide](installation.md)
- ⚙️ [Configuration reference](configuration.md)
- 📦 [Manifest resolution](manifest.md)
- 🔒 [Security and CSP](security.md)
- 🧪 [Testing guide](testing.md)
[← Back to documentation](../README.md#documentation)
Binary file added docs/images/yii2-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/yii2-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/yii3-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/yii3-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading