Assets
Any component can declare the URLs of the CSS and JavaScript files it uses. Jx automatically collects these assets from all the components you use and provides simple functions to render them.
Why Per-Component Assets?
Traditional approaches put all CSS and JS in global files. This has problems:
- Hard to maintain: Which styles belong to which component?
- Bloat: Load everything even if you only use a few components
- Coupling: Can't move/share components without hunting down their styles
With per-component assets:
- Portability: Copy a component folder, and its assets come with it
- Clarity: Each component declares what it needs
- Performance: Only load assets for components you actually use
- Testing: Test component styles and behavior together
Declaring Assets
Use {#css ... #} and {#js ... #} comments at the top of your component:
{#css card.css, animations.css #}
{#js card.js #}
{#def title #}
<div class="card">
<h3>{{ title }}</h3>
<div class="card-body">
{{ content }}
</div>
</div>
Multiple files are comma-separated. Each file can be:
- Relative:
card.css(relative to your static files) - Absolute path:
/static/styles/global.css - URL:
https://cdn.example.com/library.js
The assets Global
When you render a component, Jx provides an assets global object with methods to collect and render assets.
assets.render()
The simplest approach; renders both CSS and JS:
{#css layout.css #}
{#js layout.js #}
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
{{ assets.render() }}
</head>
<body>
{{ content }}
</body>
</html>
This collects assets from the layout component and all components it imports, then renders them as <link> and <script> tags.
assets.render_css()
Renders only CSS as <link> tags:
<head>
<title>My App</title>
{{ assets.render_css() }}
</head>
Output:
<link rel="stylesheet" href="layout.css">
<link rel="stylesheet" href="card.css">
<link rel="stylesheet" href="button.css">
assets.render_js(module=True, defer=True)
Renders JavaScript as <script> tags:
<body>
{{ content }}
{{ assets.render_js() }}
</body>
Output (default):
<script type="module" src="layout.js"></script>
<script type="module" src="card.js"></script>
Options:
module=True(default): Addtype="module"module=False: Regular scriptsdefer=True: Adddeferattribute (only whenmodule=False)
{# ES modules (default) #}
{{ assets.render_js() }}
{# <script type="module" src="..."></script> #}
{# Regular deferred scripts #}
{{ assets.render_js(module=False) }}
{# <script src="..." defer></script> #}
{# Regular non-deferred scripts #}
{{ assets.render_js(module=False, defer=False) }}
{# <script src="..."></script> #}
Collection Methods
For more control, use the collection methods:
assets.collect_css()
Returns a list of all CSS file URLs:
{% for url in assets.collect_css() %}
<link rel="stylesheet" href="{{ url }}">
{% endfor %}
assets.collect_js()
Returns a list of all JS file URLs:
{% for url in assets.collect_js() %}
<script type="module" src="{{ url }}"></script>
{% endfor %}
How Asset Collection Works
Jx collects assets by walking the component tree:
- Start with the root component you're rendering
- Collect its CSS and JS declarations
- Recursively collect from each imported component
- Deduplicate (each file appears only once)
- Return in dependency order
Example:
{#import "./layout.jinja" as Layout #}
{#import "./card.jinja" as Card #}
{#css page.css #}
<Layout>
<Card>...</Card>
</Layout>
{#import "./header.jinja" as Header #}
{#css layout.css #}
<div>
<Header />
{{ content }}
</div>
{#css header.css #}
<header>...</header>
{#css card.css #}
<div class="card">{{ content }}</div>
Collected CSS (in order):
page.css
layout.css
header.css
card.css
Each imported component's assets are collected recursively.
Asset URLs
Jx doesn't process or rewrite asset URLs; they're used exactly as you write them.
Relative URLs
{#css card.css #}
{#js card.js #}
Output:
<link rel="stylesheet" href="card.css">
<script type="module" src="card.js"></script>
How these resolve depends on your HTML base path and server configuration.
Organizing Assets
Option 1: Keep assets next to components
components/
card/
card.jinja
card.css
card.js
button/
button.jinja
button.css
button.js
{#css /static/components/card/card.css #}
{#js /static/components/card/card.js #}
Option 2: Put component assets in a static folder
Keep components and assets separate:
components/
card.jinja
button.jinja
static/
css/
card.css
button.css
js/
card.js
button.js
Use absolute paths:
{#css /static/css/card.css #}
{#js /static/js/card.js #}
{{ assets.render() }}
Or relative ones and use your web framework to resolve them:
{#css css/card.css #}
{#js js/card.js #}
{% for name in assets.collect_css() %}
<link rel="stylesheet" href="{{ url_for('static', filename=name) }}">
{% endfor %}
{% for name in assets.collect_js() %}
<script type="module" src="{{ url_for('static', filename=name) }}"></script>
{% endfor %}
Option 3: Build tool integration
Use Vite, Webpack, or another bundler:
{#css /dist/card.css #}
{#js /dist/card.js #}
Your build tool will generate the files with hashes for cache-busting:
<link rel="stylesheet" href="/dist/card.abc123.css">
<script type="module" src="/dist/card.def456.js"></script>
Best Practices
1. Declare Third-Party Dependencies
{# ✅ Good - explicit dependencies #}
{#css https://cdn.example.com/library.css #}
{#import "./component-using-library.jinja" as Component #}
2. Keep Asset Files Small
Each component should have focused styles and scripts:
{# ✅ Good - focused component #}
{#css button.css #} {# ~2KB #}
{#js button.js #} {# ~1KB #}
{# ❌ Bad - too much stuff #}
{#css button-and-everything-else.css #} {# ~50KB #}
3. Use CSS Scoping
/* ✅ Good - scoped to component */
.Card {
padding: 1rem;
}
.Card__title {
font-size: 1.5rem;
}
/* ❌ Bad - will affect everything */
h3 {
font-size: 1.5rem;
}
Modern browsers support CSS nesting:
.Card {
padding: 1rem;
& h3 {
font-size: 1.5rem;
}
}
No Middleware Required
Jx doesn't require middleware to serve component assets. You serve them however you want:
- Static files: Configure your web framework to serve from
static/ - CDN: Upload to S3/CloudFront and reference those URLs
- Build tools: Use Vite/Webpack to bundle and serve
- Reverse proxy: Nginx/Caddy serve static files
Jx just collects the URLs you declare and renders them as tags.
Performance Considerations
Asset Deduplication
Jx automatically deduplicates assets. If multiple components declare the same CSS file, it's only included once:
{# card.jinja uses common.css #}
{# button.jinja uses common.css #}
{# page.jinja uses both #}
Results in:
<link rel="stylesheet" href="common.css"> <!-- Only once! -->
<link rel="stylesheet" href="card.css">
<link rel="stylesheet" href="button.css">
Loading Order
Assets are collected in dependency order:
- Parent component assets first
- Then imported component assets
- In the order they're imported
This ensures proper cascade and dependency resolution.