NakedJSX Documentation

A detailed walkthrough of NakedJSX features.

For a high-level overview, please visit nakedjsx.org.

This page was built using NakedJSX, and you can look its source.

Topics

Hello World

NakedJSX searches a directory for filenames that match *-page.jsx. Each matching file is compiled and then executed to produce a HTML file in an output directory.

Here is a near-minimal NakedJSX project. It consists of one file in an otherwise empty directory:

src/index-page.jsx

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

const BodyContent =
    () =>
    <>
        <h1>Hello World</h1>
        <p>A near-minimal NakedJSX example.</p>
    </>

Page.Create('en');
Page.AppendHead(<title>Hello World</title>);
Page.AppendBody(<BodyContent />);
Page.Render();

Building this requires running an npx command. If you have Node.js installed, you can create the file above and try it now:

# build command

$ npx nakedjsx src --out out --pretty

This tells NakedJSX to look for pages to build in the 'src' directory, build them into a 'out' directory, and to format the generated files nicely.

The result in this case is a single new file:

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

<!doctype html>
<html lang="en">
    <head>
        <title>Hello World</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <p>A near-minimal NakedJSX example.</p>
    </body>
</html>

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

# build command

$ npx nakedjsx src --out out

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

<!DOCTYPE html><html lang="en"><head><title>Hello World</title></head><body><h1>Hello World</h1><p>A near-minimal NakedJSX example.</p></body></html>

Adding CSS and Client JavaScript

CSS and client JavaScript features are covered in detail later. However it's worth looking at a second example that touches on these function areas:

src/index-page.jsx

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

const BodyContent =
    ({ title }) =>
    <>
        <h1 css="color: fuchsia">{title}</h1>
    </>

const ClientJsx =
    () =>
    <p css="color: #ff00ff">
        This paragraph was added by browser JavaScript!
    </p>

// Prepare to produce a HTML file
Page.Create('en');

// Provide some static content
Page.AppendCss('body { font-family: sans-serif }');
Page.AppendHead(<title>Hello NakedJSX 2!</title>);
Page.AppendBody(<BodyContent title="Hello NakedJSX 2!" />);

// Make a JSX function available to browser JavaScript
Page.AppendJs(ClientJsx);

// Add some JavaScript that will run in the browser.
Page.AppendJs(document.body.appendChild(<ClientJsx />));

// Output the HTML file
Page.Render();

The resulting HTML now has embedded CSS and JavaScript:

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

<!doctype html>
<html lang="en">
    <head>
        <title>Hello NakedJSX 2!</title>
        <style>
            body {
                font-family: sans-serif;
            }
            .a {
                color: #f0f;
            }
        </style>
    </head>
    <body>
        <h1 class="a">Hello NakedJSX 2!</h1>
        <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(
                        () =>
                            e(
                                'p',
                                { class: 'a' },
                                'This paragraph was added by browser JavaScript!',
                            ),
                        null,
                    ),
                )
        </script>
    </body>
</html>

Note that:

  1. The scoped CSS is extracted from the JSX, minified, with the resulting class shared by both the page JSX and client JSX.
  2. Some functions were automatically added to allow the compiled client JSX to create DOM nodes in the browser.

The same example built without --pretty is less than a kilobyte in size:

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

<!DOCTYPE html><html lang="en"><head><title>Hello NakedJSX 2!</title><style>body{font-family:sans-serif}.a{color:#f0f}</style></head><body><h1 class="a">Hello NakedJSX 2!</h1><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((()=>e("p",{class:"a"},"This paragraph was added by browser JavaScript!")),null));</script></body></html>

Return to list of topics

Development Tools

Development Server

NakedJSX includes a development server. You can start it by passing the --dev flag on the command line:

$ npx nakedjsx src --out out --dev

This time, instead of exiting after the build, this is displayed:

Development server: http://localhost:8999, Press (x) to exit

You can now open http://localhost:8999 and you will see the rendered index page. If you edit and save src/index-page.jsx, the server will rebuild out/index.html and the browser will refresh automatically.

Return to list of topics

Using a Config File

As projects start to make use of more NakedJSX features, the required command line options can stack up. A config file can be used to avoid this.

Building with --config-save will save the build configuration into a .nakedjsx.json config file in your source directory.

This config file will be automatically read by future builds, removing the need to specify anything other than the source directory when invoking npx nakedjsx.

Config file settings can be overriden by arguments supplied on the command line.

Use of a config file is entirely optional.

Return to list of topics

Build-time Defined Values

Values can be passed from the command line into the generated code. This is useful for switching between multiple client ids for third party APIs.

To use this feature, pass --define <key> <value> on the build command, and import from the key in page JavaScript:

src/index-page.jsx

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

const BodyContent =
    () =>
    <>
        <h1>Definition Injection</h1>
        <p>The following was passed from the build command: {buildValue}</p>
    </>

Page.Create('en');
Page.AppendBody(<BodyContent />);
Page.Render();

# build command

$ npx nakedjsx src --out out --define BUILD_KEY BUILD_VALUE --pretty

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Definition Injection</h1>
        <p>The following was passed from the build command: BUILD_VALUE</p>
    </body>
</html>

Return to list of topics

Import Source Path Aliases

NakedJSX supports import source path aliases, making it easier to manage larger projects.

To use this feature, pass --path-alias <alias> <path> on the build command, and then use the alias at the start of your import string:

lib/something.mjs

export const something = 'a string imported via a path alias.';

src/index-page.jsx

import { Page } from '@nakedjsx/core/page'
import { something } from '$LIB/something.mjs'

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Path Alias</h1>
        <p>Here is something: {something}</p>
    </>
    );
Page.Render();

# build command

$ npx nakedjsx src --out out --path-alias $LIB lib --pretty

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Path Alias</h1>
        <p>Here is something: a string imported via a path alias.</p>
    </body>
</html>

Return to list of topics

JSX

JSX is a HTML-like extension to the JavaScript language, and all due respect must be paid to the people at Meta who designed it. It's a fabulous way to create reusable HTML templates.

For those not familiar, Meta's legacy Introducing JSX page serves as a good introduction to JSX.

Props and Children

If you have used JSX before, props and children work as you would expect. For example:

src/index-page.jsx

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

const Section =
    ({ title, children }) =>
    <>
        <h2>{title}</h2>
        {children}
    </>

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Using JSX</h1>
        <Section title="Properties" />
        <Section title="and Children">
            <p>Work,</p>
            <p>as expected.</p>
        </Section>
    </>
);
Page.Render();

is rendered as follows:

# build command

$ npx nakedjsx src --out out --pretty

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Using JSX</h1>
        <h2>Properties</h2>
        <h2>and Children</h2>
        <p>Work,</p>
        <p>as expected.</p>
    </body>
</html>

Note how the <Section> implementation was able to dynamically set the heading title and selectively place its children.

Return to list of topics

Fragments

Note the <> ... </> JSX 'fragment' syntax. Fragments allow a flat list of JSX elements to be passed around in code in the same way that a single element can be.

Essentially, a tag implementation needs to return either a single top level element like a <div>, or a fragment. The returned element / frament can itself have as many children as you like.

Return to list of topics

Exporting and Importing

JSX functions compile down to functions, and you can export and import them like any other function. A refactored version of the previous example might be split into two files like this:

src/common.jsx

export const Section =
    ({ title, children }) =>
    <>
        <h2>{title}</h2>
        {children}
    </>

src/index-page.jsx

import { Page } from '@nakedjsx/core/page'
import { Section } from './common.jsx'

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Using JSX</h1>
        <Section title="Properties" />
        <Section title="and Children">
            <p>Work,</p>
            <p>as expected.</p>
        </Section>
    </>
);
Page.Render();

Note that the Section tag has been imported from common.jsx.

In this way, libraries of reusable components can be easily shared by multiple pages, or even published in an npm package.

Return to list of topics

Conditional Rendering

The usual JSX conditional rendering tricks work. In the following example, the <h2> title will only render if a title prop is supplied on the <Section> tag:

src/common.jsx

export const Section =
    ({ title, children }) =>
    <>
        {title && <h2>{title}</h2>}
        {children}
    </>

Return to list of topics

Refs

There are times when it is helpful to capture a reference to a created element, and add more children to it later. One use case is to build a table of contents as content is added below:

src/index-page.jsx

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

// Create an empty ref - we'll bind it to an element later
const tocList = Page.RefCreate();

const TocEntry =
    ({ title, path }) =>
    <li>
        <a href={'#' + path}>{title}</a>
    </li>;

const Section =
    ({ title, path, children }) =>
    {
        // A section is being added, so create a link in the table of contents.
        tocList.appendJsx(<TocEntry title={title} path={path} />);

        return  <div id={path}>
                    <h2>{title}</h2>
                    {children}
                </div>
    }

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Refs</h1>
        {/* Capture a reference to the nav ul element using the magic 'ref' prop. */}
        <nav><ul ref={tocList} /></nav>
        <hr/>
        <Section title="Section One" path="section-one">
            This is section one.
        </Section>
        <Section title="Section Two" path="section-two">
            This is section two.
        </Section>
    </>
);
Page.Render();

A Ref is created by calling Page.RefCreate(), and it is later bound to the <ul> element by passing it as a ref prop.

Then, as each Section tag is created, a new TocEntry is added to the <ul> using tocList.appendJsx().

The result:

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Refs</h1>
        <nav>
            <ul>
                <li><a href="#section-one">Section One</a></li>
                <li><a href="#section-two">Section Two</a></li>
            </ul>
        </nav>
        <hr />
        <div id="section-one">
            <h2>Section One</h2>
            This is section one.
        </div>
        <div id="section-two">
            <h2>Section Two</h2>
            This is section two.
        </div>
    </body>
</html>

This documentation uses a similar approach to build the topic list.

Return to list of topics

Context - Parent to Child

Another useful feature allows parent elements to pass data to child elements using the built-in context prop.

In this example, a Section tag uses context to determine which heading tag to use:

src/tags.jsx

const Heading =
    ({ depth, children, ...props }) =>
    {
        if (depth == 1)
            return <h1 {...props}>{children}</h1>
        if (depth == 2)
            return <h2 {...props}>{children}</h2>
        if (depth == 3)
            return <h3 {...props}>{children}</h3>
        if (depth == 4)
            return <h4 {...props}>{children}</h4>
        if (depth == 5)
            return <h5 {...props}>{children}</h5>
        else
            return <h6 {...props}>{children}</h6>
    }

export const Section =
    ({ title, path, context, children }) =>
    {
        // Start a depth of 2, and increment for each nested section.
        context.depth = context.depth ? context.depth + 1 : 2;

        return  <div css="margin-left: 32px">
                    <Heading depth={context.depth}>{title}</Heading>
                    {children}
                </div>
    }

src/index-page.jsx

import { Page } from '@nakedjsx/core/page'
import { Section } from './tags.jsx'

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Context</h1>
        <Section title="h2: Section 1">
            <Section title="h3: Section 1.1">
                <Section title="h4: Section 1.1.1">
                </Section>
            </Section>
            <Section title="h3: Section 1.2">
            </Section>
        </Section>
        <Section title="h2: Section 2">
        </Section>
    </>
);
Page.Render();

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

<!doctype html>
<html lang="en">
    <head>
        <style>
            .a {
                margin-left: 32px;
            }
        </style>
    </head>
    <body>
        <h1>Context</h1>
        <div class="a">
            <h2>h2: Section 1</h2>
            <div class="a">
                <h3>h3: Section 1.1</h3>
                <div class="a"><h4>h4: Section 1.1.1</h4></div>
            </div>
            <div class="a"><h3>h3: Section 1.2</h3></div>
        </div>
        <div class="a"><h2>h2: Section 2</h2></div>
    </body>
</html>

Note that context.depth is never directly decremented, and yet the correct heading tag is used in each case. This is because changes made to the context object itself are never visible to parent elements.

Return to list of topics

Context - Child to Parent

There are cases in which it is useful for children to make data available to their parents. Achieving this requires two things:

  1. A mutable object or array in the parent context
  2. A way to control when children are evaluated

Here is an example of a <Section> tag that adds a 'Back to top' link at the end of each section that has no subsections. This avoids, for example, a top level section with one child section adding two consequative 'Return to Top' links to the page.

src/tags.jsx

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

export const Section =
    ({ title, context, children }) =>
    {
        // If a parent section provided context,
        // let it know it has a subsection.
        if (context.has)
            context.has.subsection = true;

        // Provide a context to child sections
        context.has = { subsection: false };

        // Evaluate children tags immediately.
        // After this, context.has.children is valid.
        children = Page.EvaluateNow(children);

        const result =
            <div css={'margin-left: 48px'}>
                <strong>{title}</strong>
                <p>
                    This section has {
                        context.has.subsection
                            ? 'at least one subsection.'
                            : 'no subsections.'
                    }
                </p>
                {children}
                {!context.has.subsection &&
                    <p><a href="#top">Back to Top.</a></p>
                }
            </div>

        return result;
    }

src/index-page.jsx

import { Page } from '@nakedjsx/core/page'
import { Section } from './tags.jsx'

Page.Create('en');
Page.AppendBody(
    <>
        <h1 id="top">Context (Evaluate Now)</h1>
        <Section title="Section 1">
            <Section title="Section 1.1">
                <Section title="Section 1.1.1" />
                <Section title="Section 1.1.2">
                    <Section title="Section 1.1.2.1" />
                </Section>
            </Section>
            <Section title="Section 1.2">
            </Section>
        </Section>
        <Section title="Section 2">
        </Section>
    </>
);
Page.Render();

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

<!doctype html>
<html lang="en">
    <head>
        <style>
            .a {
                margin-left: 48px;
            }
        </style>
    </head>
    <body>
        <h1 id="top">Context (Evaluate Now)</h1>
        <div class="a">
            <strong>Section 1</strong>
            <p>This section has at least one subsection.</p>
            <div class="a">
                <strong>Section 1.1</strong>
                <p>This section has at least one subsection.</p>
                <div class="a">
                    <strong>Section 1.1.1</strong>
                    <p>This section has no subsections.</p>
                    <p><a href="#top">Back to Top.</a></p>
                </div>
                <div class="a">
                    <strong>Section 1.1.2</strong>
                    <p>This section has at least one subsection.</p>
                    <div class="a">
                        <strong>Section 1.1.2.1</strong>
                        <p>This section has no subsections.</p>
                        <p><a href="#top">Back to Top.</a></p>
                    </div>
                </div>
            </div>
            <div class="a">
                <strong>Section 1.2</strong>
                <p>This section has no subsections.</p>
                <p><a href="#top">Back to Top.</a></p>
            </div>
        </div>
        <div class="a">
            <strong>Section 2</strong>
            <p>This section has no subsections.</p>
            <p><a href="#top">Back to Top.</a></p>
        </div>
    </body>
</html>

A another use of this feature can be seen in the <Example> tag used to compile the examples in this documentation.

Return to list of topics

CSS

NakedJSX is CSS aware, and injects an inline <style> tag in the document head that contains classes generated by NakedJSX features.

Scoped CSS

NakedJSX will extract CSS classes based on a css JSX prop.

If two different JSX functions contain equivalent CSS, they will share a class in the final output:

src/index-page.jsx

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

const Section =
    ({ title, children }) =>
    <>
        <h2 css="color: fuchsia">{title}</h2>
        {children}
    </>

Page.Create('en');
Page.AppendBody(
    <>
        <Section title="Fuchsia Title">
            <p css="color: #ff00ff">Fuchsia Content.</p>
        </Section>
    </>
);
Page.Render();

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

<!doctype html>
<html lang="en">
    <head>
        <style>
            .a {
                color: #f0f;
            }
        </style>
    </head>
    <body>
        <h2 class="a">Fuchsia Title</h2>
        <p class="a">Fuchsia Content.</p>
    </body>
</html>

In this case the CSS compiler produces color: #f0f from both color: fuchsia and color: #ff00ff, so a single CSS class is shared by both elements.

Return to list of topics

Nested CSS

NakedJSX supports CSS nesting syntax, with automatic conversion to browser compatible CSS:

src/index-page.jsx

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

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Nested CSS</h1>
        <ul css={`
            list-style-type: upper-roman;

            & li {
                line-height: 1.5
            }
        `}>
            <li>Item one</li>
            <li>Item two</li>
            <li>Item three</li>
            <li>Item four</li>
        </ul>
    </>
    );
Page.Render();

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

<!doctype html>
<html lang="en">
    <head>
        <style>
            .a {
                list-style-type: upper-roman;
            }
            .a li {
                line-height: 1.5;
            }
        </style>
    </head>
    <body>
        <h1>Nested CSS</h1>
        <ul class="a">
            <li>Item one</li>
            <li>Item two</li>
            <li>Item three</li>
            <li>Item four</li>
        </ul>
    </body>
</html>

Return to list of topics

Page.AppendCss()

Raw CSS can be added by passing a string containing CSS to the Page.AppendCss() function.

This is particularly useful for incorporating CSS imported from a file as a raw asset. When NakedJSX knows what CSS us being used, it can avoid generating CSS classes with names that are already in use.

src/index-page.jsx

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

Page.Create('en');
Page.AppendCss(`
    html {
        font-family: sans-serif;
    }
    `);
Page.AppendBody(
    <>
        <h1>Phew.</h1>
        <p>Those yucky serifs are gone.</p>
    </>
);
Page.Render();

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

<!doctype html>
<html lang="en">
    <head>
        <style>
            html {
                font-family: sans-serif;
            }
        </style>
    </head>
    <body>
        <h1>Phew.</h1>
        <p>Those yucky serifs are gone.</p>
    </body>
</html>

Return to list of topics

Common CSS File

Document default CSS and common utility classes can be placed in a common CSS file that is added to all pages. This requires building with an additional flag (but don't forget that build flags can be saved into a config file):

src/style.css

html {
    font-family: sans-serif;
}
body {
    font-size: 1.125rem;
}

src/index-page.jsx

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

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Hello again, NakedJSX</h1>
        <p>
            A minimal NakedJSX example,
            with a common CSS file.
        </p>
    </>
    );
Page.Render();

built with the following command:

# build command

$ npx nakedjsx src --out out --css-common src/style.css --pretty

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

<!doctype html>
<html lang="en">
    <head>
        <style>
            html {
                font-family: sans-serif;
            }
            body {
                font-size: 1.125rem;
            }
        </style>
    </head>
    <body>
        <h1>Hello again, NakedJSX</h1>
        <p>A minimal NakedJSX example, with a common CSS file.</p>
    </body>
</html>

Return to list of topics

Assets

Assets files such as images, JSON, CSS can be added to a build via a JavaScript import statment.

Optionally, the asset file can be processed during import by a NakedJSX plugin and it's simple to make your own plugins.

Arbitrary Files

NakedJSX allows you to publish and link to arbitrary files via the asset system.

To import a file as a generic asset, use a regular JavaScript import statement but prefix the import path with ::, like this:

import circleHref from '::./circle.svg'

This will cause NakedJSX to place a copy of ./circle.svg in the asset directory in the build output folder, and circleHref will be a relative URL to it.

For example:

src/circle.svg

<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
    <circle cx="32" cy="32" r="30" fill="#fff" stroke="#000" stroke-width="2" />
</svg>

src/index-page.jsx

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

import circleHref from '::./circle.svg'

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Title</h1>
        <p><img src={circleHref} /></p>
    </>
);
Page.Render();

produces the following:

out/asset/circle.oMMrymZnZTYGC05OM9Rrf5H5Yj4.svg (151 bytes) (open in new tab)

<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
    <circle cx="32" cy="32" r="30" fill="#fff" stroke="#000" stroke-width="2" />
</svg>

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Title</h1>
        <p><img src="asset/circle.oMMrymZnZTYGC05OM9Rrf5H5Yj4.svg" /></p>
    </body>
</html>

Return to list of topics

JSON Data

If you have JSON data in a file, you can import it using a :json: asset import like this:

src/data.json

{
    "Australia":
        {
            "population": 26357171,
            "updated": "Monday, May 29, 2023"
        }
}

src/index-page.jsx

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

import data from ':json:./data.json'

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Population of Australia</h1>
        <p>
            {data.Australia.population} as of {data.Australia.updated}.
        </p>
    </>
);
Page.Render();

the result:

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Population of Australia</h1>
        <p>26357171 as of Monday, May 29, 2023.</p>
    </body>
</html>

Return to list of topics

Raw Asset String

Sometimes it is desireable to embed the content of an asset itself in your page JS, as this can save a round trip to the server. NakedJSX includes a :raw: import plugin that returns a string containing the contents of a specified asset file.

That content can then be placed directly into the document without further processing, using the built-in <raw-content> tag.

src/circle.svg

<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
    <circle cx="32" cy="32" r="30" fill="#fff" stroke="#000" stroke-width="2" />
</svg>

src/index-page.jsx

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

import circleRaw from ':raw:./circle.svg'

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Title</h1>
        <raw-content content={circleRaw} />
    </>
);
Page.Render();

with the result:

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Title</h1>
        <svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
            <circle
                cx="32"
                cy="32"
                r="30"
                fill="#fff"
                stroke="#000"
                stroke-width="2"
            />
        </svg>
    </body>
</html>

Return to list of topics

Raw Asset Buffer

The :raw: plugin also supports importing an asset as a Buffer object rather than as a utf-8 string. To do this, add a query string of ?as=Buffer:

src/circle.svg

<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
    <circle cx="32" cy="32" r="30" fill="#fff" stroke="#000" stroke-width="2" />
</svg>

src/index-page.jsx

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

import circleRawBuffer from ':raw:./circle.svg?as=Buffer'

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Raw Buffer</h1>
        <pre><code>{
            JSON.stringify(circleRawBuffer.toJSON())
        }</code></pre>
        <pre><code>{
            circleRawBuffer.toString()
        }</code></pre>
    </>
);
Page.Render();

with the result (best viewed via 'open in new tab'):

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Raw Buffer</h1>
        <pre><code>{&quot;type&quot;:&quot;Buffer&quot;,&quot;data&quot;:[60,115,118,103,32,119,105,100,116,104,61,34,54,52,34,32,104,101,105,103,104,116,61,34,54,52,34,32,120,109,108,110,115,61,34,104,116,116,112,58,47,47,119,119,119,46,119,51,46,111,114,103,47,50,48,48,48,47,115,118,103,34,62,10,32,32,32,32,60,99,105,114,99,108,101,32,99,120,61,34,51,50,34,32,99,121,61,34,51,50,34,32,114,61,34,51,48,34,32,102,105,108,108,61,34,35,102,102,102,34,32,115,116,114,111,107,101,61,34,35,48,48,48,34,32,115,116,114,111,107,101,45,119,105,100,116,104,61,34,50,34,32,47,62,10,60,47,115,118,103,62]}</code></pre>
        <pre><code>&lt;svg width=&quot;64&quot; height=&quot;64&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;circle cx=&quot;32&quot; cy=&quot;32&quot; r=&quot;30&quot; fill=&quot;#fff&quot; stroke=&quot;#000&quot; stroke-width=&quot;2&quot; /&gt;
&lt;/svg&gt;</code></pre>
    </body>
</html>

Return to list of topics

Client Javascript

NakedJSX can compile client JavaScript to execute in the browser, which has two distinct advantages over linking to externally built JavaScript:

By default, compiled client JavaScript is placed in a <script> tag at the end of the <body>. It can also be configured to place it in an asset file, with a <script> linking to it automatically added to the document <head>.

There are several ways to get NakedJSX to compile client JavaScript:

A dedicated file is usually the right choice for large amounts of code, with the other methods used for event handlers, support functions needed by custom JSX tags, and other snippets.

Adding via Dedicated File

Files matching the pattern *-client.mjs are automatically compiled and placed into a script tag in the HTML generated by the corresponing *-page.jsx.

Modern JavaScript can be used, with the result being transpiled to a browser-compatible format when necessary.

src/index-client.js

var p = document.getElementById('click-me');
var clickCounter = 0;
p.onclick =
    () =>
    {
        p.appendChild(document.createElement('br'));
        p.appendChild(document.createTextNode(`Click ${++clickCounter}: This content was dynamically added to the DOM.`));
    };

src/index-page.jsx

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

Page.Create('en');
Page.AppendBody(
    <>
        <h1>Title</h1>
        <p id="click-me">Click Me!</p>
    </>
);
Page.Render();

As you can see, the JavaScript output is minified (although it has been formatted a little because we built with --pretty):

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Title</h1>
        <p id="click-me">Click Me!</p>
        <script>
            'use strict'
            var e = document.getElementById('click-me'),
                t = 0
            e.onclick = () => {
                e.appendChild(document.createElement('br')),
                    e.appendChild(
                        document.createTextNode(
                            `Click ${++t}: This content was dynamically added to the DOM.`,
                        ),
                    )
            }
        </script>
    </body>
</html>

Minification is great for production builds, but not during development. In development mode (--dev) minification is disabled and sourcemaps are generated, providing a comfortable debugging experience.

Return to list of topics

Adding via Page.AppendJs()

Page.AppendJs(...code) adds JavaScript to the page for the browser to execute. If multiple arguments are supplied, each will be added to the client JavaScript in turn.

A string argument containing JavaScript code is supported, however it is usually a better developer experience to pass source directly to Page.AppendJs(). The page JavaScript compilation process will automatically convert code passed this way to strings of code. For example:

Page.AppendJs(
    alert('magic!'),
    document.write('code')
    );
Page.AppendJs(console.log('transformation'))

Becomes:

Page.AppendJs("alert('magic!')", "document.write('code')");
Page.AppendJs("console.log('transformation')");

The code has been converted to strings of code, which are later compiled as client JavaScript. This ultimately results in this <script> being placed the HTML file:

<script>
    alert('magic!');
    document.write('code');
    console.log('transformation');
</script>

Appending blocks of code

If you directly add an anonymous or arrow function, then each statement in the body of that function will be added to the top level scope of the client JavaScript. If an unnamed function was added to the client JavaScript there would be no way to invoke it, so NakedJSX repurposes the syntax:

src/index-page.jsx

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

Page.Create('en');
Page.AppendJs(
    function()
    {
        document.write('zero');
    });
Page.AppendJs(
    () =>
    {
        document.write(' one');
        document.write(' two');
    });
Page.AppendJs(() => document.write(' three'));

Page.AppendBody(<h1>Anon / Arrow Function</h1>);
Page.Render();

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Anon / Arrow Function</h1>
        <script>
            'use strict'
            document.write('zero'),
                document.write(' one'),
                document.write(' two'),
                document.write(' three')
        </script>
    </body>
</html>

Return to list of topics

Adding via Inline Event Handlers

It's also possible to add client JavaScript using classic inline event handlers on JSX elements:

src/index-page.jsx

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

Page.Create('en');
Page.AppendJs(
    function clicked()
    {   
        alert('You clicked!');
    });
Page.AppendBody(
    <>
        <h1>Event Handler</h1>
        <p onClick="clicked()">Click Me!</p>
    </>
    );
Page.Render();

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Event Handler</h1>
        <p onclick="clicked()">Click Me!</p>
        <script>
            'use strict'
            window.clicked = function () {
                alert('You clicked!')
            }
        </script>
    </body>
</html>

The minified output is a little different here, as clicked()has not been renamed to something shorter. Currently, NakedJSX will prevent the renaming of an identifier that is used in an inline event handler. The toplevel function has also been set as a window property to prevent the usual tree shaking process from removing the seemingly unused function. These inefficiencies will be addressed in future updates.

Return to list of topics

Adding via Page.AppendJsCall()

A common pattern is to pass a named function to Page.AppendJs(), and then generate a series of calls to that function with differing arguments.

While it is possible to manually generate a JavaScript string containg the calls and then pass that string to Page.AppendJs(), NakedJSX provides an easier way.

Page.AppendJsCall(functionName, ...args) accepts the name of a function followed by a series of arguments. It generates and then appends client JavaScript that will call that function with those arguments. Here is a contrived example:

src/index-page.jsx

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

Page.Create('en');
Page.AppendJs(
    function type(thing)
    {
        const t = typeof thing;
        if (t !== 'object')
            return t;
    
        return Array.isArray(thing) ? 'array' : t;
    });
Page.AppendJs(
    function clientLog(...args)
    {
        const pre = document.getElementById('parent');

        for (const arg of args)
            pre.innerText += `${type(arg)} arg: ${JSON.stringify(arg)}
`;
    });
Page.AppendJsCall('clientLog', 'one', 2, ['three'], { four: 4 });
Page.AppendJsCall('clientLog', 5.5);
Page.AppendBody(
    <>
        <h1>AppendJsCall</h1>
        <pre id="parent" />
    </>
);
Page.Render();

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

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>AppendJsCall</h1>
        <pre id="parent"></pre>
        <script>
            'use strict'
            function t(t) {
                const n = typeof t
                return 'object' !== n ? n : Array.isArray(t) ? 'array' : n
            }
            function n(...n) {
                const r = document.getElementById('parent')
                for (const e of n)
                    r.innerText += `${t(e)} arg: ${JSON.stringify(e)}\n`
            }
            n('one', 2, ['three'], { four: 4 }), n(5.5)
        </script>
    </body>
</html>

Return to list of topics

Using JSX in Client JavaScript

Client JavaScript can also use JSX. Props are supported, as are scoped and nested CSS. Extracted CSS classes are deduplicated with those used by the HTML.

Refs and context are not currently supported in client JavaScript.

Here is a version of an earlier example converted to use JSX:

src/index-client.js

const JsxTag =
    ({ count }) =>
    <>
        <br/>
        Click {`${count}`}: This
        <span css="color: fuchsia"> JSX </span>
        content was dynamically added to the DOM.
    </>

var clickCounter = 0;

src/index-page.jsx

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

Page.Create('en');
Page.AppendBody(
    <>
        <h1 css="color: fuchsia">Title</h1>
        <p onClick="this.appendChild(<JsxTag count={++clickCounter}/>); console.log(this); console.log({ hello: world, self: this })">Click Me!</p>
    </>
    );
Page.Render();

The client JSX is compiled down to JavaScript that creates the necessary DOM elements and sets their attributes.

About half a kilobyte is added for the DOM element construction runtime.

The browser Element.prototype.appendChild() implementation is patched to add support for adding an array of elements. Without this, this example would have needed to iterate over the JSX fragment returned by <JsxTag />.

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

<!doctype html>
<html lang="en">
    <head>
        <style>
            .a {
                color: #f0f;
            }
        </style>
    </head>
    <body>
        <h1 class="a">Title</h1>
        <p onClick="__nakedjsx_event_handler_a.call(this,event)">Click Me!</p>
        <script>
            'use strict'
            const t = Element.prototype.appendChild
            function n(t, n, ...e) {
                if (((n = n || {}), 'function' == typeof t))
                    return (n.children = e), t(n)
                const o = document.createElement(t)
                for (const [t, e] of Object.entries(n)) {
                    if (t.startsWith('on')) {
                        const n = t.toLowerCase()
                        if (n in window) {
                            o.addEventListener(n.substring(2), e)
                            continue
                        }
                    }
                    o.setAttribute(t, e)
                }
                for (const t of e) o.appendChild(t)
                return o
            }
            Element.prototype.appendChild = function (n) {
                if (Array.isArray(n)) {
                    for (const t of n) this.appendChild(t)
                    return n
                }
                return 'string' == typeof n
                    ? t.call(this, document.createTextNode(n))
                    : n
                      ? t.call(this, n)
                      : void 0
            }
            const e = ({ count: t }) => [
                n('br', null),
                'Click ',
                `${t}`,
                ': This',
                n('span', { class: 'a' }, ' JSX '),
                'content was dynamically added to the DOM.',
            ]
            var o = 0
            window.__nakedjsx_event_handler_a = function (t) {
                this.appendChild(n(e, { count: ++o })),
                    console.log(this),
                    console.log({ hello: world, self: this })
            }
        </script>
    </body>
</html>

Return to list of topics

Multiple Pages

Used as it has been so far in this guide, NakedJSX will generate a single HTML page for each *-page.jsx within the project source directory.

However, it is perfectly valid for a single *-page.jsx file to generate multiple pages, or even no page at all.

Important: while a single *-page.jsx file can produce multiple output pages, the Page API can only work on a single page between each pair of Page.Create() and Page.Render() calls.

Hardcoded

The simplest way to create multiple pages from a single file is to simply add more calls to the Page API and override the output filename, like this:

src/index-page.jsx

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

const BodyContent =
    ({ title, children }) =>
    <>
        <h1>{title}</h1>
        {children}
    </>

// Make a HTML page, and override the default filename
Page.Create('en');
Page.AppendBody(
    <BodyContent title="Output File One">
        <p>This is the content for output file one.</p>
    </BodyContent>
    );
Page.Render('index-one.html');

// Now make another page!
Page.Create('en');
Page.AppendBody(
    <BodyContent title="Output File Two">
        <p>This is the content for output file two.</p>
    </BodyContent>
    );
Page.Render('index-two.html');

Note that a filename is being passed to Page.Render() to override the default filename.

out/index-one.html (171 bytes) (open in new tab)

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Output File One</h1>
        <p>This is the content for output file one.</p>
    </body>
</html>

out/index-two.html (171 bytes) (open in new tab)

<!doctype html>
<html lang="en">
    <head></head>
    <body>
        <h1>Output File Two</h1>
        <p>This is the content for output file two.</p>
    </body>
</html>

Return to list of topics

From Data at Build-Time

It is also possible fetch data at build time and use it to generate an arbitrary number of pages.

Simply fetch the data at the top level scope in the page JavaScript, awaiting as needed, then generate your Page.* API calls in a loop.

TODO: example

Return to list of topics

From Source Generated at Compile-Time

It is possible to fetch or construct sources (JSX / MDX / JavaScript / etc.) at compile time. These sources are then compiled by NakedJSX, with full support for scoped CSS, asset import plugins and anything else that static sources can do.

This requires the use of the dyanmic built-in plugin. This plugin requires a source file that is executed at compile time, and which returns JavaScript source for NakedJSX to compile.

This example generates a NakedJSX page for each of an arbitray number of MDX files. The dynamic JavaScript file scans a folder for MDX files, generates a JavaScript import statement of each via the @nakedjsx/plugin-asset-mdx plugin, and returns an array of JSX:

TODO: example

Return to list of topics

Plugins

Plugins can be enabled by passing --plugin <alias> <plugin-package-name-or-path> on the command line.

Each plugin loaded must be given a unique alias. For an asset import plugin, the alias correspondes to the label between :: characters in an asset import string.

The npx nakedjsx command bundles in @nakedjsx/plugin-asset-image and @nakedjsx/plugin-asset-prism, but other plugins need to be installed either globally (npm install -g) or locally (npm install) in the project source directory or a parent of it. You can also pass a path to a JavaScript file that implements the plugin interface.

@nakedjsx/plugin-asset-image

Generate a <picture> HTML tag with multi-resolution webp & jpeg sourcesets from an image file.

Documentation for @nakedjsx/plugin-asset-image.

@nakedjsx/plugin-asset-prism

Provides a <PrismCode> tag that uses Prism to render source code to HTML with syntax highlighting, and an optional CSS theme that handles both light and dark modes. Handy for creating technical documentation sites.

Documentation for @nakedjsx/plugin-asset-prism.

@nakedjsx/plugin-asset-mdx

This plugin allows MDX files to be imported and used as JSX tags within NakedJSX Pages.

Documentation for @nakedjsx/plugin-asset-mdx.

Plugin Development Guide

Development of asset plugins is straightforward. Create project specific plugins, or publish them in npm packages for others to use.

Plugin development guide.

Return to list of topics

Cookbook

Using JSX with jQuery

With NakedJSX, you can pass inline JSX directly to jQuery functions. See the NakedJSX jQuery cookbook entry for an example.