Asset registry
How a feature contributes a stylesheet or script to the public shell, so the page’s CSS/JS is composed from the installed features instead of hand-wired into hooks and templates.
Table of contents
- What it is
- SPL rationale: the cascade is derived, not configured
- The
register_assetAPI - The cascade order convention
- How the theme renders assets
- Cleared per app
- See also
What it is
The asset registry is a small framework module —
splent_framework.assets.asset_registry — that lets each feature declare the
CSS and JS files it ships from inside its init_feature(app). The feature drops
the file under its blueprint’s assets/ folder (served by the blueprint asset
route) and registers it; the theme renders every declared asset in the right
place, deduplicated and in a deterministic order.
This replaces two old patterns: hand-written <link> tags emitted from a
layout.head.css hook, and <script> blocks inlined into templates. A feature
no longer injects raw markup to load its assets — it declares them as data, and
the shell decides where and in what order they land.
SPL rationale: the cascade is derived, not configured
In a Software Product Line the page’s asset cascade is a point of variability resolved at derivation time from the set of installed features:
- Install a feature that registers an asset → its
<link>/<script>appears. - Remove it and re-derive the product → it disappears.
Two products built from different feature selections get different asset sets for free, because each set is exactly the union of what its installed features registered. There is no product-level list of stylesheets to maintain, and no risk of two features emitting a duplicate tag — registration is deduplicated by endpoint and params.
The register_asset API
Import it from splent_framework.assets.asset_registry and call it inside
init_feature(app). This is how the slider ships slider.css and slider.js:
from splent_framework.blueprints.base_blueprint import create_blueprint
from splent_framework.services.service_locator import register_service
from splent_io.splent_feature_slider.services import SliderService
slider_bp = create_blueprint(__name__)
def init_feature(app):
from splent_framework.assets.asset_registry import register_asset
register_service(app, "SliderService", SliderService)
register_asset("css", "slider.assets", order=100, subfolder="css", filename="slider.css")
register_asset("js", "slider.assets", order=100, subfolder="js", filename="slider.js")
| Parameter | Required | Description |
|---|---|---|
kind |
yes | "css" or "js". |
endpoint |
yes | The blueprint’s asset route, e.g. "slider.assets". |
order |
no | Load order; lower comes first. Defaults to 100. See the cascade convention below. |
**params |
no | url_for params for the asset route, e.g. subfolder="css", filename="slider.css". |
The call is idempotent: the registry dedupes by (kind, endpoint, params),
so registering the same asset twice does not emit a duplicate tag.
Companion functions:
get_assets(kind)— returns the resolved, deduplicated asset URLs of a kind, ordered by(order, endpoint).url_foris resolved at call time, so this is called from the template (inside a request context); an asset whose route can’t be resolved is silently skipped, not raised.clear_assets()— empties the registry.
You rarely call these two yourself. get_assets is exposed as a Jinja global
and used by the theme; clear_assets() is called per app at feature-load (see
below).
The cascade order convention
order governs the cascade deterministically. The convention is:
| Layer | order |
Wins |
|---|---|---|
| Theme base | ~0 | first (lowest priority) |
| Features | ~100 | middle |
| Skins | ~200 | last (highest priority) |
A skin therefore ships its skin.css via register_asset(..., order=200) so it
loads after every feature’s CSS and can override it. Features stick to the
default 100; the theme’s own base CSS sits near 0.
How the theme renders assets
The public shell — public_base.html in the theme — pulls the registered URLs
straight from the Jinja globals. CSS goes in the <head>:
{% for href in get_assets('css') %}<link rel="stylesheet" href="{{ href }}">{% endfor %}
…and JS is emitted (deferred) just before </body>:
{% for src in get_assets('js') %}<script defer src="{{ src }}"></script>{% endfor %}
Because the URLs are already ordered and deduped, the template just iterates.
Cleared per app
Like the nav registry, the asset registry is cleared per app at feature-load:
the framework’s FeatureManager calls clear_assets() in register_features()
before loading each product’s features. The registry therefore only ever holds
the assets of this product’s installed features — so a product only emits
the CSS/JS it actually uses.
See also
- Navigation registry — the same declare-then-compose pattern for the main menu
- Admin-configurable settings — declare a feature’s settings and get an admin panel for free
hooks.py& template hooks — inject HTML into layout slots (the other way a feature contributes UI)