Assets

Kaly knows where the browser-ready assets of the application live, produces their URL, and can publish them. It knows nothing of npm, Bun, importmaps, bundling or minification.

Asset directories contain browser-ready files. Kaly preserves their relative paths and contents. It never parses, compiles, bundles, minifies, rewrites, or resolves frontend dependencies.

Layout

assets/                  static files of the application (namespace `app`)
modules/Admin/assets/    static files of the Admin module (namespace `admin`)
modules/Site/assets/     static files of the Site module (namespace `site`)

A namespace is the decamelized module name. In templates and code, assets are addressed as app.css (the app namespace) or @admin/admin.js (a module namespace):

$assets->url('app.css');
$assets->url('@admin/admin.js');
<link rel="stylesheet" href="{{ asset('app.css') }}">
<script type="module" src="{{ asset('@admin/admin.js') }}"></script>

Because the relative tree is preserved from source to URL, relative references keep working with zero rewriting:

import "./components/dialog.js";
background-image: url("./images/logo.svg");

Bare specifiers such as import "@lekoala/calendar" are not Kaly’s problem: if Bun or another tool resolves them, its output must land browser-ready in assets/ (or modules/*/assets/).

Development

The application opts in to serving sources directly, typically in debug mode:

if ($app->isDebug()) {
    $app->middleware()->incoming(AssetServer::class);
}
/_assets/app/app.js      -> assets/app.js
/_assets/admin/a.css     -> modules/Admin/assets/a.css

Responses carry Cache-Control: no-store. Edit a file, reload, done. The server is never enabled automatically: like everything in Kaly, the framework provides the mechanism and the application decides its runtime.

Production

A build step publishes the sources to an immutable versioned directory:

// bin/build.php
$publisher = new AssetPublisher($sources, $paths->publicDir());
$publisher->publish();
public/assets/
  .version
  8af319c42d/
    app/
    admin/
    site/

URLs become /assets/8af319c42d/app/app.js, served with Cache-Control: public, max-age=31536000, immutable. There is no framework CLI: call publish() from bin/build.php, a composer script, a Makefile or CI.

The version is the explicit argument (or APP_ASSETS_VERSION) when given, otherwise a deterministic content hash (SHA-256 over sorted paths and contents, truncated to 10 chars). The publisher writes public/assets/.version so the runtime knows which version was built; in production Assets::url() reads APP_ASSETS_VERSION first, then that file, and fails explicitly when nothing was published:

Assets have not been published. Set APP_ASSETS_VERSION or run your asset publisher.

Version resolution is lazy: an application that never calls asset() never fails.

An explicit asset version must change whenever published contents change. An existing destination is considered already published and left untouched: a given version is immutable.

Rules

  • Symlinks are not supported: the publisher fails loudly on them.
  • Path segments starting with . (.env, .htaccess, .git/) are never published and never served.
  • Executable extensions (.php, …) are refused, mirroring FileServer.
  • Generating a URL never checks the filesystem; verification belongs to the dev server and the publisher.

Reference

Class Role
Kaly\Asset\AssetsInterface url(string $asset): string
Kaly\Asset\Assets dev (/_assets/...) and prod (/assets/<version>/...) URLs
Kaly\Asset\AssetSources namespace => directory map shared by all three consumers
Kaly\Asset\AssetPublisher build-time copy + .version
Kaly\Asset\Middleware\AssetServer dev serving, opt-in

Kaly PHP framework — MIT

This site uses Just the Docs, a documentation theme for Jekyll.