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
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.1 Under development

- feat: add `create()` factories for renderers and Vite configurations, and use them in examples and tests.

## 0.2.0 August 25, 2026

- docs: add `Next steps` section with links to installation, usage, configuration, and testing guides.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ use PHPForge\Vite\Html\HtmlRenderer;
use PHPForge\Vite\Vite;

$vite = Vite::create(
new DevelopmentConfiguration(
DevelopmentConfiguration::create(
devServerUrl: 'http://localhost:5173',
),
entrypoints: ['resources/js/app.js'],
);

echo (new HtmlRenderer())->render($vite->resolve());
echo HtmlRenderer::create()->render($vite->resolve());
```

### Production
Expand All @@ -82,14 +82,14 @@ use PHPForge\Vite\Html\HtmlRenderer;
use PHPForge\Vite\Vite;

$vite = Vite::create(
new ProductionConfiguration(
ProductionConfiguration::create(
manifestPath: '/srv/app/public/build/.vite/manifest.json',
assetBaseUrl: '/build',
),
entrypoints: ['resources/js/app.js'],
);

echo (new HtmlRenderer())->render($vite->resolve());
echo HtmlRenderer::create()->render($vite->resolve());
```

## Documentation
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Configuration objects are immutable and accept only resolved filesystem paths an
```php
use PHPForge\Vite\Configuration\DevelopmentConfiguration;

$configuration = new DevelopmentConfiguration(
$configuration = DevelopmentConfiguration::create(
devServerUrl: 'http://localhost:5173',
includeViteClient: true,
inlineModuleProviders: [],
Expand All @@ -28,7 +28,7 @@ The URL may include a path prefix, but not a query or fragment.
```php
use PHPForge\Vite\Configuration\ProductionConfiguration;

$configuration = new ProductionConfiguration(
$configuration = ProductionConfiguration::create(
manifestPath: '/srv/app/public/build/.vite/manifest.json',
assetBaseUrl: '/build',
modulePreload: true,
Expand Down Expand Up @@ -98,7 +98,7 @@ Resolution does not produce HTML. Use `HtmlRenderer` only when the application w
use PHPForge\Vite\Html\HtmlRenderer;
use PHPForge\Vite\Html\HtmlRenderOptions;

$html = (new HtmlRenderer())->render(
$html = HtmlRenderer::create()->render(
$vite->resolve(),
HtmlRenderOptions::create()
->withNonce($nonce)
Expand Down
16 changes: 8 additions & 8 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ use PHPForge\Vite\Html\HtmlRenderer;
use PHPForge\Vite\Vite;

$configuration = $isDevelopment
? new DevelopmentConfiguration(
? DevelopmentConfiguration::create(
devServerUrl: 'http://localhost:5173',
)
: new ProductionConfiguration(
: ProductionConfiguration::create(
manifestPath: __DIR__ . '/public/build/.vite/manifest.json',
assetBaseUrl: '/build',
);
Expand All @@ -46,7 +46,7 @@ $vite = Vite::create($configuration, entrypoints: ['resources/js/app.js']);

$assets = $vite->resolve();

echo (new HtmlRenderer())->render($assets);
echo HtmlRenderer::create()->render($assets);
```

The production example assumes `__DIR__` is the absolute project root used by the matching Vite configuration.
Expand Down Expand Up @@ -90,10 +90,10 @@ $config = [
'class' => Vite::class,
'__construct()' => [
'configuration' => YII_ENV === 'dev'
? new DevelopmentConfiguration(
? DevelopmentConfiguration::create(
devServerUrl: 'http://localhost:5173',
)
: new ProductionConfiguration(
: ProductionConfiguration::create(
manifestPath: dirname(__DIR__) . '/public/build/.vite/manifest.json',
assetBaseUrl: '/build',
),
Expand All @@ -106,7 +106,7 @@ $config = [
/** @var Vite $vite */
$vite = Yii::$app->get('vite');

echo (new HtmlRenderer())->render($vite->resolve());
echo HtmlRenderer::create()->render($vite->resolve());
```

The `__construct()` entry is Yii2 container syntax. Its values are passed to the framework-independent constructor, and
Expand All @@ -123,7 +123,7 @@ use Yiisoft\Aliases\Aliases;

static function (Aliases $aliases): Vite {
return Vite::create(
new ProductionConfiguration(
ProductionConfiguration::create(
manifestPath: $aliases->get('@public/build/.vite/manifest.json'),
assetBaseUrl: '/build',
),
Expand Down Expand Up @@ -162,7 +162,7 @@ final class ReactRefreshPreamble implements InlineModuleProviderInterface
}
}

$configuration = new DevelopmentConfiguration(
$configuration = DevelopmentConfiguration::create(
devServerUrl: 'http://localhost:5173',
inlineModuleProviders: [new ReactRefreshPreamble()],
);
Expand Down
2 changes: 1 addition & 1 deletion docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ $nonce = base64_encode(random_bytes(18));

header("Content-Security-Policy: script-src 'nonce-{$nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'");

$html = (new HtmlRenderer())->render(
$html = HtmlRenderer::create()->render(
$vite->resolve(),
HtmlRenderOptions::create()->withNonce($nonce),
);
Expand Down
19 changes: 19 additions & 0 deletions src/Configuration/DevelopmentConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ public function __construct(
$this->inlineModuleProviders = $this->normalizeProviders($inlineModuleProviders);
}

/**
* Creates a development-server configuration.
*
* @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<mixed> $inlineModuleProviders Providers of application-owned inline modules.
*
* @throws ConfigurationException if the development-server URL is invalid, or if a provider is unsupported.
*
* @return self A new development-server configuration.
*/
public static function create(
string $devServerUrl,
bool $includeViteClient = true,
array $inlineModuleProviders = [],
): self {
return new self($devServerUrl, $includeViteClient, $inlineModuleProviders);
}

/**
* Rejects any provider that does not satisfy the contract, and reindexes the survivors as a list.
*
Expand Down
19 changes: 19 additions & 0 deletions src/Configuration/ProductionConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,23 @@ public function __construct(string $manifestPath, string $assetBaseUrl, public b
$this->manifestPath = Path::requireAbsolute($manifestPath, 'manifestPath');
$this->assetBaseUrl = Url::normalizeAssetBaseUrl($assetBaseUrl);
}

/**
* Creates a production-manifest configuration.
*
* @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 or base URL is invalid.
*
* @return self A new production-manifest configuration.
*/
public static function create(
string $manifestPath,
string $assetBaseUrl,
bool $modulePreload = true,
): self {
return new self($manifestPath, $assetBaseUrl, $modulePreload);
}
}
10 changes: 10 additions & 0 deletions src/Html/HtmlRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ final class HtmlRenderer
'type' => true,
];

/**
* Creates an HTML renderer.
*
* @return self A new HTML renderer.
*/
public static function create(): self
{
return new self();
}

/**
* Renders a collection of neutral assets as HTML5 tags joined by the configured separator.
*
Expand Down
81 changes: 72 additions & 9 deletions tests/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,48 @@ public function testAssetValueAcceptsCaseInsensitiveHttpScheme(): void
);
}

public function testConfigurationFactoriesCreateConfiguredInstances(): void
{
$manifestPath = __DIR__ . '/Fixture/manifest.json';

$provider = new CapturingInlineModuleProviderStub();

$development = DevelopmentConfiguration::create(
'http://localhost:5173',
false,
[$provider],
);
$production = ProductionConfiguration::create(
$manifestPath,
'/build',
false,
);

self::assertSame(
'http://localhost:5173',
$development->devServerUrl,
'The development factory must preserve the normalized server URL.',
);
self::assertFalse(
$development->includeViteClient,
'The development factory must preserve the client setting.',
);
self::assertSame(
[$provider],
$development->inlineModuleProviders,
'The development factory must preserve inline module providers.',
);
self::assertSame(
$manifestPath,
$production->manifestPath,
'The production factory must preserve the manifest path.',
);
self::assertFalse(
$production->modulePreload,
'The production factory must preserve the preload setting.',
);
}

public function testCreateReturnsConfiguredViteFacade(): void
{
$manifestPath = __DIR__ . '/Fixture/manifest.json';
Expand All @@ -41,7 +83,7 @@ public function testCreateReturnsConfiguredViteFacade(): void
$cachedManifest = $loader->load($manifestPath);

$vite = Vite::create(
new ProductionConfiguration($manifestPath, '/build'),
ProductionConfiguration::create($manifestPath, '/build'),
['views/foo.js'],
$loader,
);
Expand All @@ -64,11 +106,22 @@ public function testCreateReturnsConfiguredViteFacade(): void
);
}

public function testDevelopmentConfigurationEnablesViteClientByDefault(): void
{
$configuration = new DevelopmentConfiguration('http://localhost:5173');

self::assertTrue(
$configuration->includeViteClient,
'The development configuration must enable the Vite client by default.',
);
}

public function testDevelopmentConfigurationNormalizesValues(): void
{
$firstProvider = new CapturingInlineModuleProviderStub();
$secondProvider = new CapturingInlineModuleProviderStub();
$configuration = new DevelopmentConfiguration(

$configuration = DevelopmentConfiguration::create(
devServerUrl: ' HTTPS://localhost:5173/vite/ ',
inlineModuleProviders: [$firstProvider, $secondProvider],
);
Expand All @@ -89,9 +142,19 @@ public function testDevelopmentConfigurationNormalizesValues(): void
);
}

public function testProductionConfigurationEnablesModulePreloadByDefault(): void
{
$configuration = new ProductionConfiguration(__DIR__ . '/Fixture/manifest.json', '/build');

self::assertTrue(
$configuration->modulePreload,
'The production configuration must enable module preloading by default.',
);
}

public function testProductionConfigurationNormalizesBaseUrl(): void
{
$configuration = new ProductionConfiguration(
$configuration = ProductionConfiguration::create(
manifestPath: __DIR__ . '/Fixture/manifest.json',
assetBaseUrl: ' HTTPS://cdn.example.com/build/ ',
);
Expand All @@ -115,7 +178,7 @@ public function testThrowConfigurationExceptionForInvalidDevelopmentServerUrl(st
$message->getMessage(),
);

new DevelopmentConfiguration($url);
DevelopmentConfiguration::create($url);
}

public function testThrowConfigurationExceptionForInvalidInlineModuleProvider(): void
Expand All @@ -125,7 +188,7 @@ public function testThrowConfigurationExceptionForInvalidInlineModuleProvider():
Message::DEVELOPMENT_INLINE_MODULE_PROVIDER_INVALID->getMessage(),
);

new DevelopmentConfiguration('http://localhost:5173', inlineModuleProviders: [new stdClass()]);
DevelopmentConfiguration::create('http://localhost:5173', inlineModuleProviders: [new stdClass()]);
}

public function testThrowConfigurationExceptionForNonAbsoluteManifestPath(): void
Expand All @@ -135,7 +198,7 @@ public function testThrowConfigurationExceptionForNonAbsoluteManifestPath(): voi
Message::FILESYSTEM_PATH_INVALID->getMessage('manifestPath'),
);

new ProductionConfiguration('@webroot/build/.vite/manifest.json', '/build');
ProductionConfiguration::create('@webroot/build/.vite/manifest.json', '/build');
}

#[DataProviderExternal(ConfigurationProvider::class, 'unsafeAssetUrls')]
Expand All @@ -157,7 +220,7 @@ public function testThrowConfigurationExceptionForUnsafeProductionBaseUrl(string
$message->getMessage(),
);

new ProductionConfiguration(__DIR__ . '/Fixture/manifest.json', $url);
ProductionConfiguration::create(__DIR__ . '/Fixture/manifest.json', $url);
}

public function testThrowConfigurationExceptionForWhitespaceInlineModuleSource(): void
Expand All @@ -180,7 +243,7 @@ public function testThrowInvalidEntrypointExceptionForInvalidRelativeSourcePath(
$message->getMessage(),
);

new Vite(new DevelopmentConfiguration('http://localhost:5173'), [$entrypoint]);
Vite::create(DevelopmentConfiguration::create('http://localhost:5173'), [$entrypoint]);
}

public function testThrowInvalidEntrypointExceptionForNonStringEntrypoint(): void
Expand All @@ -190,6 +253,6 @@ public function testThrowInvalidEntrypointExceptionForNonStringEntrypoint(): voi
Message::ENTRYPOINT_TYPE_INVALID->getMessage(),
);

new Vite(new DevelopmentConfiguration('http://localhost:5173'), [123]);
Vite::create(DevelopmentConfiguration::create('http://localhost:5173'), [123]);
}
}
Loading
Loading