Mounting
Mounting
Preactile components render to the DOM via Preact's render function. This page covers
how to set up your project and mount components in different environments.
Prerequisites
You need two things: a ScalaJS build that includes preactile, and Preact itself, spliced into your bundle by sbt-splice. No npm or Node is required for bundling.
Add sbt-splice
// project/plugins.sbt
addSbtPlugin("rocks.earlyeffect" % "sbt-splice" % "0.1.0")
Pin Preact
Preact is imported via @JSImport, so the browser needs a copy of it. Splice downloads a
pinned copy from a CDN and inlines it into your bundle. The sha256 makes the pin
verifiable: an unresolved or mismatched specifier fails the splice task.
val preactSplice = Splice.lib("preact", "10.26.4", "dist/preact.module.js")
.sha256("2ce1b7b810fc14cda3f4242e636311b5a5e6d5a6cb1f3274fe948fe3bba3d32e")
lazy val example = project
.enablePlugins(ScalaJSPlugin)
.dependsOn(core)
.settings(
scalaJSUseMainModuleInitializer := true,
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) },
spliceResolvers += Splice.jsDelivr,
spliceLibs ++= Seq(preactSplice),
)
Building the bundle
Splice produces a self-contained ES module that your page loads directly:
Development —
sbt example/spliceFastwrites an unminified bundle with source maps toexample/target/splice/fast.js.Production —
sbt example/spliceFullClosure-optimizes the same bundle (JDK 21+) toexample/target/splice/full.js.
Your index.html loads the bundle and provides a mount point:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Preactile App</title>
<script type="module" src="/fast.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
Serve the output directory with any static file server.
Development workflow
This repo's example is:
sbt ~example/ascentPreview
That stages the spliced bundle and serves it with
ascent-preview at http://localhost:8766.
Rebuilds rewrite assets/dev-stamp; the app opts in with ascent.js.DevReload.install()
(localhost only). Docs use the same loop: sbt ~docs/specularPreview.
Minimal mount code
Regardless of your build setup, mounting a component looks the same:
import org.scalajs.dom.document
import preactile.*
object App extends Component[Unit]:
def render(props: Unit): VNode =
E.div(
E.h1("My Preactile App"),
E.p("Hello from ScalaJS!"),
)
@main def run(): Unit =
val root = document.getElementById("app")
preactile.preact.render(App(()), root)
The @main annotation makes this your entry point when using
scalaJSUseMainModuleInitializer := true.
Mounting multiple components
You can mount different components into different DOM elements:
import org.scalajs.dom.document
import preactile.*
object Header extends Component[Unit]:
def render(props: Unit): VNode = E.header(E.h1("Header"))
object MainContent extends Component[Unit]:
def render(props: Unit): VNode = E.main(E.p("Main content here."))
@main def run(): Unit =
preactile.preact.render(Header(()), document.getElementById("header"))
preactile.preact.render(MainContent(()), document.getElementById("main"))
Unmounting
To remove a component from the DOM, pass null to render:
import org.scalajs.dom.document
import preactile.*
def unmount(elementId: String): Unit =
val el = document.getElementById(elementId)
if el != null then preactile.preact.render(null, el)
This triggers componentWillUnmount lifecycle hooks and cleans up subscriptions.
Next steps
See Components for building your first component
See Stateful Components for managing internal state
See Conduit Integration for effect-native state management
See Embedding for host React and host Preact pages