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
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

_Nothing yet._
### Fixed

- **`Assets` resolves on a fresh install.** It read its config from the bare `assets` key while
the provider registers it at `laranail.assets`, so every call threw *"Trying to access array
offset on null"* unless the config had been published. It now reads `laranail.assets`.
- **`addStyles()` adds styles.** It merged into the script list, so a stylesheet rendered as a
`<script>` tag.
- The config default is merged in `register()`, so it exists before other providers boot.

### Deprecated

- **A config published to `config/assets.php`** is still honoured, so an existing override keeps
working. Republish with `--tag=laranail::assets-config`, which now writes
`config/laranail/assets.php`, and delete the old file. Support for the old path can go in a
future minor release.

### Added

- A `Quick start` section in the README.

## v0.1.0

Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,27 @@ Requires PHP `^8.4.1 || ^8.5` and Laravel `^13.0`.
composer require laranail/assets
```

## Quick start

```blade
{{-- resources/views/layouts/app.blade.php --}}
<head>
{!! Assets::renderHeader() !!}
</head>
<body>
@yield('content')

{!! Assets::renderFooter() !!}
</body>

{{-- resources/views/checkout.blade.php --}}
@extends('layouts.app')

@php(Assets::addScriptsDirectly('js/checkout.js'))
```

The registered names and design are in [Architecture](docs/architecture.md); everything else is in the [documentation index](#documentation).

## <a name="documentation"></a>Documentation

Full documentation is at
Expand Down
10 changes: 8 additions & 2 deletions src/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ class Assets

public function __construct(Repository $config, HtmlBuilder $htmlBuilder)
{
$this->config = $config->get('assets');
// The provider registers the defaults at `laranail.assets`. A config published before
// 2026-10 landed at the bare `config/assets.php` and is still honoured, so an application
// that already overrode it keeps its override (deprecated: republish with
// `--tag=laranail::assets-config`, which now writes config/laranail/assets.php).
$this->config = $config->has('assets.scripts')
? $config->get('assets')
: $config->get('laranail.assets');

$this->scripts = $this->config['scripts'];

Expand All @@ -57,7 +63,7 @@ public function addScripts(array|string $assets): self
*/
public function addStyles(array|string $assets): self
{
$this->scripts = array_merge($this->scripts, Arr::wrap($assets));
$this->styles = array_merge($this->styles, Arr::wrap($assets));

return $this;
}
Expand Down
13 changes: 11 additions & 2 deletions src/Providers/AssetsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@

class AssetsServiceProvider extends ServiceProvider
{
public function boot(): void
public function register(): void
{
// In register(), not boot(): a merged default must exist before any other provider's boot()
// can resolve Assets, and before config:cache snapshots the repository.
$this->mergeConfigFrom(__DIR__ . '/../../config/config.php', 'laranail.assets');
}

public function boot(): void
{
$this->loadViewsFrom(__DIR__ . '/../../resources/views', 'laranail/assets');

if ($this->app->runningInConsole()) {
// config/laranail/assets.php is what Laravel loads as `laranail.assets`. The tag used
// to write config/assets.php -- a bare key nothing registered -- which Assets now reads
// only as a deprecated fallback.
$this->publishes([
__DIR__ . '/../../config/config.php' => config_path('assets.php'),
__DIR__ . '/../../config/config.php' => config_path('laranail/assets.php'),
], 'laranail::assets-config');

$this->publishes([
Expand Down
63 changes: 63 additions & 0 deletions tests/Feature/AssetsConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

use Simtabi\Laranail\Assets\Assets;
use Illuminate\Support\Facades\Config;

/**
* The provider registers the defaults at `laranail.assets`. Until 2026-10 the class read the bare
* `assets` key instead, which nothing populates on a fresh install, so resolving `Assets` threw on
* a null index and every documented call failed. Only an application that had published the config
* (to the bare `config/assets.php`) ever had a working package.
*/
it('resolves on a fresh install, from the defaults registered at laranail.assets', function (): void {
$assets = app(Assets::class);

expect($assets->getStyles())->not->toBeEmpty()
->and(Config::get('assets'))->toBeNull();
});

it('takes its script and style lists from laranail.assets', function (): void {
Config::set('laranail.assets.scripts', []);
Config::set('laranail.assets.styles', []);

$assets = app(Assets::class);

expect($assets->getScripts('header'))->toBe([])
->and($assets->getScripts('footer'))->toBe([])
->and($assets->getStyles())->toBe([]);
});

/**
* The working population before the fix: applications that published the old bare file. Their
* override keeps winning, so the repair changes nothing for anyone the package already worked for.
*/
it('still honours a config published to the old bare config/assets.php', function (): void {
Config::set('assets', array_replace(Config::get('laranail.assets'), [
'styles' => [],
'scripts' => [],
]));

$assets = app(Assets::class);

expect($assets->getScripts('header'))->toBe([])
->and($assets->getScripts('footer'))->toBe([])
->and($assets->getStyles())->toBe([]);
});

/** addStyles() used to merge into the script list, so a stylesheet was rendered as a <script>. */
it('adds styles to the styles, not the scripts', function (): void {
Config::set('laranail.assets.scripts', []);
Config::set('laranail.assets.styles', []);
Config::set('laranail.assets.resources.styles.invoice', [
'use_cdn' => false,
'src' => ['local' => '/css/invoice.css'],
]);

$assets = app(Assets::class)->addStyles('invoice');

expect($assets->getScripts('header'))->toBe([])
->and($assets->getScripts('footer'))->toBe([])
->and(json_encode($assets->getStyles(), JSON_UNESCAPED_SLASHES))->toContain('/css/invoice.css');
});
Loading