A mountable library picks how its CSS and JavaScript reach the browser
with the :assets option. Each strategy describes entries (a name, a
URL and a kind) that PhoenixMountable.Layout.assets/1 renders with the
request's CSP nonces and phx-track-static.
Embedded
assets:
{PhoenixMountable.Assets.Embedded,
css: "priv/static/app.css",
js: "priv/static/app.js",
prepend: [:phoenix, :phoenix_html, :phoenix_live_view, :phoenix_mountable]}The approach of LiveDashboard, Oban Web and Lotus Web. The files are read,
concatenated and gzipped when the library compiles, and served from
memory at <mount>/css-<md5> and <mount>/js-<md5>. The host needs no
Node.js and no Plug.Static entry.
- Responses are cached for a year as immutable; a hash this build did
not produce answers 404 with
no-store. :prependputs Phoenix's, LiveView's and PhoenixMountable's browser scripts before the library's bundle, so it can use thePhoenix,LiveViewandPhoenixMountableglobals.- The JavaScript response opts out of the CSRF check for cross-origin scripts, since the bundle is public.
Build the files before publishing the package, and list them in the
package's files.
Static
assets:
{PhoenixMountable.Assets.Static,
from: "priv/static/my_lib",
manifest: ".vite/manifest.json",
entries: [app: {"src/main.tsx", :module}]}A directory of files built by Vite, esbuild or another bundler, served
under <mount>/assets with Plug.Static (gzip and brotli variants
included). With a Vite manifest, entries are its keys and their CSS
files become entries too (:app_css); without one, entries are file
names and their URLs carry ?vsn=<md5>.
This is how a library brings a React or Vue frontend: a controller (or
a LiveView) renders the shell with PhoenixMountable.Layout.assets/1,
and the library's API is a forward of its routes.
Host
layout: :host,
assets: {PhoenixMountable.Assets.Host, []}The library's templates are styled by the host's Tailwind, and render inside the host's layout. The host tells Tailwind to scan the library:
Tailwind v4 (Phoenix 1.8), in
assets/css/app.css:@source "../../deps/my_lib";Tailwind v3 (Phoenix 1.7), in
assets/tailwind.config.js:content: ["../deps/my_lib/**/*.*ex", ...]
PhoenixMountable.Igniter.add_tailwind_source/2 adds the right line.
Layouts
<head>
<PhoenixMountable.Layout.meta mount={@mount} />
<PhoenixMountable.Layout.assets mount={@mount} />
</head>meta/1 renders the CSRF token, the LiveView socket path and transport,
and the mount path. The browser helper reads them:
// with the embedded bundle and `prepend: [..., :phoenix_mountable]`
const liveSocket = PhoenixMountable.connect({hooks: MyHooks})
// with a bundler: import {connect} from "phoenix_mountable"
connect({Phoenix: {Socket, LongPoll}, LiveView: {LiveSocket}, hooks})
PhoenixMountable.path("/items/1") //=> "/admin/my_lib/items/1"Deploys
A tab left open across a deploy reconnects to the new server with the
old bundle. Phoenix's static_changed?/1 only knows the host's
manifest, so PhoenixMountable checks the library's own entries when a
library LiveView connects, and reloads the page when they belong to a
previous build. reload_on_stale: false turns it off.
CDNs
A host serves a library's assets from a CDN with asset_host::
my_lib "/my_lib", asset_host: "https://cdn.example.com"