ArcMoon 1.1.0

Start

Language

Reference

Nothing matches “”.

↑↓ move Enter open Esc close

Reference

Browser build

Compile ArcMoon in the browser: render(), compile(), files, baseUrl and packages.

What it is

ArcMoon also runs in the browser: build a page from ArcMoon code and show it, with no server. It is made for live editors, previews and tools. The Playground on this site uses it. For real sites, build with the CLI in Node.js.

No install needed, straight from a CDN:

index.html
<!doctype html>
<div id="app"></div>
<script type="module">
  import ArcMoon from "https://cdn.jsdelivr.net/npm/arcmoon@1/+esm";

  const am = new ArcMoon({ src: "[p]From the CDN: ${ 6 * 7 }$[end]" });
  document.getElementById("app").append(await am.render());
</script>

Getting it

  • From npm: npm install arcmoon, then import ArcMoon from "arcmoon". Bundlers pick the browser build (dist/browser.js and dist/worker.js) by themselves.
  • From a CDN: import ArcMoon from "https://cdn.jsdelivr.net/npm/arcmoon@1/+esm".

The compile runs in a Web Worker. Browsers only start workers from the page's own site, so from a CDN, ArcMoon starts it through a blob: file. If your page has a Content Security Policy, allow worker-src blob:.

render()

Builds the page as real elements, ready to put in the page. Runtime code works:

import ArcMoon from "arcmoon";

const am = new ArcMoon({
  src: `
    runtime \${
      import { signal } from "arcmoon/reactive";
      const count = signal(0);
    }$
    [button = onclick: runtime \${ () => count(count() + 1) }$]Clicked runtime \${ count() }$[end]
  `
});

const app = document.getElementById("app");
app.append(await am.render());   // insert it right away: runtime code starts on the next task
// clicking the button counts up
  • It returns a document fragment. Insert it right after await am.render(): runtime code starts on the next task.
  • Refs point straight at the built elements, and the data-arcm-ref attributes are removed.
  • { raw } HTML is turned into real elements too.

compile()

Returns the page as an HTML string, like in Node.js:

const html = await new ArcMoon({ src: "[p]Hi ${ 20 + 1 }$[end]" }).compile();
// "<p>Hi 21</p>"

If the page has runtime code, its script loads its code from blob: URLs that only exist while this page is open. So the HTML runs in this page, in a frame or a new tab of it, but not as a saved file:

import ArcMoon from "arcmoon";

const html = await new ArcMoon({ src: "…" }).compile();

// show it in a frame of this page: its runtime code works there
const frame = document.querySelector("iframe");
frame.srcdoc = html;

To make HTML files, build in Node.js.

Files in memory

There is no disk in the browser. Give the files as a map of names to code, and they can import each other:

import ArcMoon from "arcmoon";

const html = await new ArcMoon({
  filename: "index.arcm",
  files: {
    "index.arcm": '[import = Card: "./Card.arcm" !][Card = title: "Virtual"]Body[end]',
    "Card.arcm": '${ const { title } = ArcMoon.props(); }$[div][h3]${ title }$[end][slot!][end]'
  }
}).compile();
// <div><h3>Virtual</h3>Body</div>
  • filename picks which file is the page. Without it, use src for the page code.
  • Paths are relative, as on disk. importAliases works too.
  • .js and .json files in files can be imported by build-time and runtime code.

Files from a server

With baseUrl, files not in files are fetched from that address: components, JSON, scripts:

import ArcMoon from "arcmoon";

// https://example.com/site/components/Card.arcm and https://example.com/site/data.json
const html = await new ArcMoon({
  src: `
    [import = Card: "./components/Card.arcm" !]
    \${ import { name } from "./data.json"; }$
    [Card = title: \${ name }$]From the server[end]
  `,
  baseUrl: "https://example.com/site/"
}).compile();
// <div class="card"><h3>Fetched site</h3>From the server</div>

The server must allow the request (CORS), like any fetch from the page.

npm packages

The browser can't read node_modules. Map each package name to a URL that serves it as a module, like esm.sh:

import ArcMoon from "arcmoon";

const html = await new ArcMoon({
  src: '${ import dayjs from "dayjs"; }$[p]${ dayjs("2026-10-15").format("D MMM YYYY") }$[end]',
  packages: { dayjs: "https://esm.sh/dayjs@1.11.13" }
}).compile();
// <p>15 Oct 2026</p>

The same map serves build-time and runtime code. A package that isn't in it stops the build:

✗ /anonymous.arcm  "dayjs" is not in packages; add packages: { "dayjs": "https://esm.sh/dayjs" }

Props and env

const html = await new ArcMoon({
  src: '${ const { user } = ArcMoon.props(); }$[p]${ process.env.API_URL }$ for ${ user }$[end]',
  props: { user: "Adam" },
  env: { API_URL: "https://api.example.com" }
}).compile();
// <p>https://api.example.com for Adam</p>
  • props works as in Node.js: ArcMoon.props() at the top of the page reads it.
  • env is the whole process.env: the browser has no environment of its own. Each compile gets only the env passed to it.

What build-time code can use

Build-time code runs in the browser's worker, not in Node.js, so some things work differently:

  • npm packages: from packages only.
  • Local .js / .json: from files or baseUrl.
  • node:path, node:events, node:buffer, node:url: browser stand-ins, also by bare name ("path").
  • node:fs: readFileSync, existsSync and promises, reading the files in memory (and fetched ones), not a disk.
  • fetch: the browser's own; CORS applies.
  • Other Node.js modules: an error.
build-time code with node:fs
${ import { readFileSync } from "node:fs"; }$
[pre]${ readFileSync("/notes.txt", "utf8") }$[end]
✗ /anonymous.arcm  "node:child_process" is not available in the browser

The stand-ins are close to Node.js but not the same: for example path.win32 works like posix, and printing a module object can differ. Code that leans on Node.js details is best built in Node.js. In runtime code, every node: module is an error, as always.

What the worker can't touch

The whole compile (imports, warnings, build-time code) runs in a Web Worker, away from your page. Build-time code there has no document, cookies or storage:

await new ArcMoon({ src: "[p]${ typeof document }$ ${ typeof localStorage }$[end]" }).compile();
// "<p>undefined undefined</p>"
  • Each compile has a time limit (timeout, default 5000 ms). A worker that runs over is stopped: ${ }$ code took longer than 500 ms and was stopped.
  • It can still fetch what the browser allows. The protector doesn't run in the browser, so only compile code you trust, or show results in a sandboxed frame.
  • Runtime code from render() runs in your page, with its full access.

All options

new ArcMoon({
  src: "…",                                     // the page code, or
  filename: "index.arcm",                       // the page, from files or baseUrl
  files: { "Card.arcm": "…" },                  // files in memory
  baseUrl: "https://example.com/site/",         // where other files are fetched from
  packages: { dayjs: "https://esm.sh/dayjs" },  // npm packages, by URL
  props: { user: "Adam" },                       // page data for ArcMoon.props()
  env: { API_URL: "…" },                        // process.env in build-time code
  importAliases: { "@": "./src" },
  removeComments: true,
  timeout: 5000,                                // ms, then the worker is stopped
  onWarning: (w) => {},                         // { source, position, message }
  worker: myWorker,                             // for tools: your own worker
  toModuleURL: (code) => url                    // for tools and tests: how code becomes a module URL
});

Warnings and errors

The same as in Node.js: warnings go to onWarning as { source, position, message }, and errors throw with name, source and position. See JavaScript API.

try {
  await new ArcMoon({ src: "[p = id: AKZB]x[end]" }).compile();
} catch (err) {
  err.name;      // "ParserError"
  err.message;   // "/anonymous.arcm:1:10  unquoted value AKZB for prop \"id\"; …"
}

Size

browser.js is about 400 kB and worker.js about 390 kB (minified, before compression). Most of it is the HTML reader and writer. Load ArcMoon only on pages that need it, like an editor.