Skip to content

Asset management

NEEDS TO BE REWRITTEN BY ME

Iridium has a shared asset system that lets your application — or a third‑party plugin — register CSS and JavaScript files (and a favicon) that Iridium then serves and injects into the <head> of every page, with the Content‑Security‑Policy nonce applied for you.

This is how you ship the JS/CSS your custom templ components need, a favicon, or a compiled Tailwind stylesheet.

Where to register

Register assets during bootstrap, before app.Serve(...):

go
package main

import (
    "embed"

    "github.com/iridiumgo/iridium/core/asset"
)

//go:embed assets
var appAssets embed.FS

func registerAssets() {
    asset.Register(
        asset.Css("app", appAssets, "assets/app.css"),
        asset.Js("app", appAssets, "assets/app.js").Module(),
    )
    asset.Favicon(appAssets, "assets/favicon.ico")
}

func main() {
    registerAssets()

    app := iridium.NewIridiumApp(config).RegisterPanels(/* ... */)
    app.Serve(mux)
}

That's it — app.css and app.js now load in the <head> of every page, and /favicon.ico is served. There is no separate "build assets" command.

Registering CSS

go
asset.Register(
    asset.Css("custom-stylesheet", appAssets, "assets/custom.css"),
)
  • The first argument is the id; the file is served at /static/registered/app/custom-stylesheet.css.
  • It's linked into every page's <head> automatically.

From a URL / CDN

External stylesheets are linked but not served or embedded by Iridium:

go
asset.Register(
    asset.CssURL("fontawesome", "https://cdn.example.com/fa.css"),
)

External origins must be allowed by your CSP. See CSP below.

Registering JavaScript

go
asset.Register(
    asset.Js("custom-script", appAssets, "assets/custom.js"),       // classic <script>
    asset.Js("app-module", appAssets, "assets/app.js").Module(),    // <script type="module">
    asset.Js("analytics", appAssets, "assets/analytics.js").Defer(),
)
asset.Register(
    asset.JsURL("stripe", "https://js.stripe.com/v3/"),             // external
)

.Module() and .Defer() add type="module" / defer to the tag. .Attr("crossorigin", "anonymous") adds arbitrary attributes (e.g. integrity).

Registering assets for a plugin

If you're writing a plugin, namespace your assets with .Package(...) so your ids never clash with the host app or another plugin:

go
asset.Register(
    asset.Css("styles", pluginAssets, "assets/styles.css").Package("acme/blog"),
    asset.Js("editor", pluginAssets, "assets/editor.js").Package("acme/blog").Module(),
)

These serve under /static/registered/acme/blog/.... Re‑registering the same package + kind + id is ignored (first registration wins), so a plugin and an app can't double‑inject the same file.

Lazy loading (load on request)

By default every registered asset loads on every page. Mark heavy or page‑specific assets as load‑on‑request so they are not auto‑injected:

go
asset.Register(
    asset.Css("editor", appAssets, "assets/editor.css").LoadedOnRequest(),
)

Then fetch the URL where you need it and load it yourself (e.g. with Alpine):

go
// In a templ component:
<div x-data x-init={ "loadCss('" + asset.StyleHref("editor") + "')" }>
    ...
</div>

asset.StyleHref(id, pkg...) and asset.ScriptSrc(id, pkg...) return the served URL of a registered asset (empty string if it isn't registered).

CSS variables

Expose backend values to your CSS as :root custom properties:

go
asset.RegisterCSSVariables(map[string]string{
    "brand-color":      "#0a7cff",
    "background-image":  "url(/static/registered/app/bg.jpg)",
})
css
.btn-brand { background: var(--brand-color); }
body       { background-image: var(--background-image); }

These render into a nonce'd <style>:root{ ... }</style> block in the <head>.

Favicon

go
asset.Favicon(appAssets, "assets/favicon.ico") // served at /favicon.ico + <link rel="icon">
// or
asset.FaviconURL("https://cdn.example.com/favicon.ico")

Custom templ components with their own JS/CSS

A templ component doesn't bundle assets. Register the component's CSS/JS once at boot, then write the component normally:

go
//go:embed assets
var appAssets embed.FS

func init() {
    asset.Register(
        asset.Css("rating", appAssets, "assets/rating.css"),
        asset.Js("rating", appAssets, "assets/rating.js").Module(),
    )
}
templ
templ StarRating(value int) {
    <div class="star-rating" data-value={ strconv.Itoa(value) }>
        // ...
    </div>
}

The stylesheet and script are already in the page <head>, so the component just renders its markup. For a one‑off inline script inside a component, see CSP below — you must attach the nonce yourself.

Content Security Policy

Iridium runs with a CSP and (in CSPMode) a strict, nonce‑based policy.

  • Registered assets are handled for you. Every <link>/<script> the asset system injects gets the request nonce automatically, and is served from your own origin, so it satisfies the policy.

  • Inline scripts/styles inside your own templ components are NOT automatic. If you hand‑write an inline <script>, attach the nonce or it will be blocked:

    templ
    import "github.com/iridiumgo/iridium/core/security/csp"
    
    templ MyWidget() {
        <script nonce={ csp.Nonce(ctx) }>
            /* ... */
        </script>
    }

    Prefer registering a real .js file via asset.Js(...) over inline scripts.

  • External origins (CDN CssURL/JsURL) must be added to your CSP's script-src / style-src. Self‑hosting via asset.Css/asset.Js avoids this.

Compiling your own Tailwind CSS

Iridium ships a pre‑compiled core stylesheet (core.css). If you want to use Tailwind utility classes in your components, you build your own Tailwind file and register it — Iridium does not run Tailwind for you.

A Tailwind build only includes the classes it finds in your content globs, so point them at your templ/Go files (and any plugin you use that relies on the host to compile its classes):

js
// tailwind.config.js
export default {
  content: [
    './**/*.templ',                 // your components
    './**/*_templ.go',              // generated templ (covers class strings)
    // If a plugin documents that you must compile its classes, add its files:
    // './vendor/acme/blog/**/*.templ',
  ],
  // ...
}

Build it (input is your own CSS entrypoint with the Tailwind directives):

bash
npx tailwindcss -i ./assets/app.css -o ./assets/app.build.css --minify

Then embed and register the compiled output:

go
//go:embed assets
var appAssets embed.FS

asset.Register(
    asset.Css("app", appAssets, "assets/app.build.css"),
)

Notes for plugin authors

  • Don't ship a pre‑built Tailwind file from a plugin unless you have to. Tailwind output is specific to each application's class usage. Instead, ship the raw CSS and tell users to add your files to their content array so their build picks up your classes.
  • The exception: if your plugin uses utility classes the host app's build would never see (e.g. classes only present in compiled Go strings the user can't glob), compile and register a Tailwind stylesheet in the plugin instead.

Reference

FunctionPurpose
asset.Css(id, fsys, path)Register a served stylesheet
asset.CssURL(id, url)Register an external stylesheet (linked only)
asset.Js(id, fsys, path)Register a served script
asset.JsURL(id, url)Register an external script (linked only)
.Package(name)Namespace the asset (plugins)
.Module() / .Defer()type="module" / defer on a script
.LoadedOnRequest()Don't auto‑inject; load on demand
.Attr(k, v)Extra tag attribute (integrity, crossorigin, …)
asset.Register(...)Add assets to the registry
asset.RegisterCSSVariables(map)Expose :root CSS variables
asset.Favicon(fsys, path) / asset.FaviconURL(url)Register a favicon
asset.StyleHref(id, pkg...) / asset.ScriptSrc(id, pkg...)Served URL of an asset

Released under the MIT License.