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
21 changes: 19 additions & 2 deletions src/Asset/Application/Services/AssetManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace Pollora\Asset\Application\Services;

use Closure;
use Pollora\Asset\Domain\Contracts\PublicPath;
use Pollora\Asset\Domain\Exceptions\AssetException;
use Pollora\Asset\Domain\Models\Asset;
use Pollora\Asset\Infrastructure\Repositories\AssetContainer;
use Pollora\Asset\Infrastructure\Services\AssetEnqueuer;
Expand Down Expand Up @@ -33,8 +36,14 @@ class AssetManager
* Initializes the asset manager with the retrieval service.
*
* @param AssetRetrievalService $retrievalService Service for retrieving assets
* @param PublicPath|null $publicPath Where the hot file of a container without one lives
* @param (Closure(): AssetEnqueuer)|null $enqueuers Builds the enqueuer of an asset
*/
public function __construct(private readonly AssetRetrievalService $retrievalService) {}
public function __construct(
private readonly AssetRetrievalService $retrievalService,
private readonly ?PublicPath $publicPath = null,
private readonly ?Closure $enqueuers = null,
) {}

/**
* Factory method: returns an AssetEnqueuer (builder) for fluent asset management and enqueueing.
Expand All @@ -45,7 +54,11 @@ public function __construct(private readonly AssetRetrievalService $retrievalSer
*/
public function add(string $handle, string $file): AssetEnqueuer
{
return resolve(AssetEnqueuer::class)
if (! $this->enqueuers instanceof Closure) {
throw new AssetException('No asset enqueuer is set: the asset manager was built without one.');
}

return ($this->enqueuers)()
->handle($handle)
->path($file);
}
Expand Down Expand Up @@ -79,6 +92,10 @@ public function all(): array
*/
public function addContainer(string $name, array $config): void
{
if (! isset($config['hot_file']) && $this->publicPath instanceof PublicPath) {
$config['hot_file'] = $this->publicPath->path($name.'.hot');
}

$this->containers[$name] = new AssetContainer($name, $config);
}

Expand Down
7 changes: 5 additions & 2 deletions src/Asset/Application/Services/MissingBuilds.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

namespace Pollora\Asset\Application\Services;

use Illuminate\Support\Facades\Log;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
* The asset containers asked for assets this request whose Vite build is missing.
Expand All @@ -17,6 +18,8 @@
*/
class MissingBuilds
{
public function __construct(private readonly ?LoggerInterface $logger = null) {}

/**
* Manifest path of each container whose build is missing, by container name.
*
Expand All @@ -32,7 +35,7 @@ public function record(string $container, string $manifest): void

$this->missing[$container] = $manifest;

Log::warning(sprintf('Pollora: the "%s" assets are not built, no Vite manifest at %s. Its scripts and styles are left out until you run npm run build.', $container, $manifest), [
($this->logger ?? new NullLogger)->warning(sprintf('Pollora: the "%s" assets are not built, no Vite manifest at %s. Its scripts and styles are left out until you run npm run build.', $container, $manifest), [
'container' => $container,
'manifest' => $manifest,
]);
Expand Down
16 changes: 16 additions & 0 deletions src/Asset/Domain/Contracts/PublicPath.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Pollora\Asset\Domain\Contracts;

/**
* Where built assets and hot files live on disk.
*/
interface PublicPath
{
/**
* The absolute path of a file or directory under the public directory.
*/
public function path(string $to = ''): string;
}
16 changes: 16 additions & 0 deletions src/Asset/Domain/Contracts/RuntimeContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Pollora\Asset\Domain\Contracts;

/**
* What the assets need to know about how the code runs.
*/
interface RuntimeContext
{
/**
* Whether the code runs on the command line (Artisan or WP-CLI): nothing is enqueued there.
*/
public function isCommandLine(): bool;
}
21 changes: 21 additions & 0 deletions src/Asset/Domain/Contracts/ViteManagerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Pollora\Asset\Domain\Contracts;

use Pollora\Asset\Infrastructure\Repositories\AssetContainer;

/**
* Builds the Vite resolver of an asset container.
*
* The environment decides what resolves a container: Laravel's Vite in a
* Pollora application, the manifest and hot file read directly elsewhere.
*/
interface ViteManagerFactory
{
/**
* The resolver of the container: its own hot file, build directory and manifest.
*/
public function make(AssetContainer $container): ViteManagerInterface;
}
41 changes: 41 additions & 0 deletions src/Asset/Infrastructure/Laravel/LaravelAssetFileResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Pollora\Asset\Infrastructure\Laravel;

use Illuminate\Contracts\Container\Container;
use Pollora\Asset\Application\Services\AssetManager;
use Pollora\Asset\Domain\Contracts\ViteManagerFactory;
use Pollora\Asset\Infrastructure\Repositories\AssetContainer;
use Psr\Log\LoggerInterface;

/**
* Turns the file of an AssetFile into its URL, through the container it names.
*
* An unknown container, or any error while resolving, gives an empty string and a line in the log.
*/
final readonly class LaravelAssetFileResolver
{
public function __construct(private Container $app) {}

public function __invoke(string $file, string $container): string
{
try {
$assetContainer = $this->app->make(AssetManager::class)->getContainer($container);

if (! $assetContainer instanceof AssetContainer) {
return '';
}

return $this->app->make(ViteManagerFactory::class)->make($assetContainer)->asset($file);
} catch (\Throwable $throwable) {
$this->app->make(LoggerInterface::class)->error('Error in AssetFile::__toString', [
'error' => $throwable->getMessage(),
'path' => $file,
]);

return '';
}
}
}
18 changes: 18 additions & 0 deletions src/Asset/Infrastructure/Laravel/LaravelPublicPath.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Pollora\Asset\Infrastructure\Laravel;

use Pollora\Asset\Domain\Contracts\PublicPath;

/**
* The public directory of the Laravel application.
*/
final class LaravelPublicPath implements PublicPath
{
public function path(string $to = ''): string
{
return public_path($to);
}
}
21 changes: 21 additions & 0 deletions src/Asset/Infrastructure/Laravel/LaravelRuntimeContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Pollora\Asset\Infrastructure\Laravel;

use Pollora\Application\Application\Services\ConsoleDetectionService;
use Pollora\Asset\Domain\Contracts\RuntimeContext;

/**
* The command line, as Pollora's console detection sees it.
*/
final readonly class LaravelRuntimeContext implements RuntimeContext
{
public function __construct(private ConsoleDetectionService $console) {}

public function isCommandLine(): bool
{
return $this->console->isConsole() || $this->console->isWpCli();
}
}
21 changes: 21 additions & 0 deletions src/Asset/Infrastructure/Laravel/LaravelViteManagerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Pollora\Asset\Infrastructure\Laravel;

use Pollora\Asset\Domain\Contracts\ViteManagerFactory;
use Pollora\Asset\Domain\Contracts\ViteManagerInterface;
use Pollora\Asset\Infrastructure\Repositories\AssetContainer;
use Pollora\Asset\Infrastructure\Services\ViteManager;

/**
* Resolves a container through Laravel's Vite.
*/
final class LaravelViteManagerFactory implements ViteManagerFactory
{
public function make(AssetContainer $container): ViteManagerInterface
{
return new ViteManager($container);
}
}
34 changes: 32 additions & 2 deletions src/Asset/Infrastructure/Providers/AssetServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,29 @@

namespace Pollora\Asset\Infrastructure\Providers;

use Closure;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Pollora\Application\Application\Services\ConsoleDetectionService;
use Pollora\Asset\Application\Services\AssetManager;
use Pollora\Asset\Application\Services\AssetRetrievalService;
use Pollora\Asset\Application\Services\MissingBuilds;
use Pollora\Asset\Domain\Contracts\AssetRepositoryInterface;
use Pollora\Asset\Domain\Contracts\PublicPath;
use Pollora\Asset\Domain\Contracts\RuntimeContext;
use Pollora\Asset\Domain\Contracts\ViteManagerFactory;
use Pollora\Asset\Infrastructure\Laravel\LaravelAssetFileResolver;
use Pollora\Asset\Infrastructure\Laravel\LaravelPublicPath;
use Pollora\Asset\Infrastructure\Laravel\LaravelRuntimeContext;
use Pollora\Asset\Infrastructure\Laravel\LaravelViteManagerFactory;
use Pollora\Asset\Infrastructure\Repositories\InMemoryAssetRepository;
use Pollora\Asset\Infrastructure\Services\AssetEnqueuer;
use Pollora\Asset\Infrastructure\Services\AssetFile;
use Pollora\Asset\Infrastructure\Services\RootAssetManager;
use Pollora\Asset\UI\Http\MissingBuildNotice;
use Pollora\Hook\Domain\Contract\Action;
use Pollora\Hook\Domain\Contract\Filter;
use Psr\Log\LoggerInterface;

/**
* Laravel service provider for asset management services and bindings.
Expand All @@ -32,10 +44,28 @@ class AssetServiceProvider extends ServiceProvider
public function register(): void
{
$this->app->bind(AssetRepositoryInterface::class, InMemoryAssetRepository::class);

// What the assets need from the environment: here, Laravel
$this->app->singleton(PublicPath::class, LaravelPublicPath::class);
$this->app->singleton(ViteManagerFactory::class, LaravelViteManagerFactory::class);
$this->app->singleton(RuntimeContext::class, fn (Application $app): RuntimeContext => new LaravelRuntimeContext($app->make(ConsoleDetectionService::class)));

$this->app->singleton(AssetManager::class, fn (Application $app): AssetManager => new AssetManager(
$app->make(AssetRetrievalService::class)
$app->make(AssetRetrievalService::class),
$app->make(PublicPath::class),
static fn (): AssetEnqueuer => $app->make(AssetEnqueuer::class),
));
// Hooks come from pollora/hook's provider, absent from some test applications
$this->app->bind(AssetEnqueuer::class, fn (Application $app): AssetEnqueuer => new AssetEnqueuer(
$app->make(AssetManager::class),
$app->bound(Action::class) ? $app->make(Action::class) : null,
$app->bound(Filter::class) ? $app->make(Filter::class) : null,
$app->make(ViteManagerFactory::class),
$app->make(RuntimeContext::class),
$app->make(LoggerInterface::class),
));
$this->app->bind(AssetEnqueuer::class, fn (Application $app): AssetEnqueuer => new AssetEnqueuer($app));
AssetFile::resolveUsing(Closure::fromCallable(new LaravelAssetFileResolver($this->app)));
$this->app->bind(MissingBuildNotice::class, fn (Application $app): MissingBuildNotice => new MissingBuildNotice($app->make(MissingBuilds::class), base_path()));
$this->app->singleton(MissingBuilds::class);
$this->app->singleton(RootAssetManager::class, fn (Application $app): RootAssetManager => new RootAssetManager(
$app->make(AssetManager::class)
Expand Down
2 changes: 1 addition & 1 deletion src/Asset/Infrastructure/Repositories/AssetContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class AssetContainer
*/
public function __construct(protected string $name, protected array $config = [])
{
$this->hotFile = $config['hot_file'] ?? public_path($this->name.'.hot');
$this->hotFile = $config['hot_file'] ?? $this->name.'.hot';
$this->buildDirectory = $config['build_directory'] ?? 'build/'.$this->name;
$this->manifestPath = $config['manifest_path'] ?? 'manifest.json';
$this->basePath = $config['base_path'] ?? '';
Expand Down
Loading
Loading