xp-forge / frontend

Web frontends
1 stars 1 forks source link

Implement asset fingerprinting #17

Closed thekid closed 3 years ago

thekid commented 3 years ago

Implements #15 using assets manifests to keep this bundling ecosystem compatible with Webpack.

Usage

To include fingerprints in the asset filenames, you must invoke the bundle command with the -m option. This will include a fingerprint in the filenames:

image

...and generate a manifest, mapping all bundles' and all their dependencies' file names to the fingerprinted versions:

$ cat src/main/webapp/static/manifest.json | json_pp
{
   "vendor.js" : "vendor.7b1f7cb.js",
   "vendor.css" : "vendor.f6cad2a.css",
   "editor.css" : "editor.28c52e0.css",
   "editor.js" : "editor.870c123.js",
   "icons.svg" : "icons.dd83d26.svg"
}

Like GIT, we use the first 7 characters of an SHA1 checksum for fingerprinting.

This manifest needs to be passed to the frontend:

diff --git a/src/main/php/com/example/skills/App.php b/src/main/php/com/example/skills/App.php
index 6fc6ac5..d62593d 100755
--- a/src/main/php/com/example/skills/App.php
+++ b/src/main/php/com/example/skills/App.php
@@ -3,7 +3,7 @@
 use inject\{Injector, Bindings};
 use security\credentials\{Credentials, FromEnvironment, FromFile};
 use web\frontend\helpers\Dates;
-use web\frontend\{Frontend, HandlersIn, AssetsFrom, Handlebars};
+use web\frontend\{Frontend, HandlersIn, AssetsFrom, AssetsManifest, Handlebars};
 use web\rest\{RestApi, ResourcesIn};
 use web\session\{Sessions, InFileSystem, Cookies};
 use web\{Application, Filters};
@@ -29,8 +29,9 @@ class App extends Application {
     $inject->bind(Sessions::class, $sessions);

     $auth= $inject->get(Office365Integration::class)->using($sessions);
-    $assets= new AssetsFrom($this->environment->path('src/main/webapp'))->with([
-      'Cache-Control' => 'max-age=2419200, must-revalidate'
+    $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'
     ]);
     return [
       '/favicon.ico' => $assets,
@@ -47,7 +48,7 @@ class App extends Application {
           new Translations($this->environment->path('src/main/handlebars/texts.csv')),
           new RenderMarkdown(),
         ),
-        '/'
+        ['manifest' => $manifest]
       ))
     ];
   }

Now, you can access the access the real filenames of your assets in your templates, for example:

-    <script src="/static/vendor.js"></script>
+    <script src="/static/{{lookup manifest.assets 'vendor.js'}}"></script>

This will yield <script src="/static/vendor.7b1f7cb.js"></script> in the output document.

Cleaning

When using a manifest, we keep track of all files. To ensure our output directory only contains those that are really referenced, the XP bundler will make use of this and delete any previous versions when it runs:

image

thekid commented 3 years ago

Released in https://github.com/xp-forge/frontend/releases/tag/v2.3.0