Skip to content
Open
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
29 changes: 29 additions & 0 deletions docs/assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Following configurations are available:
| location | int | falls back to `Asset::FRONTEND` | x | x | x | depending on location of the `Asset`, it will be enqueued with different hooks |
| version | string | `null` | x | x | x | version of the given asset |
| enqueue | bool/callable | `true` | x | x | x | is the asset only registered or also enqueued |
| priority | int | `10` | x | x | x | controls asset output order within the same location (lower = earlier) |
| data | array/callable | `[]` | x | | x | additional data assigned to the asset via `WP_Script::add_data` or `WP_Style::add_data` |
| filters | callable[] | `[]` | x | | x | an array of `Inpsyde\Assets\OutputFilter` or callable values to manipulate the output |
| handler | string | `ScriptHandler::class`, `StyleHandler::class`, `ScriptModuleHandler::class` | x | x | x | The handler which will be used to register/enqueue the Asset |
Expand Down Expand Up @@ -161,6 +162,34 @@ add_action(
);
```

### Priority

Assets can be assigned a priority to control their output order within the same location. Lower values are processed first (default is `10`):

```php
<?php
use Inpsyde\Assets\Script;
use Inpsyde\Assets\Style;
use Inpsyde\Assets\Asset;

// This script will be output first (priority 5)
$criticalScript = new Script('critical', 'critical.js', Asset::FRONTEND);
$criticalScript->withPriority(5);

// This script will be output last (priority 20)
$deferredScript = new Script('less-important', 'less-important.js', Asset::FRONTEND);
$deferredScript->withPriority(20);

// This script keeps the default priority (10)
$normalScript = new Script('normal', 'normal.js', Asset::FRONTEND);

// Same works for styles
$resetStyle = new Style('reset', 'reset.css', Asset::FRONTEND);
$resetStyle->withPriority(1); // Output first among styles
```

This is useful when enqueueing is spread accross a large codebase and you need [fine grained control over the order](https://rviscomi.github.io/capo.js/).

### Dependencies resolving

Adding an `Asset` with dependencies can be done like following:
Expand Down
11 changes: 10 additions & 1 deletion src/AssetCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ public function has(string $handle, string $type): bool
*/
public function all(): array
{
return $this->assets;
$sorted = [];
foreach ($this->assets as $type => $assets) {
uasort($assets, static function (Asset $assetA, Asset $assetB): int {
$priorityA = $assetA instanceof PrioritizedAsset ? $assetA->priority() : 10;
$priorityB = $assetB instanceof PrioritizedAsset ? $assetB->priority() : 10;
return $priorityA <=> $priorityB;
});
$sorted[$type] = $assets;
}
return $sorted;
}
}
31 changes: 30 additions & 1 deletion src/BaseAsset.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/**
* phpcs:disable Syde.Classes.PropertyLimit.TooManyProperties
*/
abstract class BaseAsset implements Asset
abstract class BaseAsset implements Asset, PrioritizedAsset
{
use ConfigureAutodiscoverVersionTrait;

Expand Down Expand Up @@ -57,6 +57,11 @@ abstract class BaseAsset implements Asset
*/
protected $handler = null;

/**
* Priority for asset registration order. Lower = earlier.
*/
protected int $priority = 10;

/**
* @param string $handle
* @param string $url
Expand Down Expand Up @@ -257,6 +262,30 @@ public function handler(): string
return $this->handler;
}

/**
* Get the priority for asset registration order.
*
* @return int
*/
public function priority(): int
{
return $this->priority;
}

/**
* Set the priority for asset registration order. Lower = earlier.
*
* @param int $priority
*
* @return static
*/
public function withPriority(int $priority): PrioritizedAsset
{
$this->priority = $priority;

return $this;
}

/**
* @return class-string<AssetHandler> className of the default handler
*/
Expand Down
28 changes: 28 additions & 0 deletions src/PrioritizedAsset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Inpsyde\Assets;

/**
* Interface for assets that support priority-based ordering.
* Lower priority values are processed first.
*/
interface PrioritizedAsset
{
/**
* Get the priority for asset registration order.
*
* @return int
*/
public function priority(): int;

/**
* Set the priority for asset registration order. Lower = earlier.
*
* @param int $priority
*
* @return static
*/
public function withPriority(int $priority): self;
}
19 changes: 19 additions & 0 deletions tests/phpunit/Unit/Asset/BaseAssetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,25 @@ protected function defaultHandler(): string
static::assertSame($expected, $asset->handler());
}

/**
* @test
*/
public function testPriority(): void
{
$asset = $this->createBaseAsset();

// Default priority is 10
static::assertSame(10, $asset->priority());

// Can set priority
$asset->withPriority(5);
static::assertSame(5, $asset->priority());

// Fluent interface
static::assertSame($asset, $asset->withPriority(15));
static::assertSame(15, $asset->priority());
}


private function createBaseAsset(string $handle = '', string $src = ''): BaseAsset
{
Expand Down
131 changes: 131 additions & 0 deletions tests/phpunit/Unit/AssetCollectionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace Inpsyde\Assets\Tests\Unit;

use Inpsyde\Assets\AssetCollection;
use Inpsyde\Assets\Script;
use Inpsyde\Assets\Style;

class AssetCollectionTest extends AbstractTestCase
{
/**
* @test
*/
public function testAddAndGet(): void
{
$collection = new AssetCollection();

$script = new Script('my-script', 'script.js');
$style = new Style('my-style', 'style.css');

$collection->add($script);
$collection->add($style);

static::assertSame($script, $collection->get('my-script', Script::class));
static::assertSame($style, $collection->get('my-style', Style::class));
}

/**
* @test
*/
public function testAllReturnsSortedByPriority(): void
{
$collection = new AssetCollection();

$scriptHigh = new Script('script-high', 'high.js');
$scriptHigh->withPriority(20);

$scriptLow = new Script('script-low', 'low.js');
$scriptLow->withPriority(5);

$scriptDefault = new Script('script-default', 'default.js');
// Default priority is 10

// Add in non-priority order
$collection->add($scriptHigh);
$collection->add($scriptDefault);
$collection->add($scriptLow);

$all = $collection->all();
$scripts = array_values($all[Script::class]);

// Should be sorted: low (5), default (10), high (20)
static::assertSame('script-low', $scripts[0]->handle());
static::assertSame('script-default', $scripts[1]->handle());
static::assertSame('script-high', $scripts[2]->handle());
}

/**
* @test
*/
public function testAllSortsEachTypeSeparately(): void
{
$collection = new AssetCollection();

$scriptA = new Script('script-a', 'a.js');
$scriptA->withPriority(20);

$scriptB = new Script('script-b', 'b.js');
$scriptB->withPriority(5);

$styleA = new Style('style-a', 'a.css');
$styleA->withPriority(15);

$styleB = new Style('style-b', 'b.css');
$styleB->withPriority(1);

$collection->add($scriptA);
$collection->add($styleA);
$collection->add($scriptB);
$collection->add($styleB);

$all = $collection->all();

$scripts = array_values($all[Script::class]);
$styles = array_values($all[Style::class]);

// Scripts sorted: b (5), a (20)
static::assertSame('script-b', $scripts[0]->handle());
static::assertSame('script-a', $scripts[1]->handle());

// Styles sorted: b (1), a (15)
static::assertSame('style-b', $styles[0]->handle());
static::assertSame('style-a', $styles[1]->handle());
}

/**
* @test
*/
public function testHas(): void
{
$collection = new AssetCollection();

$script = new Script('my-script', 'script.js');
$collection->add($script);

static::assertTrue($collection->has('my-script', Script::class));
static::assertFalse($collection->has('my-script', Style::class));
static::assertFalse($collection->has('other', Script::class));
}

/**
* @test
*/
public function testGetFirst(): void
{
$collection = new AssetCollection();

$script = new Script('my-handle', 'script.js');
$style = new Style('my-handle', 'style.css');

$collection->add($script);
$collection->add($style);

// getFirst returns first match regardless of type
$first = $collection->getFirst('my-handle');
static::assertNotNull($first);
static::assertSame('my-handle', $first->handle());
}
}