Assets
Any component can declare the URLs of the CSS and JavaScript files that 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: Use¶
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 1: Put components assets in the 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 2: Build Tool Integration¶
Use Vite, Webpack, or another bundler:
{#css /dist/card.css #}
{#js /dist/card.js #}
Your build tool will generates 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: 1. Parent component assets first 2. Then imported component assets 3. In the order they're imported
This ensures proper cascade and dependency resolution.