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 as follows.
| 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)