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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## 0.1.0 Under development

- feat: added a framework-agnostic Vite facade with explicit development and production configuration.
- docs: add class-level PHPDoc for the framework-neutral Vite APIs.
71 changes: 60 additions & 11 deletions src/Asset/AssetCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
final readonly class AssetCollection implements Countable, IteratorAggregate
{
/**
* @var list<AssetInterface>
* @var list<AssetInterface> Accepted assets in insertion order, with per-type duplicates removed.
*/
private array $assets;

/**
* @param iterable<AssetInterface> $assets
* @param iterable<AssetInterface> $assets Assets to collect.
*
* @throws ConfigurationException if a value does not implement {@see AssetInterface}, or implements it without
* being one of the four supported asset types.
*/
public function __construct(iterable $assets = [])
{
Expand All @@ -52,33 +55,55 @@ public function __construct(iterable $assets = [])
}

/**
* @return list<AssetInterface>
* Returns every collected asset in insertion order, regardless of type.
*
* @return list<AssetInterface> The collected assets.
*/
public function all(): array
{
return $this->assets;
}

public function append(AssetInterface ...$assets): self
/**
* Returns a new collection with the supplied assets added after the current ones.
*
* The receiver is left untouched; deduplication is re-applied across the combined sequence.
*
* @param AssetInterface ...$assets Assets to add at the end.
*
* @throws ConfigurationException if an asset is not one of the four supported types.
*
* @return AssetCollection A new collection holding both sequences.
*/
public function append(AssetInterface ...$assets): AssetCollection
{
return new self([...$this->assets, ...$assets]);
}

/**
* Counts the collected assets after deduplication.
*
* @return int<0, max> Number of assets in the collection.
*/
public function count(): int
{
return count($this->assets);
}

/**
* @return Traversable<int, AssetInterface>
* Iterates over the collected assets in insertion order.
*
* @return Traversable<int, AssetInterface> Iterator over the collected assets.
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->assets);
}

/**
* @return list<InlineModule>
* Filters the collection down to its inline modules.
*
* @return list<InlineModule> Inline modules in insertion order.
*/
public function inlineModules(): array
{
Expand All @@ -94,7 +119,9 @@ public function inlineModules(): array
}

/**
* @return list<ModulePreload>
* Filters the collection down to its module-preload hints.
*
* @return list<ModulePreload> Module preloads in insertion order.
*/
public function modulePreloads(): array
{
Expand All @@ -110,7 +137,9 @@ public function modulePreloads(): array
}

/**
* @return list<ModuleScript>
* Filters the collection down to its module scripts.
*
* @return list<ModuleScript> Module scripts in insertion order.
*/
public function moduleScripts(): array
{
Expand All @@ -125,13 +154,26 @@ public function moduleScripts(): array
return $assets;
}

public function prepend(AssetInterface ...$assets): self
/**
* Returns a new collection with the supplied assets added before the current ones.
*
* The receiver is left untouched; deduplication is re-applied across the combined sequence.
*
* @param AssetInterface ...$assets Assets to add at the beginning.
*
* @throws ConfigurationException if an asset is not one of the four supported types.
*
* @return AssetCollection A new collection holding both sequences.
*/
public function prepend(AssetInterface ...$assets): AssetCollection
{
return new self([...$assets, ...$this->assets]);
}

/**
* @return list<Stylesheet>
* Filters the collection down to its stylesheets.
*
* @return list<Stylesheet> Stylesheets in insertion order.
*/
public function stylesheets(): array
{
Expand All @@ -147,7 +189,14 @@ public function stylesheets(): array
}

/**
* @return InlineModule|ModulePreload|ModuleScript|Stylesheet
* Narrows an arbitrary value to one of the four supported asset types.
*
* @param mixed $asset Value taken from the incoming iterable.
*
* @throws ConfigurationException if the value does not implement {@see AssetInterface}, or implements it without
* being a supported type.
*
* @return InlineModule|ModulePreload|ModuleScript|Stylesheet The narrowed asset.
*/
private function requireAsset(mixed $asset): AssetInterface
{
Expand Down
8 changes: 8 additions & 0 deletions src/Asset/InlineModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@

use function trim;

/**
* Represents validated inline JavaScript module source.
*/
final readonly class InlineModule implements AssetInterface
{
/**
* @param string $source JavaScript module source rendered inside a `<script type="module">` element.
*
* @throws ConfigurationException if the source is empty or contains only whitespace.
*/
public function __construct(public string $source)
{
if (trim($source) === '') {
Expand Down
13 changes: 13 additions & 0 deletions src/Asset/ModulePreload.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@

namespace PHPForge\Vite\Asset;

use PHPForge\Vite\Exception\ConfigurationException;
use PHPForge\Vite\Support\Url;

/**
* Represents a validated module-preload asset URL.
*/
final readonly class ModulePreload implements AssetInterface
{
/**
* Absolute or relative URL of the chunk to preload.
*/
public string $url;

/**
* @param string $url Asset URL emitted as the `href` of a `modulepreload` link.
*
* @throws ConfigurationException if the URL is empty, malformed, protocol-relative, or uses a scheme other than
* HTTP(S).
*/
public function __construct(string $url)
{
$this->url = Url::requireSafeAssetUrl($url);
Expand Down
13 changes: 13 additions & 0 deletions src/Asset/ModuleScript.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@

namespace PHPForge\Vite\Asset;

use PHPForge\Vite\Exception\ConfigurationException;
use PHPForge\Vite\Support\Url;

/**
* Represents a validated JavaScript module asset URL.
*/
final readonly class ModuleScript implements AssetInterface
{
/**
* Absolute or relative URL of the JavaScript module to execute.
*/
public string $url;

/**
* @param string $url Asset URL emitted as the `src` of a `<script type="module">` element.
*
* @throws ConfigurationException if the URL is empty, malformed, protocol-relative, or uses a scheme other than
* HTTP(S).
*/
public function __construct(string $url)
{
$this->url = Url::requireSafeAssetUrl($url);
Expand Down
13 changes: 13 additions & 0 deletions src/Asset/Stylesheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@

namespace PHPForge\Vite\Asset;

use PHPForge\Vite\Exception\ConfigurationException;
use PHPForge\Vite\Support\Url;

/**
* Represents a validated stylesheet asset URL.
*/
final readonly class Stylesheet implements AssetInterface
{
/**
* Absolute or relative URL of the stylesheet to link.
*/
public string $url;

/**
* @param string $url Asset URL emitted as the `href` of a `stylesheet` link.
*
* @throws ConfigurationException if the URL is empty, malformed, protocol-relative, or uses a scheme other than
* HTTP(S).
*/
public function __construct(string $url)
{
$this->url = Url::requireSafeAssetUrl($url);
Expand Down
23 changes: 19 additions & 4 deletions src/Configuration/DevelopmentConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,43 @@
*/
final readonly class DevelopmentConfiguration
{
/**
* Normalized development-server URL, without a trailing separator.
*/
public string $devServerUrl;

/**
* @var list<InlineModuleProviderInterface>
* @var list<InlineModuleProviderInterface> Providers emitted, in order, before the Vite client.
*/
public array $inlineModuleProviders;

/**
* @param list<InlineModuleProviderInterface> $inlineModuleProviders
* @param string $devServerUrl Absolute HTTP(S) URL of the running Vite development server.
* @param bool $includeViteClient Whether the `@vite/client` module script is emitted.
* @param list<InlineModuleProviderInterface> $inlineModuleProviders Providers of application-owned inline
* modules that must run before the Vite client.
*
* @throws ConfigurationException if the development-server URL is not an absolute HTTP(S) URL, or if a provider
* does not implement {@see InlineModuleProviderInterface}.
*/
public function __construct(
string $devServerUrl,
public bool $includeViteClient = true,
array $inlineModuleProviders = [],
) {
$this->devServerUrl = Url::normalizeDevServerUrl($devServerUrl);

$this->inlineModuleProviders = $this->normalizeProviders($inlineModuleProviders);
}

/**
* @param iterable<mixed> $providers
* Rejects any provider that does not satisfy the contract, and reindexes the survivors as a list.
*
* @param iterable<mixed> $providers Raw providers supplied by the application.
*
* @throws ConfigurationException if a value does not implement {@see InlineModuleProviderInterface}.
*
* @return list<InlineModuleProviderInterface>
* @return list<InlineModuleProviderInterface> Providers in the order supplied.
*/
private function normalizeProviders(iterable $providers): array
{
Expand Down
23 changes: 18 additions & 5 deletions src/Configuration/ProductionConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,34 @@

namespace PHPForge\Vite\Configuration;

use PHPForge\Vite\Exception\ConfigurationException;
use PHPForge\Vite\Support\{Path, Url};

/**
* Immutable configuration for Vite production-manifest resolution.
*/
final readonly class ProductionConfiguration
{
/**
* Normalized base URL prefixed to every emitted asset path, without a trailing separator.
*/
public string $assetBaseUrl;

/**
* Validated absolute path to the Vite build manifest.
*/
public string $manifestPath;

public function __construct(
string $manifestPath,
string $assetBaseUrl,
public bool $modulePreload = true,
) {
/**
* @param string $manifestPath Absolute path to the manifest emitted by the Vite build.
* @param string $assetBaseUrl Public base URL of the build output, absolute or relative.
* @param bool $modulePreload Whether `modulepreload` hints are emitted for transitive imports.
*
* @throws ConfigurationException if the manifest path is not absolute, or if the base URL is malformed,
* protocol-relative, carries a query or fragment, or uses a scheme other than HTTP(S).
*/
public function __construct(string $manifestPath, string $assetBaseUrl, public bool $modulePreload = true)
{
$this->manifestPath = Path::requireAbsolute($manifestPath, 'manifestPath');
$this->assetBaseUrl = Url::normalizeAssetBaseUrl($assetBaseUrl);
}
Expand Down
12 changes: 12 additions & 0 deletions src/Development/InlineModuleProviderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@
namespace PHPForge\Vite\Development;

use PHPForge\Vite\Asset\InlineModule;
use PHPForge\Vite\Exception\ConfigurationException;

/**
* Provides application-owned inline modules that must run before the Vite client.
*/
interface InlineModuleProviderInterface
{
/**
* Builds the inline module to emit ahead of the Vite client and the configured entrypoints.
*
* Implementations own the module source; the package only positions the result in the resolved asset order.
*
* @param string $devServerUrl Normalized development-server URL, without a trailing separator.
*
* @throws ConfigurationException if the produced source is empty or contains only whitespace.
*
* @return InlineModule Inline module rendered before the Vite client.
*/
public function provide(string $devServerUrl): InlineModule;
}
3 changes: 3 additions & 0 deletions src/Exception/ConfigurationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@

use InvalidArgumentException;

/**
* Reports invalid Vite integration configuration.
*/
class ConfigurationException extends InvalidArgumentException implements ViteException {}
3 changes: 3 additions & 0 deletions src/Exception/EntrypointNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@

namespace PHPForge\Vite\Exception;

/**
* Reports an entrypoint missing from a loaded Vite manifest.
*/
final class EntrypointNotFoundException extends ManifestException {}
6 changes: 6 additions & 0 deletions src/Exception/HtmlRenderingException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@

use InvalidArgumentException;

/**
* Reports a failure encountered while rendering neutral Vite assets as HTML.
*
* Raised for an unsafe or unsupported render option, for an asset implementation the renderer cannot map to a tag,
* and when inline module source cannot be neutralized.
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.
final class HtmlRenderingException extends InvalidArgumentException implements ViteException {}
3 changes: 3 additions & 0 deletions src/Exception/InvalidEntrypointException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@

namespace PHPForge\Vite\Exception;

/**
* Reports an invalid Vite entrypoint value.
*/
final class InvalidEntrypointException extends ConfigurationException {}
3 changes: 3 additions & 0 deletions src/Exception/InvalidManifestException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@

namespace PHPForge\Vite\Exception;

/**
* Reports malformed or inconsistent Vite manifest data.
*/
final class InvalidManifestException extends ManifestException {}
Loading
Loading