xoong
Menu · Build and delivery
DocsArchitectureBuild and delivery

Build and delivery

There are three ways to run a xoong app: serve it with xoong dev while you work, ship the static files that xoong build writes, or prerender public pages. The runtime needs no bundler in any of them.

Development: xoong dev

xoong dev is @xoong/server wired to your folder. One web-standard handler does all of it, and file access is injected, so the handler imports no Node module.

{
  "port": 5173,
  "proxy": { "/api": "http://localhost:8080" },
  "spa": false,
  "allowedHosts": [],
  "packages": ["rainlet"]
}
npx xoong dev                          # development: static, proxy, TypeScript stripping
npx xoong build --out dist             # strict-CSP static output
npx xoong build --out dist --base /admin --include data --include img
npx xoong render index.html --api http://localhost:8080 --out dist/index.html

Not yet: live reload and SCSS compilation.

Release: xoong build

What xoong build does Every HTML page under the root is the start. The build follows what each page references: scripts, stylesheets and images, the modules the scripts import, CSS urls, and any files named with --include. Installed @xoong packages are found in node_modules and copied to the same URLs the dev server uses. It then rewrites bare imports to URLs, strips TypeScript, drops the import map and applies the --base prefix. It warns about anything the strict policy would block: an inline script, a style attribute, an inline style element. The result is the dist folder: the files you wrote, plus build-report.json. At the top right is the command that starts it, in a terminal window. The code window shows Terminal: npx xoong build --out dist. Steps: 1. You run xoong build. It starts from every HTML page under the root and follows what each one references: scripts, stylesheets, images, the modules the scripts import, CSS url() and any files named with --include. Installed @xoong packages are found in node_modules. 2. Each file is rewritten as it is copied: bare imports become URLs, TypeScript is stripped, the import map is dropped and the --base prefix is applied. 3. The build warns about anything the strict policy would block: an inline script, a style attribute, an inline style element. 4. The result is dist/: your files, rewritten, plus build-report.json. When the animation plays, a dot goes down the steps in order, while the command is lit: the pages and the installed packages are followed together, then the files are rewritten, checked, and written to dist. HTML pagesevery .html file under --root Follow referencesscript, link, img; module imports;CSS url(); --include files Installed packages@xoong/* fromnode_modules Rewritebare imports to URLs, TypeScript stripped,import map dropped, --base prefix Warnwhat a strict policy blocks: inline script,style attribute, style element dist/your files, rewritten, plus build-report.json 1 2 3 4

No bundling and no minifying: each file in the output is the file you wrote, with its imports rewritten. In motion, a dot goes down the steps in order, while the command is lit: the pages and the installed packages are followed together, then the files are rewritten, checked, and written to dist. Use the slider, or press a number, to look at one step.

The CSP model

The runtime runs under this policy, with no unsafe-eval and no unsafe-inline:

Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self' data:; font-src 'self'; base-uri 'none'; form-action 'self'

It is tested, not only claimed. packages/cli/test/build.test.mjs builds the admin example and serves it in real Chromium with exactly this policy, and fails on any securitypolicyviolation; a second test shows the policy really is enforced there (an inline script is blocked). The documentation site you are reading is built and tested the same way, under /xoong.

Prerendering with @xoong/ssr

Prerendering a page The input is a public page, or a route chosen with --url. xoong render, or renderPage(), runs the real xo-view runtime on a happy-dom document and sends GET requests to the backend API. It waits until no request is pending, for at most ten seconds. The output is HTML that already shows the data; each top-level xo-view keeps its original markup in a template element with data-xo-template. In the browser, the view draws again from that template, with the same result, and then becomes live and interactive. Page HTMLa public page, or a route with --url xoong render / renderPage()the real xo-view runtimeon a happy-dom document Backend APIpublic data Waituntil nothing is loading, up to 10 s HTML with the dataeach top-level xo-view keeps its markup intemplate[data-xo-template] Browserthe view draws again from the template:same result, then live and interactive GET

There is no hydration step: the browser simply draws again from the saved markup, so there is nothing to mismatch.

Size budgets and what the tests guard

The size of a runtime package is a tested promise. test/architecture.test.mjs measures the gzip size of each package's source with comments removed and fails when it passes its budget. It is a regression guard, not the shipped size (that is the files themselves, which are not minified). A budget sits 5 to 10 percent above the measured value, and raising one needs a reason in the commit. The roadmap's target of single-digit kilobytes applies to the minified size, which has not been measured yet.

const budgetKb = { core: 5.6, data: 17.2, forms: 9.5, i18n: 5, ui: 10, controls: 9.5, richtext: 8.5 };

The tests that keep the architecture honest:

Where to look in the code