Closed thekid closed 3 years ago
How this might work:
The place where this hash value is stored could also contain other global values, e.g. a navigation lookup table
Most other solutions use a so-called asset manifest, see e.g. https://webpack.js.org/concepts/manifest/ and https://webpack.js.org/guides/caching/
{
"main.css": "/static/main.225eedd.css",
"main.js": "/static/main.5f31c89.js",
"icons.svg": "/static/icons.3a909e9.svg",
"brand-icons.svg": "/static/brand-icons.5f363d3.svg"
}
In EmberJS, this is called an "asset map", see See https://codeburst.io/ember-js-lazy-assets-fingerprinting-loading-static-dynamic-assets-on-demand-f09cd7568155
{
"assets": {
"assets/images/blue-theme-logo.png": "assets/images/blue-theme-logo-40cfba464935ff80190c3507e84d94b5.png",
"assets/bootstrap.css": "assets/bootstrap-c87a4c2c79156714058688911e8c1493.css",
"assets/bootstrap.js": "assets/bootstrap-a6614093a8f614bdac2df7cf89676516.js",
}
}
Here's a version for ruby on rails: https://github.com/eliotsykes/asset_fingerprint. It actually stores the file as [name].css
and only has the fingerprints on the "front" side, rewriting via
rewrite "^(.+)-fp-[0-9a-z]{32}(.*)" $1$2 last;
For newer versions, this is now in https://guides.rubyonrails.org/asset_pipeline.html
See also:
If we pass the fingerprint as context variable then it would be easily accessible from within the templates:
new Frontend(
new HandlersIn('com.example.skills.web'),
new Handlebars($this->environment->path('src/main/handlebars')),
['assets' => '/assets/'.$fingerprint]
);
The base parameter which is currently passed to the context as "base" would be remodeled to be a map holding a global context; each key/value pair being passed in the context. This is implementable in a BC-break free way.
The place where this hash value is stored could also contain other global values
We could generate a config file for this, e.g. frontend.ini, which would hold these values.
fingerprint=40cfba464935ff80190c3507e84d94b5
Web applications can access these via Environment::properties()
, giving us:
new Frontend(
new HandlersIn('com.example.skills.web'),
new Handlebars($this->environment->path('src/main/handlebars')),
$this->environment->properties('frontend')->readSection(null)
);
The bundler would then use -f src/main/etc/frontend.ini
to find the location, and rewrite this file replacing the fingerprint key, the web application would be started with -c src/main/etc
to point it to the same location.
The downside of inventing our own approach is that if we switch from using our own bundler to webpack, we would also need to switch the application's implementation. Here's an alternative idea:
new Frontend(
new HandlersIn('com.example.skills.web'),
new Handlebars($this->environment->path('src/main/handlebars')),
['manifest' => new AssetManifest($this->environment->path('manifest.json'))]
);
Our bundler would use -m manifest.json
to generate this file, mapping asset names to files including the fingerprint:
{
"vendor.js": "vendor.1234567890.js",
"vendor.css": "vendor.0987654321.css"
}
Note: Webpack puts this file in the same directory as the bundled .css and .js files, and includes publicPath
inside the values.
The AssetsManifest
class is mostly straight-forward:
<?php namespace web\frontend;
use text\json\{Json, Input, FileInput};
class AssetsManifest {
public $assets;
/** @param text.json.Input|io.Path|io.File|string */
public function __construct($arg) {
$this->assets= Json::read($arg instanceof Input ? $arg : new FileInput($arg));
}
}
Now caching can be done by something along the lines of:
$assets= new AssetsFrom($this->environment->path('src/main/webapp'))->with(function($file) {
if (preg_match(AssetsManifest::WITH_FINGERPRINT, $file->filename)) {
yield 'Cache-Control' => 'max-age=31536000, immutable';
} else {
yield 'Cache-Control' => 'max-age=2419200, must-revalidate';
}
});
However, this is quite some boilerplate we might want to reduce by including it in a default implementation somewhere.
However, this is quite some boilerplate we might want to reduce
First iteration:
$manifest= new AssetsManifest($this->environment->path('src/main/webapp/static/manifest.json'));
$assets= new AssetsFrom($this->environment->path('src/main/webapp'))->with(fn($file) => [
'Cache-Control' => $manifest->immutable($file) ?? 'max-age=2419200, must-revalidate'
]);
This could be incorporated into the cache control API and become something like:
$manifest= new AssetsManifest($this->environment->path('src/main/webapp/static/manifest.json'));
$assets= new AssetsFrom($this->environment->path('src/main/webapp'))->with(
CacheControl::immutable($manifest)->default('max-age=2419200, must-revalidate')
);
Static resources such as JavaScript, CSS, images, etc.:
See https://webhint.io/docs/user-guide/hints/hint-http-cache/