Browser REPL

May 29, 2026 ยท View on GitHub

The squint vite plugin (squint-cljs/vite) hot-reloads your squint code in the browser and lets you eval into the live page over nREPL. The following setup runs it via npm run dev.

The squint vite plugin currently supports vite v5-v8.

For a working setup, see examples/browser-repl.

Setup

squint.edn:

{:paths ["src"]
 :output-dir "js"
 :extension "js"
 :main plain}

vite.config.js:

import { defineConfig } from 'vite';
import squint from 'squint-cljs/vite';

export default defineConfig({ plugins: [squint()] });

index.html (the plugin injects the :main entry ns):

<div id="plain"></div>

package.json:

{
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build"
  }
}

npm run dev compiles :paths, serves the app, and starts the nREPL server (port 1339, written to .nrepl-port).

npm run build produces a normal optimized bundle with regular (non-REPL) squint output, with the dev-only REPL/HMR stripped.

React / Preact (JSX)

Set :jsx-runtime so squint emits jsx()/jsxs() calls (importing the framework's runtime) instead of raw <tags>. This is what makes #jsx work at the REPL and in the browser - the output is plain JS, with no separate JSX transform step.

{:paths ["src"]
 :output-dir "js"
 :extension "js"
 :main [plain preact reagami-app]
 :jsx-runtime {:import-source "preact"}} ; or "react"

The plugin uses the dev runtime (<import-source>/jsx-dev-runtime) under vite dev and the production runtime (<import-source>/jsx-runtime) for vite build.

See examples/browser-repl/src/preact.cljs for a working component.

Reagami (no JSX)

Reagami is a zero-dep, Reagent-like hiccup renderer. It renders plain hiccup vectors ([:div ...]), so no :jsx-runtime and no #jsx - it works at the REPL out of the box. State is a plain atom with add-watch + render. See examples/browser-repl/src/reagami_app.cljs.

Options

keymeaning
:mainentry ns whose compiled module the plugin injects as a <script> into index.html, booting the app (so the output path isn't hardcoded); symbol/string, or a vector for several
:pathssource dirs (default ["src"])
:output-diroutput dir (default "js")
:extensionoutput extension (default "js")
:nrepl-portnREPL port (default 1339)
:targetruntime target (only browser)
:jsx-runtime{:import-source "react"|"preact"} to emit jsx-runtime calls for JSX (see above)

Optional: pre-bundle deps for a smoother REPL

The first time you require a dependency vite hasn't seen, vite pre-bundles it and reloads the page, which causes loss of REPL state. To avoid this, list the deps you reach for at the REPL in vite's optimizeDeps.include:

export default defineConfig({
  optimizeDeps: {
    include: ['preact', 'preact/hooks', 'preact/jsx-runtime', 'preact/jsx-dev-runtime'],
  },
  plugins: [squint()],
});

Hopefully a better solution for this will be implemented later.