Tutorial: Modules and Components

Modules and Components

Modules are external files loaded by a Shui file. This can be:

  • other Shui files (i.e. Shui components)
  • files with JavaScript code
  • WebAssembly compiled binaries
  • CSS files (icons sets are loaded this way)

Loading Modules

The require keyword may be used at the top of a Shui document before the root element to load modules from other files.

require "./MyElement.shui";

Document {

}

If the path is prepended by ./ as in this example, then the path is regarded relative to the path of the current Shui document.

Paths beginning with / are absolute paths on the web server serving the current Shui document.

Module Aliases

When loading a module, you may use the as keyword to assign an alias by which the module can be addressed. This, for instance, is required if you want to invoke functions from a JavaScript or WebAssembly module.

require "./somecode.js" as code;

MouseBox {
    onClick: () => { code.foo(42); }
}

When loading another Shui file this way, you may omit the alias as all loaded modules are searched automatically for elements. The alias may be used for disambiguation, however.

require "./Box.shui" as myBox;

Document {
    myBox.Box {
        // This is NOT Shellfish's default Box Element
    }

    Box {
        // But this is
    }
}

The core and declarative modules are always available in a Shui document under the aliases core and declarative, respectively, without having to import them explicitly.

Loading Stylesheets

CSS stylesheets may be loaded as modules as well. But since stylesheets work globally, it is advisable to only load them at the beginning of the main Shui document.

The main use for loading CSS stylesheets is to provide icon sets.

require "./custom-icons.css";

No module alias is used when loading a CSS stylesheet.

Shortcut Paths

Internal modules are available under shortcut paths, so you don't have to specify a path on the filesystem.

  • shellfish/low: The low module.
  • shellfish/declarative: The declarative module (already loaded).
  • shellfish/html: The html module with elements for HTML UIs.
  • shellfish/ui: The ui module with lots of UI elements.
  • shellfish/3d: The shf3d module with elements for building 3D scenes with OpenGL.
  • shellfish/server: The server module with elements for modeling the server-side.
  • shellfish/fengshui: The fengshui module.
  • shellfish/core/matrix: The matrix module for matrix and vector algebra.

Use the shortcut together with the require keyword.

require "shellfish/ui";

Shui Components

Shui components are other Shui files that can be used just like elements. When loading components as modules, the filename determines the name of the component.

So, for example, the file MyComponent.shui gives the component the name MyComponent.

require "./MyComponent.shui";

Document {

    MyComponent {
        aProperty: 42
        bProperty: 5
    }

}

Therefore, the filename of a component must begin with a capital letter.

JavaScript Modules

When loading a JavaScript file, only exported members are accessible from outside. To export a member, assign it to the predefined exports object.

// somecode.js

function internalFunction()
{
    // this function is internal only
}

function foo(a)
{
    // this function is exported
}
exports.foo = foo;
// main.js

require "./somecode.js" as code;

Document {

    MouseBox {
        onClick: () => { code.foo(42); }
    }

}

Note: Code in JavaScript modules does not know how to handle dynamic values (such as element properties) or element identifiers. Without this overhead (or magic, actually), it runs a tiny bit faster than code blocks inside of a Shui document.

WebAssembly Modules

WebAssembly modules come in two flavors. Either as stand-alone .wasm files, or together with a JavaScript module for loading and setting up a runtime environment.

Stand-alone *.wasm Modules may be imported and invoked just like JavaScript modules.

require "./tools.wasm" as tools;

Document {
    
    MouseBox {
        onClick: () => { tools.foo(42); }
    }

}

WebAssembly files that are tied to a runtime, like what the Emscripten compiler toolchain builds, for instance, are loaded by their main JavaScript file.

Depending on the runtime, manual setup may be required after loading. For example, in case of Emscripten, the onRuntimeInitialized callback hook is provided by the Emscripten runtime module for taking actions as soon as the runtime becomes ready.

require "./cpptools.js" as tools;

Document {
    id: doc

    property cppTools: null

    onInitialization: () =>
    {
        function init()
        {
            // instantiate a C++ class
            doc.cppTools = new tools.CppTools();
        }

        if (tools.calledRun)
        {
            // the runtime is already initialized, so we may use it right ahead
            init();
        }
        else
        {
            // the runtime will be initialized soon, so we have to setup a callback
            tools.onRuntimeInitialized = init;
        }
    }

    MouseBox {
        onClick: () => { box.cppTools.foo(42); }
    }
}

Loading Modules Dynamically

The import function lets you load modules dynamically from within JavaScript code blocks.

It returns a Promise object for the module.

Box {

    onInitialization: () =>
    {
        import(__dirname + "/tools.js")
        .then(tools =>
        {
            tools.foo(42);
        })
        .catch(err => console.error("Failed to load: " + err));
    }

}