NakedJSX Plugin Development Guide

Development of NakedJSX plugins is straightforward. Create project specific plugins in your source directory, or publish them as npm packages for others to use.

Topics

Overview

A NakedJSX plugin is an ESM JavaScript file with a default function export responsible for initialising the plugin and registering it with NakedJSX.

Currently, asset import plugins are the only kind of plugin that NakedJSX supports.

Asset Plugins

Asset plugins replace the default NakedJSX behaviour when an asset file is added to a build via a JavaScript import statement.

An asset import statement that utilises a plugin looks like this:

import someAsset from ':<plugin alias>:path/to/asset.file';

Where <plugin alias> is the alias assigned to the plugin on the build command line (or config file).

Boilerplate Code

Here is the boilerplate code for an asset import plugin:

my-asset-plugin.mjs

export default async function({ logging, register })
{
    // NakedJSX provides access to its logging functions
    const { log, warn, err, fatal } = logging;

    //
    // *** Optional initialisation can go here **
    //

    // Let NakedJSX know what sort of plugin it is.
    register(
        {
            // At the moment, asset plugins are the only valid type.
            type: 'asset',

            //
            // Asset plugins are expected to provide an 'importAsset' function.
            // This may be relaxed in future if the asset plugin system is
            // extended to support other functionality.
            //

            async importAsset(context, asset)
            {
                //
                // *** Asset import implementation goes here ***
                //

                //
                // Return JavaScript code exporting whatever results we
                // want to pass to the importing code:
                //

                return 'export default "Hello boilerplate!";';
            }
        });
}

Return to list of topics

Example: Asset File Details Plugin

Here is a simple plugin that adds an asset file to the built output normally, but suppliments the returned asset output href with original filename, size, and modification date information.

The example asset file is a small SVG file that we want to add to the build:

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>

The plugin implemenation is a single JavaScript file in the project source directory:

src/file-details.mjs

import fsp from 'node:fs/promises';
import path from 'node:path';

export default async function({ register })
{
    register(
        {
            type: 'asset',

            async importAsset(context, asset)
            {
                // Copy the imported file to the build output
                const outputFile    = await context.hashAndCopyAsset(asset.file);
            
                // Obtain a href/src reference to the output file that pages can use
                const outputHref    = await context.assetUriPath(outputFile);
            
                // Gather the size and modification date of the original file
                const stats         = await fsp.stat(asset.file);
            
                // Assemble the object that the importing code will recieve
                const importResult =
                    {
                        href:       outputHref,
                        name:       path.basename(asset.file),
                        size:       stats.size,
                        modified:   stats.mtime.toUTCString()
                    };
            
                // Generate JavaScript code exporting importResult to the importing code
                return `export default ${JSON.stringify(importResult)}`;
            }
        });
}

Then we have a page that imports our SVG file via our plugin, and displays it along with the stats gathered by the plugin:

src/index-page.jsx

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

//
// Import the SVG asset via our plugin. circleAsset will be set
// to the 'importResult' object exported by the plugin.
//

import circleAsset from ':file-details:./circle.svg'

Page.Create('en');
Page.AppendHead(<meta name="viewport" content="width=device-width, initial-scale=1.0" />);
Page.AppendBody(
    <>
        <h1>Plugin Development Guide - Hello Plugin</h1>
        <figure>
            <img src={circleAsset.href} />
            <figcaption>
                <div css={`
                    display: grid;
                    grid-template-columns: max-content max-content;
                    gap: 0 16px
                `}>
                    File:       <span>{circleAsset.name}</span>
                    Size:       <span>{circleAsset.size} bytes</span>
                    Modified:   <span>{circleAsset.modified}</span>
                </div>
            </figcaption>
        </figure>
    </>
);
Page.Render();

Because we're building with a plugin, we need to modify our build command (or project config file) to tell NakedJSX that we want to use our plugin. This is also how we tell NakedJSX what 'alias' we want to use to refer to our plugin (file-details):

# build command

$ npx nakedjsx src --out out --plugin file-details src/file-details.mjs --pretty

After running the build commend, the SVG file has been copied to the output directory, with a hash in the filename. The HTML file contains an <img> tag that uses the href returned by the plugin as its src. The <figcaption> tag contains a little table of file information returned by the plugin.

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 (795 bytes) (open in new tab)

<!doctype html>
<html lang="en">
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <style>
            .a {
                display: grid;
                grid-template-columns: max-content max-content;
                gap: 0 16px;
            }
        </style>
    </head>
    <body>
        <h1>Plugin Development Guide - Hello Plugin</h1>
        <figure>
            <img src="asset/circle.oMMrymZnZTYGC05OM9Rrf5H5Yj4.svg" />
            <figcaption>
                <div class="a">
                    File: <span>circle.svg</span>Size:
                    <span>151 bytes</span>Modified:
                    <span>Sat, 30 Mar 2024 07:17:02 GMT</span>
                </div>
            </figcaption>
        </figure>
    </body>
</html>

Return to list of topics

API

The importAsset(context, asset) function is passed two objects containing useful functions and data:

async importAsset(context, asset)
{
    const {
        hashAndCopyAsset,   // async hashAndCopyAsset(sourceFilepath) -- return outputFilename
        hashAndMoveAsset,   // async hashAndMoveAsset(sourceFilepath) -- return outputFilename
        assetUriPath,       // async assetUriPath(outputFilename) -- return href/src
        mkdtemp,            // async mkdtemp() -- create a new temporary dir and return the absolute path to it
    } = context;

    const {
        id,                 // An ID representing this import and others like it
        file,               // Absolute path to source asset file
        optionsString       // A URL style (?key=value&...) query string, if one was used
    } = asset;
}

The importAsset function must return a string containing JavaScript code that exports functions and / or data to the importing code. The returned code may contain inline JSX and may freely import its own depenencies.

Return to list of topics

Return to NakedJSX documentation