Skip to content

Commit c3e010f

Browse files
authored
Merge pull request #103 from hydephp/improve-assetservice
Improve AssetService and style/script handling
2 parents 866d97d + 149970f commit c3e010f

File tree

12 files changed

+363
-21
lines changed

12 files changed

+363
-21
lines changed

CHANGELOG.md

+23-7
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
### About
1616

17-
Keep an Unreleased section at the top to track upcoming changes.
17+
> Keep an Unreleased section at the top to track upcoming changes.
18+
>
19+
> This serves two purposes:
20+
>
21+
> 1. People can see what changes they might expect in upcoming releases
22+
> 2. At release time, you can move the Unreleased section changes into a new > release version section.
1823
19-
This serves two purposes:
2024

21-
1. People can see what changes they might expect in upcoming releases
22-
2. At release time, you can move the Unreleased section changes into a new release version section.
25+
This release refactors and improves the Asset Service, adding auto-configuration features and a new Asset facade.
26+
27+
#### Using the Asset facade in Blade views
28+
29+
Instead of the long syntax `Hyde::assetManager()` you can now use the `Asset` facade directly. See this example, which both do the exact same thing using the same underlying service:
30+
31+
```blade
32+
Hyde::assetManager()->hasMediaFile('app.css')
33+
Asset::hasMediaFile('app.css')
34+
```
35+
36+
If you don't know what any of this means, good news! You don't have to worry about it. Hyde's got your back.
2337

2438
### Added
25-
- for new features.
39+
- Added feature to dynamically load hyde.css and hyde.js if they exist locally
40+
- Added the Asset facade to be used instead of `Hyde::assetManager()`
41+
- Added the Asset facade as a class alias to `config/app.css`
2642

2743
### Changed
28-
- for changes in existing functionality.
44+
- Changed `scripts.blade.php` and `styles.blade.php` to use the Asset facade
2945

3046
### Deprecated
31-
- for soon-to-be removed features.
47+
- Deprecated AssetManager.php (`Hyde::assetManager()`). Use the Asset facade instead
3248

3349
### Removed
3450
- for now removed features.

config/app.php

+12
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,20 @@
8181
Hyde\Framework\HydeServiceProvider::class,
8282
],
8383

84+
/*
85+
|--------------------------------------------------------------------------
86+
| Class Aliases
87+
|--------------------------------------------------------------------------
88+
|
89+
| This array of class aliases will be registered when this application
90+
| is started. However, feel free to register as many as you wish as
91+
| the aliases are "lazy" loaded so they don't hinder performance.
92+
|
93+
*/
94+
8495
'aliases' => [
8596
'Hyde' => Hyde\Framework\Hyde::class,
97+
'Asset' => Hyde\Framework\Facades\Asset::class,
8698
],
8799

88100
];

docs/managing-assets.md

+37-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ But as always with Hyde, you can customize everything if you want to.
1414

1515
Hyde ships with a complete frontend where base styles and scripts are included through [HydeFront](https://github.com/hydephp/hydefront) which adds accessibility and mobile support as well as interactions for dark mode switching and navigation and sidebar interactions.
1616

17-
HydeFront is split into two files, `hyde.css` and `hyde.js`. These are loaded in the default Blade views using the [jsDelivr CDN](https://www.jsdelivr.com/package/npm/hydefront). This is the recommended way to load the base styles as the [Hyde Framework](https://github.com/hydephp/framework) automatically makes sure that the correct HydeFront version for the current version of Hyde is loaded. If you don't want to use HydeFribtm you can customize the `styles.blade.php` file.
17+
HydeFront is split into two files, `hyde.css` and `hyde.js`. These are loaded in the default Blade views using the [jsDelivr CDN](https://www.jsdelivr.com/package/npm/hydefront). This is the recommended way to load the base styles as the [Hyde Framework](https://github.com/hydephp/framework) automatically makes sure that the correct HydeFront version for the current version of Hyde is loaded. If you don't want to use HydeFront you can of course customize this. See the [Telling Hyde where to find assets](#telling-hyde-where-to-find-assets) section for more information.
1818

1919
The bulk of the frontend is built with [TailwindCSS](https://tailwindcss.com/). To get you started, when installing Hyde, all the Tailwind styles you need come precompiled and minified into `_media/app.css`.
2020

@@ -80,6 +80,41 @@ npx tailwindcss -i resources/assets/app.css -o _media/app.css
8080
php hyde build OR php hyde rebuild _media
8181
```
8282

83+
## Telling Hyde where to find assets
84+
85+
### Customizing the Blade templates
86+
87+
To make it really easy to customize asset loading, the styles and scripts are loaded in dedicated Blade components.
88+
89+
- Styles are loaded in `hyde::layouts.styles`
90+
- Scripts are loaded in `hyde::layouts.scripts`
91+
92+
To customize them, run the following command:
93+
94+
```bash
95+
php hyde publish:views layouts
96+
```
97+
98+
Then edit the files found in `resources/views/vendor/hyde/layouts` of your project.
99+
100+
### You might not even need to do anything!
101+
102+
For the absolute majority of the cases, you don't need to mess with these files. The idea behind using a CDN is that your styles will be automatically updated when needed.
103+
104+
Furthermore, Hyde does some automatic configuration which is roughly as follows:
105+
106+
- If there is no `_media/app.css` file, Hyde won't attempt to load it.
107+
- If there is no `_media/app.js` file, Hyde won't attempt to load it.
108+
109+
- If there is no `_media/hyde.css` file, the correct version will be loaded through the CDN.
110+
- If there is no `_media/hyde.js` file, the correct version will be loaded through the CDN.
111+
112+
- If there is a `_media/hyde.css` file, it will be loaded instead of the CDN.
113+
- If there is a `_media/hyde.js` file, it will be loaded instead of the CDN.
114+
{.list-compact}
115+
116+
Note that this automatic configuration behaviour was implemented in v0.41.x. Prior to that, only the first two bullets were implemented and you would need to change the layouts to load local HydeFront files manually.
117+
83118

84119
## Managing images
85120
As mentioned above, assets stored in the _media folder are automatically copied to the _site/media folder,
@@ -90,8 +125,7 @@ making it the recommended place to store images. You can then easily reference t
90125
The recommended way to reference images are with relative paths as this offers the most compatibility,
91126
allowing you to browse the site both locally on your filesystem and on the web when serving from a subdirectory.
92127

93-
> Note: The path is relative to the **compiled** file
94-
{.warning}
128+
>warning Note: The path is relative to the <b>compiled</b> file in the site output
95129
96130
The path to use depends on the location of the page. Note the subtle difference in the path prefix.
97131

packages/framework/resources/views/layouts/scripts.blade.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{{-- The core HydeFront scripts --}}
2-
@if(Hyde::scripts())
3-
<script defer src="{{ Hyde::scripts() }}"></script>
4-
@endif
2+
@unless(Asset::hasMediaFile('hyde.js'))
3+
<script defer src="{{ Asset::cdnLink('hyde.js') }}"></script>
4+
@else
5+
<script defer src="{{ Hyde::relativeLink('media/hyde.js', $currentPage) }}"></script>
6+
@endunless
57

68
{{-- The compiled Laravel Mix scripts --}}
7-
@if(Hyde::assetManager()->hasMediaFile('app.js'))
9+
@if(Asset::hasMediaFile('app.js'))
810
<script defer src="{{ Hyde::relativeLink('media/app.js', $currentPage) }}"></script>
911
@endif
1012

packages/framework/resources/views/layouts/styles.blade.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{{-- The core HydeFront stylesheet --}}
2-
@if(Hyde::styles())
3-
<link rel="stylesheet" href="{{ Hyde::styles() }}">
4-
@endif
2+
@unless(Asset::hasMediaFile('hyde.css'))
3+
<link rel="stylesheet" href="{{ Asset::cdnLink('hyde.css') }}">
4+
@else
5+
<link rel="stylesheet" href="{{ Hyde::relativeLink('media/hyde.css', $currentPage) }}">
6+
@endunless
57

68
{{-- The compiled Tailwind/App styles --}}
7-
@if(Hyde::assetManager()->hasMediaFile('app.css'))
9+
@if(Asset::hasMediaFile('app.css'))
810
<link rel="stylesheet" href="{{ Hyde::relativeLink('media/app.css', $currentPage) }}">
911
@endif
1012

packages/framework/src/Concerns/Internal/AssetManager.php

+7
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
/**
88
* Offloads asset related methods for the Hyde Facade.
99
*
10+
* @deprecated version 0.41.x - Use the Asset facade instead.
1011
* @see \Hyde\Framework\Hyde
1112
*/
1213
trait AssetManager
1314
{
1415
/**
1516
* Get the asset service instance.
1617
*
18+
* @deprecated version 0.41.x - Use the Asset facade instead.
19+
*
1720
* @return \Hyde\Framework\Contracts\AssetServiceContract
1821
*/
1922
public static function assetManager(): AssetServiceContract
@@ -23,6 +26,8 @@ public static function assetManager(): AssetServiceContract
2326

2427
/**
2528
* Return the Hyde stylesheet.
29+
*
30+
* @deprecated version 0.41.x - Use the Asset facade instead.
2631
*/
2732
public static function styles(): string
2833
{
@@ -31,6 +36,8 @@ public static function styles(): string
3136

3237
/**
3338
* Return the Hyde scripts.
39+
*
40+
* @deprecated version 0.41.x - Use the Asset facade instead.
3441
*/
3542
public static function scripts(): string
3643
{
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Hyde\Framework\Facades;
4+
5+
use Hyde\Framework\Contracts\AssetServiceContract;
6+
use Illuminate\Support\Facades\Facade;
7+
8+
/**
9+
* @see \Hyde\Framework\Services\AssetService
10+
*
11+
* @method static string version()
12+
* @method static string stylePath()
13+
* @method static string scriptPath()
14+
* @method static string constructCdnPath(string $file)
15+
* @method static string cdnLink(string $file)
16+
* @method static bool hasMediaFile(string $file)
17+
*/
18+
class Asset extends Facade
19+
{
20+
protected static function getFacadeAccessor(): string
21+
{
22+
return AssetServiceContract::class;
23+
}
24+
}

packages/framework/src/Services/AssetService.php

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
use Hyde\Framework\Contracts\AssetServiceContract;
66
use Hyde\Framework\Hyde;
77

8+
/**
9+
* @see \Hyde\Framework\Facades\Asset
10+
*/
811
class AssetService implements AssetServiceContract
912
{
1013
/**
@@ -34,6 +37,16 @@ public function constructCdnPath(string $file): string
3437
return 'https://cdn.jsdelivr.net/npm/hydefront@'.$this->version().'/dist/'.$file;
3538
}
3639

40+
/**
41+
* Alias for constructCdnPath.
42+
*
43+
* @since v0.41.x
44+
*/
45+
public function cdnLink(string $file): string
46+
{
47+
return $this->constructCdnPath($file);
48+
}
49+
3750
public function hasMediaFile(string $file): bool
3851
{
3952
return file_exists(Hyde::path('_media').'/'.$file);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use Hyde\Framework\Contracts\AssetServiceContract;
4+
use Hyde\Framework\Facades\Asset;
5+
use Hyde\Framework\Services\AssetService;
6+
use Hyde\Testing\TestCase;
7+
8+
/**
9+
* @covers \Hyde\Framework\Facades\Asset
10+
*/
11+
class AssetFacadeTest extends TestCase
12+
{
13+
public function test_asset_facade_returns_the_asset_service()
14+
{
15+
$this->assertInstanceOf(AssetServiceContract::class, Asset::getFacadeRoot());
16+
}
17+
18+
public function test_facade_returns_same_instance_as_bound_by_the_container()
19+
{
20+
$this->assertSame(Asset::getFacadeRoot(), app(AssetServiceContract::class));
21+
}
22+
23+
public function test_asset_facade_can_call_methods_on_the_asset_service()
24+
{
25+
$service = new AssetService();
26+
$this->assertEquals($service->version(), Asset::version());
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
namespace Hyde\Framework\Testing\Unit\Views;
4+
5+
use Hyde\Framework\Hyde;
6+
use Hyde\Framework\Services\AssetService;
7+
use Hyde\Testing\TestCase;
8+
use Illuminate\Support\Facades\Blade;
9+
10+
/**
11+
* @see resources/views/layouts/scripts.blade.php
12+
*/
13+
class ScriptsComponentViewTest extends TestCase
14+
{
15+
protected ?string $mockCurrentPage = null;
16+
17+
protected function renderTestView(): string
18+
{
19+
view()->share('currentPage', $this->mockCurrentPage ?? '');
20+
21+
return Blade::render(file_get_contents(
22+
Hyde::vendorPath('resources/views/layouts/scripts.blade.php')
23+
));
24+
}
25+
26+
public function test_component_can_be_rendered()
27+
{
28+
$this->assertStringContainsString('<script defer', $this->renderTestView());
29+
}
30+
31+
public function test_component_has_link_to_app_js_file_when_it_exists()
32+
{
33+
touch(Hyde::path('_media/app.js'));
34+
$this->assertStringContainsString('<script defer src="media/app.js"', $this->renderTestView());
35+
unlink(Hyde::path('_media/app.js'));
36+
}
37+
38+
public function test_component_does_not_render_link_to_app_js_when_it_does_not_exist()
39+
{
40+
$this->assertStringNotContainsString('<script defer src="media/app.js"', $this->renderTestView());
41+
}
42+
43+
public function test_component_uses_relative_path_to_app_js_file_for_nested_pages()
44+
{
45+
touch(Hyde::path('_media/app.js'));
46+
$this->mockCurrentPage = 'foo';
47+
$this->assertStringContainsString('<script defer src="media/app.js"', $this->renderTestView());
48+
$this->mockCurrentPage = 'foo/bar';
49+
$this->assertStringContainsString('<script defer src="../media/app.js"', $this->renderTestView());
50+
$this->mockCurrentPage = 'foo/bar/cat.html';
51+
$this->assertStringContainsString('<script defer src="../../media/app.js"', $this->renderTestView());
52+
$this->mockCurrentPage = null;
53+
unlink(Hyde::path('_media/app.js'));
54+
}
55+
56+
public function test_scripts_can_be_pushed_to_the_component_scripts_stack()
57+
{
58+
view()->share('currentPage', '');
59+
60+
$this->assertStringContainsString('foo bar',
61+
Blade::render('
62+
@push("scripts")
63+
foo bar
64+
@endpush
65+
66+
@include("hyde::layouts.scripts")'
67+
)
68+
);
69+
}
70+
71+
public function test_component_renders_link_to_hyde_js_when_it_exists()
72+
{
73+
touch(Hyde::path('_media/hyde.js'));
74+
$this->assertStringContainsString('<script defer src="media/hyde.js"', $this->renderTestView());
75+
unlink(Hyde::path('_media/hyde.js'));
76+
}
77+
78+
public function test_component_does_not_render_link_to_hyde_js_when_it_does_not_exist()
79+
{
80+
$this->assertStringNotContainsString('<script defer src="media/hyde.js"', $this->renderTestView());
81+
}
82+
83+
public function test_component_renders_cdn_link_when_no_local_file_exists()
84+
{
85+
$this->assertStringContainsString('https://cdn.jsdelivr.net/npm/hydefront', $this->renderTestView());
86+
}
87+
88+
public function test_component_does_not_render_cdn_link_when_a_local_file_exists()
89+
{
90+
touch(Hyde::path('_media/hyde.js'));
91+
$this->assertStringNotContainsString('https://cdn.jsdelivr.net/npm/hydefront', $this->renderTestView());
92+
unlink(Hyde::path('_media/hyde.js'));
93+
}
94+
95+
public function test_cdn_link_uses_the_correct_version_defined_in_the_asset_manager()
96+
{
97+
$expectedVersion = (new AssetService)->version();
98+
$this->assertStringContainsString(
99+
'https://cdn.jsdelivr.net/npm/hydefront@'.$expectedVersion.'/dist/hyde.js',
100+
$this->renderTestView()
101+
);
102+
}
103+
}

0 commit comments

Comments
 (0)