A bundler, a server and a test runner for modern modules and applications.

Building web apps

Esbuild supports out of the box bundling for JavaScript and CSS. RNA introduces a plugin for HTML entrypoints in order to bundle Single Page Applications collecting scripts, styles, assets, icons and manifest. JavaScript and CSS sources are bundled the same way as they are used as entrypoints.


In order to bundle a Single Page Application using RNA you may have to install the bundler:

$ npm i -D @chialab/rna @chialab/rna-bundler @chialab/esbuild-plugin-html
$ yarn add -D @chialab/rna @chialab/rna-bundler @chialab/esbuild-plugin-html

and run:

$ npx rna build src/index.html --output public
$ yarn rna build src/index.html --output public

Collecting scripts

Scripts are bundled following the conventions describe in the Building JavaScript page.
There are two kinds of script: plain and module.

Module scripts can use ESM import/export statements and they are referenced in the HTML file using a script with type="module". Source can be inline:

<script type="module">
    import { route } from 'router';

    route('/profile', () => {});

or external:

<script type="module" src="js/index.js"></script>

External files are regularly bundled, while inline scripts are loaded and builded as virtual modules. At the end, the will both result in externally loaded esm modules and es2017 target:

<script type="module" src="js/inline-XXXXXX.js"></script>
<script type="module" src="js/index-XXXXXX.js"></script>

Plain scripts can also be inlined:


or a file reference:

<script src="js/index.js"></script>

Plain scripts will output using iife format and es5 target if the @chialab/esbuild-plugin-babel module is installed, and they will be respectively inlined or referenced.

Collecting styles

Styles can be imported as file using a <link rel="stylesheet"> or inlined using the style tag. Both will resolve @import statements and collect url() files, following the conventions describe in the Building CSS page.

For example:

&lt;link rel="stylesheet" href="style/style.css">

    @import url('normalize.css');

    .scale {
        transform: scale(2);

will output

&lt;link rel="stylesheet" href="style/style-XXXXX.css">

    body { margin: 0; padding: 0 };

    /* other stuff */

    .scale {
        transform: scale(2);
        -webkit-transform: scale(2);


Browsers have various support for favicons and added-to-home websites. If a rel="icon" is defined in the HTML file, RNA will generate common icon files as well as their references:

&lt;link rel="icon" href="icon.png">


&lt;link rel="icon" sizes="16x16" href="icons/favicon-16x16.png">
&lt;link rel="icon" sizes="32x32" href="icons/favicon-32x32.png">
&lt;link rel="icon" sizes="48x48" href="icons/favicon-48x48.png">
&lt;link rel="shortcut icon" href="icons/favicon-196x196.png">
&lt;link rel="icon" sizes="196x196" href="icons/favicon-196x196.png">
&lt;link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png">
&lt;link rel="apple-touch-icon" sizes="167x167" href="icons/apple-touch-icon-ipad.png">

You can also instruct RNA to generate iOS launch screens using the icon by adding the apple-touch-startup-image to the rel list:

&lt;link rel="icon apple-touch-startup-image" href="icon.png">


&lt;link rel="apple-touch-startup-image" media="..." href="icons/apple-launch-iphonex.png">
&lt;link rel="apple-touch-startup-image" media="..." href="icons/apple-launch-iphone8.png">
&lt;link rel="apple-touch-startup-image" media="..." href="icons/apple-launch-iphone8-plus.png">
&lt;link rel="apple-touch-startup-image" media="..." href="icons/apple-launch-iphone5.png">
&lt;link rel="apple-touch-startup-image" media="..." href="icons/apple-launch-ipadair.png">
&lt;link rel="apple-touch-startup-image" media="..." href="icons/apple-launch-ipadpro10.png">
&lt;link rel="apple-touch-startup-image" media="..." href="icons/apple-launch-ipadpro12.png">

Web manifest

RNA can update a Web manifest (rel="manifest") using HTML data.

If the referenced manifest does not exists, RNA will start from a blank JSON file. Otherwise, it will update only missing fields.

&lt;link rel="manifest" href="app.webmanifest">


    name,             // &lt;title>&lt;/title>
    description,      // &lt;meta name="description" content="">
    start_url,        // &lt;base href="">
    scope,            // &lt;base href="">
    display,          // standalone
    orientation,      // any
    theme_color,      // &lt;meta name="theme" content="">
    background_color, // #fff
    lang,             // &lt;html lang="">
    icons,            // &lt;link rel="icon" href="">