@thomasrandolph/taproot

It just makes my life a little simpler

Usage no npm install needed!

<script type="module">
  import thomasrandolphTaproot from 'https://cdn.skypack.dev/@thomasrandolph/taproot';
</script>

README

taproot

What is taproot

taproot is a framework in the truest sense of the word.

taproot provides everything you need to get an application with sound architectural principles running, but doesn't provide any of the parts of that application.

Imagine that your application is a house you'd like to build; taproot pours the foundation and builds scaffolding. You bring the rest.

Docs

For now, see Quick Start below

Quick Start

Installation

npm install -E @thomasrandolph/taproot

taproot ships native ES modules using bare module specifiers. If you don't already have a process that supports resolving bare module specifiers, you may want to read the Advanced Installation section.

Alternatively, see the Setup section for examples that use a version hosted by Skypack.

Setup

taproot works in a minimal mode with zero configuration.

<html>
    <head>
        <script type="module">
            import { setup } from "https://cdn.skypack.dev/@thomasrandolph/taproot";

            async function start(){
                await setup();
            }

            start();
        </script>
    </head>
</html>

By default taproot attaches itself to the global scope as window.taproot.
This is obviously undesireable in most cases, so a namespace can be provided.

await setup( {
    "namespace": "MyApp"
} );

Now your application runtime is accessible from window.MyApp.
In either case, taproot assigns your namespace to window.NAMESPACE, so it never needs to be hard-coded anywhere else.

console.log( window[ window.NAMESPACE ].startupOptions );

If you run the above code, you'll notice that db is false, but routing is true.
By default, taproot assumes that your application will have multiple routes.

For now, we can turn routing off to simplify our application.

await setup( {
    "namespace": "MyApp",
    "routing": false
} );

Now your application exists, but none of the useful features are available!
This is like pouring the foundation, but not setting up the scaffolding.


You might want to wait until you've completed other tasks before you start your application, so by default taproot is idle.
To start an idle taproot instance, you call .start().

var MyApp = await setup( {
    "namespace": "MyApp",
    "routing": false
} );

MyApp.start();

To see the event-driven power of taproot in action, let's subscribe to an event and then start the application.

async function start(){
    var MyApp = await setup( {
        "namespace": "MyApp",
        "routing": false
    } );
    
    MyApp.subscribeWith( {
        "TAPROOT|START": () => {
            document.body.innerHTML = "My application started up!";
        }
    } );
    
    MyApp.start();
}

start();

Writing an Application

import { setup } from "https://cdn.skypack.dev/@thomasrandolph/taproot";

async function start(){
    var MyApp = await setup( {
        "namespace": "MyApp",
        "routing": {
            "routes": [
                [
                    "/",
                    () => {
                        document.body.innerHTML = `Hello, world! Go to <a href="/account/put-your-name-here">/account/[your name]</a>!`;
                    }
                ],
                [
                    "/account/:name",
                    ( context ) => {
                        document.body.innerHTML = `Hello, ${context.params.name}`;
                    }
                ]
            ]
        }
    } );
    
    MyApp.start();
}

start();

Now you have a working application with routing!
But we should leverage the powerful message bus provided by taproot.

import { setup } from "https://cdn.skypack.dev/@thomasrandolph/taproot";
import { publish } from "https://cdn.skypack.dev/@thomasrandolph/taproot/MessageBus/MessageBus.js";

async function start(){
    var MyApp = await setup( {
        "namespace": "MyApp",
        "routing": {
            "routes": [
                [ "/", () => publish( { "name": "VIEW_CHANGE", "view": "home" } ) ],
                [ "/account/:name", ( context ) => publish( { "name": "VIEW_CHANGE", "view": "account", context } ) ]
            ]
        }
    } );

    MyApp.subscribeWith( {
        "VIEW_CHANGE": ( message ) => {
            let views = {
                "home": () => document.body.innerHTML = `Hello, world! <br /><br /> Go to <a href="/account/put-your-name-here">/account/[your name]</a>`,
                "account": ( msg ) => document.body.innerHTML = `Hello, ${msg.context.params.name}`,
                "error": () => document.body.innerHTML = "There was an error trying to route you."
            };

            if( views[ message.view ] ){
                views[ message.view ]( message );
            }
            else{
                views.error();
            }
        }
    } );
    
    MyApp.start();
}

start();

Advanced Installation

Use esinstall to create importable libraries

  1. Install esinstall: npm install -E -D esinstall
  2. Run a script for all your dependencies:
    import { install } from "esinstall";
    
    async function run(){
    await install(
        [
            "@thomasrandolph/taproot",
            "@thomasrandolph/taproot/MessageBus"
            // Etc.
        ],
        {
            "sourceMap": false,
            "dest": "./web_modules/",
            "treeshake": true
        }
    );
    }
    
    run();
    
  3. When you import the module, use web_modules/@thomasrandolph/taproot.js
  4. Repeat step 2 whenever your dependencies change (this can be automated, for example as a postinstall hook)

Use other bundling options

Any combination of bundlers and transpilers can resolve bare module specifiers into something that works on the web.

As long as your bundler understands ECMAScript Modules (the standard module specification for JavaScript), you can use taproot in your build pipeline.