NakedJSX Use JSX without React

NakedJSX is a command-line tool for generating HTML files from JSX. The output is pure HTML and CSS - unless you choose to add your own JavaScript.

This is an overview. Please refer to the documentation for a detailed look at each feature.

This page was built using NakedJSX. You can look at its source.

At a Glance

Generate static HTML files from JSX by running an npx command.

Scoped CSS classes are extracted from JSX and deduplicated.

No need to set up a Node.js project, just run an npx command on your NakedJSX content directory and your site is built into an output folder.

A development mode with a live-refresh build and web server is included.

NakedJSX provides an optional and small runtime allowing JSX to be used by client-side JavaScript. The runtime is automatically injected if needed, and adds around half a kilobyte to the file.

A Two Minute Test

If you have Node.js installed, you can try NakedJSX right now. Create a directory called src with the following file (the filename must end in -page.jsx):

src/index-page.jsx

import { Page } from '@nakedjsx/core/page'

const BodyContent =
    ({ title }) =>
    <>
        <h1 css="color: fuchsia">{title}</h1>
        <p css="color: #ff00ff">
            Building HTML files from JSX feels right.
        </p>
    </>

Page.Create('en');
Page.AppendCss('body { font-family: sans-serif }');
Page.AppendHead(<title>Hello NakedJSX</title>);
Page.AppendBody(<BodyContent title="Hello NakedJSX" />);
Page.Render();

Then open the parent directory in your terminal and run:

# build command

$ npx nakedjsx src --out out --pretty

The result is a new subdirectory called out, which contains a single HTML file:

out/index.html (401 bytes) (open in new tab)

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Hello NakedJSX</title>
        <style>
            body {
                font-family: sans-serif;
            }
            .a {
                color: #f0f;
            }
        </style>
    </head>
    <body>
        <h1 class="a">Hello NakedJSX</h1>
        <p class="a">Building HTML files from JSX feels right.</p>
    </body>
</html>

Note that the scoped CSS was extracted from the JSX, minified, and then deduplicated.

If you build it without the --pretty flag, the result is tightly packed and suitable for distribution:

out/index.html (241 bytes) (open in new tab)

<!DOCTYPE html><html lang="en"><head><title>Hello NakedJSX</title><style>body{font-family:sans-serif}.a{color:#f0f}</style></head><body><h1 class="a">Hello NakedJSX</h1><p class="a">Building HTML files from JSX feels right.</p></body></html>

If you build with the --dev flag then a live-refresh development webserver will be started.

Adding Client JavaScript

You can also add client JavaScript that will run in the browser when the page loads. Client JavaScript can also use JSX.

Here's the previous example converted to add the same <BodyContent> in client JavaScript:

src/index-page.jsx

import { Page } from '@nakedjsx/core/page'

const BodyContent =
    ({ title }) =>
    <>
        <h1 css="color: fuchsia">{title}</h1>
        <p css="color: #ff00ff">
            You might not need a single page app?
        </p>
    </>

// Setup the static HTML, without adding <BodyContent>
Page.Create('en');
Page.AppendCss('body { font-family: sans-serif }');
Page.AppendHead(<title>Hello NakedJSX</title>);

// Make the BodyContent JSX function available to browser JavaScript
Page.AppendJs(BodyContent);

// Add some code that will run when a browser loads the page
Page.AppendJs(document.body.appendChild(<BodyContent title="Hello NakedJSX" />));

Page.Render();

The output contains a small amount JavaScript to support creating DOM nodes from JSX, the compiled <BodyContent> function, and the code that adds it to the document (all minified):

out/index.html (2027 bytes) (open in new tab)

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Hello NakedJSX</title>
        <style>
            body {
                font-family: sans-serif;
            }
            .a {
                color: #f0f;
            }
        </style>
    </head>
    <body>
        <script>
            'use strict'
            const t = Element.prototype.appendChild
            function e(t, e, ...n) {
                if (((e = e || {}), 'function' == typeof t))
                    return (e.children = n), t(e)
                const o = document.createElement(t)
                for (const [t, n] of Object.entries(e)) {
                    if (t.startsWith('on')) {
                        const e = t.toLowerCase()
                        if (e in window) {
                            o.addEventListener(e.substring(2), n)
                            continue
                        }
                    }
                    o.setAttribute(t, n)
                }
                for (const t of n) o.appendChild(t)
                return o
            }
            ;(Element.prototype.appendChild = function (e) {
                if (Array.isArray(e)) {
                    for (const t of e) this.appendChild(t)
                    return e
                }
                return 'string' == typeof e
                    ? t.call(this, document.createTextNode(e))
                    : e
                    ? t.call(this, e)
                    : void 0
            }),
                document.body.appendChild(
                    e(
                        ({ title: t }) => [
                            e('h1', { class: 'a' }, t),
                            e(
                                'p',
                                { class: 'a' },
                                'You might not need a single page app?'
                            ),
                        ],
                        { title: 'Hello NakedJSX' }
                    )
                )
        </script>
    </body>
</html>

Without pretty printing, the result is less than a single kilobyte in size:

out/index.html (891 bytes) (open in new tab)

<!DOCTYPE html><html lang="en"><head><title>Hello NakedJSX</title><style>body{font-family:sans-serif}.a{color:#f0f}</style></head><body><script>"use strict";const t=Element.prototype.appendChild;function e(t,e,...n){if(e=e||{},"function"==typeof t)return e.children=n,t(e);const o=document.createElement(t);for(const[t,n]of Object.entries(e)){if(t.startsWith("on")){const e=t.toLowerCase();if(e in window){o.addEventListener(e.substring(2),n);continue}}o.setAttribute(t,n)}for(const t of n)o.appendChild(t);return o}Element.prototype.appendChild=function(e){if(Array.isArray(e)){for(const t of e)this.appendChild(t);return e}return"string"==typeof e?t.call(this,document.createTextNode(e)):e?t.call(this,e):void 0},document.body.appendChild(e((({title:t})=>[e("h1",{class:"a"},t),e("p",{class:"a"},"You might not need a single page app?")]),{title:"Hello NakedJSX"}));</script></body></html>

Several other ways to add client JavaScript are covered in the documentation.

Features

You can find detailed information in the documentation.

Project Status

NakedJSX is approaching version 1.0. Feedback is very welcome!

Please join us on the NakedJSX Discord Server or get in touch via contact@nakedjsx.org.

Design Philosophy

Pure HTML & CSS Output

The output files are ready for your browser to render without executing any JavaScript.

Of course, you can add any client JavaScript you like, and if NakedJSX builds that code then it will be able to use inline JSX to create DOM nodes.

Low-Friction

NakedJSX doesn't require the setting up and maintaining of a Node.js project. Just create the site files and run the npx command to build.

API Stability (After 1.0 Release)

Sites should continue to build with newer versions of NakedJSX.

Conservative Dependency Choices

Where practical, required functionality is implemented directly within NakedJSX. In other cases, well known external dependencies are chosen.

Security Compliance

Supported versions of NakedJSX will aim for an ongoing state of zero npm audit findings.

Dependencies & Acknowledgments

NakedJSX relies on these projects for core functionality. Please consider supporting them.

Babel(donate)

babeljs.io

* Plus @babel/generator, @babel/plugin-syntax-jsx, @babel/plugin-transform-react-jsx plugins, and the @babel/preset-env preset.

Chokidar(donate)

github.com/paulmillr/chokidar#readme

CSSO

github.com/css/csso#readme

CSSTree

github.com/csstree/csstree#readme

js-beautify

github.com/beautify-web/js-beautify#readme

Node.js

nodejs.org

PostCSS(donate)

postcss.org

* Plus postcss-nested plugin.

React

NakedJSX does not use React, but the excellent ideas and work of the engineers at Meta (who created JSX!) must be acknowledged.

react.dev

Rollup

rollupjs.org

* Plus @rollup/plugin-babel.

Terser

terser.org

Project Owner

Hello, I'm David Hogan. Since 1999 I have been a professional software engineer, startup/scaleup CTO, and everything in between. I am currently the CTO at Cortical Labs and a member of the VICE (Commodore 8-bit computer emulator) development team.

I designed and built NakedJSX by following my curiosity and I hope that you find it useful.

You can reach me at: