Skip to content

Commit 1efadaf

Browse files
authored
Merge pull request #143 from Laravel-Backpack/next
2 parents f0efeec + 539bbdc commit 1efadaf

38 files changed

+1206
-343
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ jobs:
77
stan-and-tests:
88
strategy:
99
matrix:
10-
php: [8.1, 8.2]
11-
laravel: ['^12.0', ^10.15]
10+
php: [8.1, 8.2, 8.3, 8.4]
11+
laravel: ['^12.0', ^11.0, ^10.15]
1212
exclude:
1313
- laravel: '^12.0'
1414
php: 8.1
15+
- laravel: '^11.0'
16+
php: 8.1
1517

1618
name: PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }}
1719

readme.md

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
<script src="{{ basset('https://cdn.com/path/to/file.js') }}">
1717
```
1818

19-
That's all you need to do. **Basset will download the file to `storage/app/public/bassets` from wherever it is, then output the now-public path to your asset.**
19+
That's all you need to do. **Basset will download the file to the predefined disk, then output that disk path to your asset.**
2020

2121
Using Basset, you easily internalize and use:
2222
- files from external URLs (like CDNs)
2323
- files from internal, but non-public URLs (like the vendor directory)
2424
- entire archives from external URLs (like GitHub)
2525
- entire directories from local, non-public paths (like other local projects)
2626

27-
No more publishing package files. No more using NPM just to download some files. It's a simple yet effective solution in the age of `HTTP/2` and `HTTP/3`.
27+
No more publishing package files. No more NPM bloat, just to download some files. It's a simple yet effective solution in the age of `HTTP/2` and `HTTP/3`.
2828

2929
## Installation
3030

@@ -33,40 +33,54 @@ composer require backpack/basset
3333
php artisan basset:install
3434
```
3535

36-
**Optional** publish the config file.
36+
To check that Basset is running correctly, please run:
37+
38+
```bash
39+
# recommended - this will tell you if anything is wrong & what to do:
40+
php artisan basset:check
41+
````
42+
43+
Optionally, you can also publish the config file to make changes in how Basset works:
44+
3745
```bash
46+
# optional
3847
php artisan vendor:publish --provider="Backpack\Basset\BassetServiceProvider"
3948
```
4049

41-
> **Note**
42-
> Basset is disabled by default on local environment. If you want to change it, please set `BASSET_DEV_MODE=false` in your env file.
43-
4450
#### Storage Symlink
45-
Basset uses the `public` disk to store cached assets in a directory that is publicly-accessible. So it needs you to run `php artisan storage:link` to create the symlink. The installation command will create ask to run that, and to add that command to your `composer.json`. That will most likely make it work on your development/staging/production servers. If that's not the case, make sure you create the links manually wherever you need them, with the command `php artisan storage:link`.
51+
The default disk remains `basset`, which lives under `storage/app/public`. Run `php artisan storage:link` so the cached assets are reachable from `public/`. The installer keeps the command in your composer scripts.
52+
53+
If you prefer to keep cached files under `public/` instead of `storage/`, switch to the `public_basset` disk (`BASSET_DISK=public_basset`) and commit or ignore `public/basset` according to your workflow.
54+
55+
### Disk Options
56+
57+
| Disk | Where files live | Pros | Cons |
58+
| --- | --- | --- | --- |
59+
| `basset` (default) | `storage/app/public/basset` | Clean git tree; downloads happen on deploy; matches previous defaults | Needs `php artisan storage:link`; |
60+
| `public_basset` | `public/basset` | Assets are ready to serve after deploy; can be versioned | Git noise unless ignored; |
4661

47-
#### Disk
48-
By default Basset uses the `public` disk. If you're having trouble with the assets not showing up on page, you might have an old Laravel configuration for it. Please make sure your disk is properly setup on `config/filsystems.php` - it should look like [the default one](https://github.com/laravel/laravel/blob/10.x/config/filesystems.php#L39-L45).
62+
If you move to `public_basset`, re-run `php artisan basset:install --git-ignore` (or edit the file yourself) when you want the installer to append `public/basset` to `.gitignore`.
4963

5064
## Usage
5165

5266
### The `basset()` Helper
5367

54-
You can just use the `basset()` helper instead of Laravel's `asset()` helper, and point to CDNs and non-public files too. Use [Laravel's path helpers](https://laravel.com/docs/10.x/helpers#paths-method-list) to construct the absolute path to your file, then Basset will take care of the rest.
68+
You can just use the `basset()` helper instead of Laravel's `asset()` helper, and point to CDNs and non-public files too. Use [Laravel's path helpers](https://laravel.com/docs/12.x/helpers#paths-method-list) to construct the absolute path to your file, then Basset will take care of the rest.
5569
5670
For local from CDNs:
5771
```blade
5872
{{-- instead of --}}
5973
<link href="{{ asset('path/to/public/file.css') }}">
6074

6175
{{-- you can do --}}
62-
<link href="{{ basset('path/to/public/file.css' }}">
76+
<link href="{{ basset('path/to/public/file.css') }}">
6377
<link href="{{ basset('https://cdn.com/path/to/file.css') }}">
6478
<link href="{{ basset(base_path('vendor/org/package/assets/file.css')) }}">
6579
<link href="{{ basset(storage_path('file.css')) }}">
6680
```
6781

6882
Basset will:
69-
- copy that file from the vendor directory to your `storage` directory (aka. internalize the file)
83+
- copy that file from the vendor directory to the basset disk (if needed)
7084
- use the internalized file on all requests
7185

7286
### The `@basset()` Directive
@@ -101,7 +115,7 @@ These are the know file types;
101115

102116

103117
Basset will:
104-
- copy that file from the vendor directory to your `storage` directory (aka. internalize the file)
118+
- copy that file from the vendor directory to your basset directory (aka. internalize the file)
105119
- use the internalized file on all requests
106120
- make sure that file is only loaded once per pageload
107121

@@ -118,7 +132,7 @@ Easily move code blocks to files, so they're cached
118132
```
119133

120134
Basset will:
121-
- create a file with that JS code in your `storage/app/public/basset` directory (aka. internalize the code)
135+
- create a file with that JS code in your basset directory (aka. internalize the code)
122136
- on all requests, use the local file (using `<script src="">`) instead of having the JS inline
123137
- make sure that file is only loaded once per pageload
124138

@@ -132,7 +146,7 @@ Easily use archived assets (.zip & .tar.gz):
132146
```
133147

134148
Basset will:
135-
- download the archive to your `storage/app/public/basset` directory (aka. internalize the code)
149+
- download the archive to your basset directory (aka. internalize the code)
136150
- unarchive it
137151
- on all requests, use the local file (using `<script src="">`)
138152
- make sure that file is only loaded once per pageload
@@ -149,32 +163,54 @@ Easily internalize and use entire non-public directories:
149163
```
150164

151165
Basset will:
152-
- copy the directory to your `storage/app/public/basset` directory (aka. internalize the code)
166+
- copy the directory to your basset directory (aka. internalize the code)
153167
- on all requests, use the internalized file (using `<script src="">`)
154168
- make sure that file is only loaded once per pageload
155169

170+
### Named Assets & Overrides
171+
172+
Named assets let packages expose friendly handles that always point to the right file.
173+
174+
```php
175+
// App\Providers\AppServiceProvider.php
176+
public function boot(): void
177+
{
178+
\Backpack\Basset\Facades\Basset::map('crud.select2', 'https://cdn.example.com/select2.js', [
179+
'defer' => true,
180+
]);
181+
}
182+
```
183+
184+
In Blade you can now call `@basset('crud.select2')` or `basset('crud.select2')` and Basset will internalize the mapped source.
185+
186+
Need a different CDN or integrity hash? Create a class that implements `Backpack\Basset\OverridesAssets`, point `config('backpack.basset.asset_overrides')` to it, and call `Basset::map()` again inside `assets()`.
187+
188+
Use `php artisan basset:list-named` anytime to check which keys are currently registered.
189+
156190
### The `basset` Commands
157191

158192
Copying an asset from CDNs to your server could take a bit of time, depending on the asset size. For large pages, that could even take entire seconds. You can easily prevent that from happening, by internalizing all assets in one go. You can use `php artisan basset:cache` to go through all your blade files, and internalize everything that's possible. If you ever need it, `basset:clear` will delete all the files.
159193

160194
```bash
161-
php artisan basset:cache # internalizes all @bassets
195+
php artisan basset:cache # internalizes all @bassets and named assets
162196
php artisan basset:clear # clears the basset directory
197+
php artisan basset:fresh # runs clear + cache back-to-back
198+
php artisan basset:list-named # shows all registered named assets
163199
```
164200

165201
In order to speed up the first page load on production, we recommend you to add `php artisan basset:cache` command to your deploy script.
166202

167-
### Basset Cached Event
168-
169-
If you require customized behavior after each asset is cached, you can set up a listener for the `BassetCachedEvent` in your `EventServiceProvider`. This event will be triggered each time an asset is cached.
170-
171203
## Configuration
172204

173205
Take a look at [the config file](https://github.com/Laravel-Backpack/basset/blob/main/src/config/backpack/basset.php) for all configuration options. Notice some of those configs also have ENV variables, so you can:
174-
- enable/disable dev mode using `BASSET_DEV_MODE=false` - this will force Basset to internalize assets even on localhost
206+
- enable/disable dev mode using `BASSET_DEV_MODE=false` - when enabled Basset will check for changes in your url/files and update the cached assets
175207
- change the disk where assets get internalized using `BASSET_DISK=yourdiskname`
176208
- disable the cache map using `BASSET_CACHE_MAP=false` (needed on serverless like Laravel Vapor)
177209

210+
### Dev Mode
211+
212+
When `BASSET_DEV_MODE` is `true`, Basset keeps hashing local files and code blocks so edits are picked up immediately. Remote URLs are still downloaded the first time they are hit; this avoids surprises when you lose connectivity.
213+
178214
## Deployment
179215

180216
There are a lot of deployment options for Laravel apps, but we'll try to cover the gotchas of the most popular ones here:
@@ -271,6 +307,42 @@ If you use the default `public` disk, Basset requires that the symlink between t
271307

272308
Note for Homestead users: the symlink can't be created inside the virtual machine. You should stop your instance with: `vagrant down`, create the symlink in your local application folder and then `vagrant up` to bring the system back up.
273309

310+
#### Where are cached assets stored?
311+
312+
Basset provides a few options out-of-the-box:
313+
- **`basset` disk** - inside the storage directory (eg. `storage/app/basset`) [DEFAULT]
314+
- PROs: the Git history is clean - because your cached assets will NOT be tracked by Git;
315+
- CONs: you have to run `php artisan basset:cache` in your deploy script, which adds seconds to your deploy time; plus, it opens up a corner case on deployment - because assets are being re-cached upon deployment, if a CDN is down during deployment, the system will not be able to internalize it; if will however internalize it when the CDN is back on, and the page that loads the file gets accessed; we consider the tradeoffs minor and unlikely, which is why this is the DEFAULT;
316+
- How to enable: do nothing, or do `BASSET_DISK=basset` in your .env file;
317+
- **`public_basset` disk** - inside the public directory (eg. `public/basset`)
318+
- PROs: you are certain the same assets you have on localhost will be in production, because the assets are commited to Git;
319+
- CONs: your Git history will be dirtier, because it will contain changes to libraries of CSS/JS files;
320+
- How to enable: do `BASSET_DISK=public_basset` in your .env file or `config/backpack/basset.php` config file;
321+
- **custom** - you can completely customize what disk is used to store the assets - just change it in the config file; most common customizations:
322+
- store assets on a S3 bucket (using a custom disk);
323+
- store assets in `public_basset` disk, but add `public/basset` to .gitignore;
324+
325+
#### Can I track the assets in git, just like my source code? (aka NOT gitignore CSS and JS assets)
326+
327+
Yes, you can track the assets in your application repository and avoid downloading them on each deployment (aka. have them in git). The easiest way to do that is to set the `BASSET_DISK=public_basset` in your .ENV, or in the basset config file. This will store the assets in the `public/basset` directory by default and they will now be committed to git alongside the rest of your application code. But note that this has both PROs and CONs:
328+
- PROs: This is advantageous as you know what assets are in your application right when you deploy, avoiding issues like a CDN being down at the deployment time and breaking your application production.
329+
- CONs: As a downside, you must be 100% sure all assets are internalized on localhost, and commited to git. Otherwise, when a page is accessed in production, Basset will internalize that file in production alone (it always prioritizes having production in a working state), which means you'll have uncommitted changes in your production code. You will then have to fix merge conflicts in production, or do a git reset before each deployment.
330+
331+
To summarize - if you're 100% sure that `php artisan basset:cache` is pulling all assets your application needs, you can safely commit your assets to git. If not, you are exposing yourself to conflicts in production (which can be managed as well).
332+
333+
### Events
334+
335+
If you require customized behavior after each asset is cached, you can set up a listener for the `BassetCachedEvent` in your `EventServiceProvider`. This event will be triggered each time an asset is cached.
336+
337+
## Upgrading from v1 to v2
338+
339+
340+
To upgrade Basset:
341+
- **Step 1.** Run `php artisan basset:install --git-ignore` to refresh composer hooks and optionally ignore the public cache folder.
342+
- **Step 2.** Decide on `BASSET_DISK` (`basset` + storage symlink, or `public_basset` + git rule) before deploying.
343+
- **Step 3.** Replace any custom `@loadOnce` usage with `@basset` or `@bassetBlock`; the old directive now proxies the new ones.
344+
- **Step 4.** Ensure deploy scripts warm the cache (`basset:cache` or `basset:fresh`) so your public folder is ready when the app boots.
345+
274346
## Change log
275347

276348
Please see the [releases tab](https://github.com/Laravel-Backpack/basset/releases) for more information on what has changed recently.
@@ -292,6 +364,7 @@ If you discover any security related issues, please email hello@backpackforlarav
292364
## Credits
293365

294366
- [Antonio Almeida](https://github.com/promatik)
367+
- [Pedro Martins](https://github.com/pxpm)
295368
- [Cristian Tabacitu][link-author]
296369
- [All Contributors][link-contributors]
297370

src/AssetHashManager.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Backpack\Basset;
4+
5+
use Backpack\Basset\Contracts\AssetHashManagerInterface;
6+
7+
final class AssetHashManager implements AssetHashManagerInterface
8+
{
9+
public function generateHash(string $content): string
10+
{
11+
return hash('xxh32', $content);
12+
}
13+
14+
public function appendHashToPath(string $path, string $hash): string
15+
{
16+
return preg_replace('/\.(css|js)$/i', "-{$hash}.$1", $path);
17+
}
18+
19+
public function validateHash(string $content, string $hash): bool
20+
{
21+
return $this->generateHash($content) === $hash;
22+
}
23+
}

src/AssetPathManager.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Backpack\Basset;
4+
5+
use Backpack\Basset\Contracts\AssetPathManagerInterface;
6+
use Illuminate\Support\Facades\File;
7+
use Illuminate\Support\Str;
8+
9+
final class AssetPathManager implements AssetPathManagerInterface
10+
{
11+
private string $basePath;
12+
13+
public function __construct()
14+
{
15+
$this->basePath = (string) Str::of(config('backpack.basset.path'))->finish('/');
16+
}
17+
18+
public function getBasePath(): string
19+
{
20+
return $this->basePath;
21+
}
22+
23+
public function getPathOnDisk(string $asset): string
24+
{
25+
return Str::of($this->basePath)
26+
->append($this->getCleanPath($asset))
27+
->replace(['//'], '/');
28+
}
29+
30+
public function getCleanPath(string $asset): string
31+
{
32+
return Str::of($asset)
33+
->replace([base_path().'/', base_path(), base_path().'\\', 'http://', 'https://', '://', '<', '>', ':', '"', '|', "\0", '*', '`', ';', "'", '+'], '')
34+
->before('?')
35+
->replace(['/\\', '\\'], '/');
36+
}
37+
38+
public function isLocal(string $path): bool
39+
{
40+
return File::exists($path);
41+
}
42+
43+
public function setBasePath(string $basePath): void
44+
{
45+
$this->basePath = $basePath;
46+
}
47+
}

0 commit comments

Comments
 (0)