Skip to content

Commit

Permalink
More flexible asset naming
Browse files Browse the repository at this point in the history
with a custom busting separator
(allowing for multiple dots in file names)
  • Loading branch information
dgvirtual committed Jan 7, 2024
1 parent e68bc85 commit 76a9c16
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 20 deletions.
11 changes: 10 additions & 1 deletion docs/building_sites/assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The `Config\Assets` class has a handful of settings to customize the experience.
### $bustingType

When a link is generated it includes a string within the filename for cache-busting reasons. This would look something
like: `https://localhost:8080/admin/css/admin.213264216523.css`. The config setting, `$bustingType` defines how this
like: `https://localhost:8080/admin/css/admin~~213264216523.css`. The config setting, `$bustingType` defines how this
string is derived. It has two possible values, either `file` or `version`.

The `file` setting will examine the requested file and use the file modified date/time as the basis, and convert it
Expand All @@ -55,6 +55,15 @@ to deploy to staging or production environments. See the next setting for detail
the current timestamp is used in testing/development environments ensuring that no caching will happen. In other
environments it inserts the version number that was specified.

### $separator

The `$separator` setting allows the app to detect the part of the asset file name that was
added for cache-busting purposes. It can be a single web-safe non-reserved character or
a combination of such characters (characters that are allowed in a URI, but do not have
a reserved purpose) that DOES NOT OCCUR in your asset file names (like `~`, `-`
or `_` or any combination of ASCII letters and numbers). Separator will be inserted
before the file version/timestamp, in between the file name and file extension.

### $versions

The `$versions` setting allows you to define the version number for `css` and `js` separately. This value is used
Expand Down
14 changes: 14 additions & 0 deletions src/Assets/Config/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ class Assets extends BaseConfig
*/
public $bustingType = 'file';

/**
* --------------------------------------------------------------------------
* Separator for the cache busting part of the file
* --------------------------------------------------------------------------
*
* It can be a single web-safe non-reserved character or a combination of
* such characters (characters that are allowed in a URI, but do not have a
* reserved purpose) that DO NOT OCCUR in your asset file names
* (like `~`, `-` or `_` or any combination of ASCII letters and numbers).
* Separator will be inserted before the file version/timestamp, in between
* the file name and file extension.
*/
public $separator = '~~';

/**
* --------------------------------------------------------------------------
* Cache Busting Versions
Expand Down
26 changes: 16 additions & 10 deletions src/Assets/Controllers/AssetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,29 @@ public function serve(...$segments)
*/
$filename = array_pop($segments);
$origFilename = $filename;
$filename = explode('.', $filename);

$fileparts = explode('.', $filename);
$count = count($fileparts);
// Must be at least a name and extension
if (count($filename) < 2) {
if ($count < 2) {
$this->response->setStatusCode(404);

return;
} else {
$ext = $fileparts[$count-1];
}

// If we have a fingerprint...
$filename = count($filename) === 3
? $filename[0] . '.' . $filename[2]
: $origFilename;

// To keep backward compatibility, we will assume there might not be
// a separator defined in user's config
$separator = config('Assets')->separator ?? '~~';
$parts = explode($separator, $filename);
if (count($parts) === 2) {
$filename = $parts[0] . '.' . $ext;
} else {
$filename = $origFilename;
}

$folder = config('Assets')->folders[array_shift($segments)];

$path = $folder . '/' . implode('/', $segments) . '/' . $filename;

if (! is_file($path)) {
$this->response->setStatusCode(404);

Expand Down
17 changes: 10 additions & 7 deletions src/Assets/Helpers/assets_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,28 @@ function asset(string $location, string $type): string
$location = trim($location, ' /');

// Add a cache-busting fingerprint to the filename
$segments = explode('/', $location);
$filename = array_pop($segments);
$ext = substr($filename, strrpos($filename, '.'));
$segments = explode('/', $location);
$filename = array_pop($segments);
$ext = substr($filename, strrpos($filename, '.'));
$namelength = strlen($filename) - strlen($ext);
$name = substr($filename, 0, $namelength);

if (empty($filename) || empty($ext) || $filename === $ext || empty($segments)) {
throw new \RuntimeException('You must provide a valid filename and extension to the asset() helper.');
}

// VERSION cache-busting
$fingerprint = '';
$separator = $config->separator ?? '~~';
if ($config->bustingType === 'version') {
switch (ENVIRONMENT) {
case 'testing':
case 'development':
$fingerprint = time();
$fingerprint = $separator . time();
break;

default:
$fingerprint = $config->versions[$type];
$fingerprint = $separator . $config->versions[$type];
}
}
// FILE cache-busting
Expand All @@ -70,14 +73,14 @@ function asset(string $location, string $type): string
$tempSegments
) . '/' . $filename;

$fingerprint = filemtime($path);
$fingerprint = $separator . filemtime($path);

if ($fingerprint === false) {

Check failure on line 78 in src/Assets/Helpers/assets_helper.php

View workflow job for this annotation

GitHub Actions / PHP 8.1 Static Analysis

Strict comparison using === between string and false will always evaluate to false.

Check failure on line 78 in src/Assets/Helpers/assets_helper.php

View workflow job for this annotation

GitHub Actions / PHP 7.4 Static Analysis

Strict comparison using === between string and false will always evaluate to false.

Check failure on line 78 in src/Assets/Helpers/assets_helper.php

View workflow job for this annotation

GitHub Actions / PHP 8.0 Static Analysis

Strict comparison using === between string and false will always evaluate to false.

Check failure on line 78 in src/Assets/Helpers/assets_helper.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Static Analysis

Strict comparison using === between string and false will always evaluate to false.
throw new \RuntimeException('Unable to get modification time of asset file: ' . $filename);
}
}

$filename = str_replace($ext, '.' . $fingerprint . $ext, $filename);
$filename = $name . $fingerprint . $ext;

// Stitch the location back together
$segments[] = $filename;
Expand Down
6 changes: 4 additions & 2 deletions tests/Assets/AssetHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,29 @@ public function testAssetVersion()
$config = config('Assets');

$config->bustingType = 'version';
$config->separator = '~~';
Factories::injectMock('config', 'Assets', $config);

$link = asset_link('admin/css/admin.css', 'css');

// In testing environment, would be the current timestamp
// so just test the pattern to ensure that works.
preg_match('|assets/admin/css/admin.([\d]+).css|i', $link, $matches);
preg_match('|assets/admin/css/admin~~([\d]+).css|i', $link, $matches);
$this->assertIsNumeric($matches[1]);
}

public function testAssetFile()
{
$config = config('Assets');
$config->bustingType = 'file';
$config->separator = '~~';
Factories::injectMock('config', 'Assets', $config);

$link = asset_link('admin/css/admin.css', 'css');

// In testing environment, would be the current timestamp
// so just test the pattern to ensure that works.
preg_match('|assets/admin/css/admin.([\d]+).css|i', $link, $matches);
preg_match('|assets/admin/css/admin~~([\d]+).css|i', $link, $matches);
$this->assertSame(filemtime(BFPATH . '../themes/Admin/css/admin.css'), (int) $matches[1]);
}
}

0 comments on commit 76a9c16

Please sign in to comment.