Assets

Assets in Bonito represent external resources like JavaScript libraries, CSS files, images, and other files. They handle loading, bundling, and serving these resources efficiently across different deployment ways (Server/Plotpane/Notebooks/Static Site/Documenter).

Basic Usage

Assets can reference local files or remote URLs and are automatically served with the appropriate URLs:

using Bonito

# Load a local image
dashi_logo = Asset(joinpath(@__DIR__, "dashi.png"))

# Create an app that displays the image
app = App() do
    # Use CSS class selector to style child images of this specific container
    img_styles = Styles(CSS(".asset-container > img", "width" => "400px"))
    DOM.div(
        img_styles,
        DOM.div(
            DOM.h2("Asset Example"),
            DOM.p("Assets render automatically as the appropriate DOM element:"),
            dashi_logo,  # Automatically becomes DOM.img(src=url_to_asset)
            DOM.p("Or use assets as src attributes for more control:"),
            DOM.img(src=dashi_logo; width="400px");  # Asset URL is inserted as src
            class="asset-container"
        )
    )
end

Asset Example

Assets render automatically as the appropriate DOM element:

Or use assets as src attributes for more control:

JavaScript Assets

JavaScript assets come in two flavors: ES6 modules and traditional scripts.

ES6 Modules

Bonito.ES6ModuleFunction
ES6Module(path)

Create an ES6 module asset that will be bundled using Deno.

ES6 modules are automatically bundled with their dependencies when first loaded. Interpolating an ES6Module in JavaScript code returns a Promise that resolves to the module's exports.

Example

THREE = ES6Module("https://unpkg.com/three@0.136.0/build/three.js")

js"""
$(THREE).then(module => {
    // Use the module
    const scene = new module.Scene();
})
"""

Rebundling

Bonito tracks the timestamp of the main module file and will automatically rebundle if it detects changes. However, changes to imported/included files (e.g., Session.js imported by Bonito.js) are not tracked.

To force a rebundle when you've modified an included file, delete the bundle file:

mod = ES6Module("path/to/module.js")
rm(mod.bundle_file)  # Bonito will rebundle on next use

For Bonito's internal JavaScript:

rm(Bonito.BonitoLib.bundle_file)
source

You can also use the convenient CDN helper (uses esm.sh):

THREE = CDNSource("three"; version="0.137.5")

Traditional Scripts (Non-module)

For traditional JavaScript libraries that expose global variables (like jQuery, ACE editor, etc.):

# The Asset automatically infers the global name from the filename
ace = Asset("https://cdn.jsdelivr.net/ace-builds/src-min/ace.js")
# name is automatically set to "ace"

# Interpolating returns a Promise that resolves with the global object
js"""
$(ace).then(ace => {
    const editor = ace.edit(element);
})
"""
# Override the global name if needed
custom = Asset("https://example.com/library.js"; name="MyLib")

Binary Assets

Non-JavaScript assets are loaded as raw bytes when interpolated into javascript:

image = Asset("path/to/image.png")

js"""
$(image).then(bytes => {
    // bytes is a Uint8Array
})
"""

Full ES6Module example creating a THREEJS visualization:

# Javascript & CSS dependencies can be declared locally and
# freely interpolated in the DOM / js string, and will make sure it loads
# Note, that they will be a `Promise` though, so to use them you need to call `module.then(module=> ...)`.
const THREE = ES6Module("https://cdn.esm.sh/v66/three@0.136/es2021/three.js")

app = App() do session, request
    width = 500; height = 500
    dom = DOM.div(width = width, height = height)
    Bonito.onload(session, dom, js"""
        function (container){
            $(THREE).then(THREE=> {
                var renderer = new THREE.WebGLRenderer({antialias: true});
                renderer.setSize($width, $height);
                renderer.setClearColor("#ffffff");
                container.appendChild(renderer.domElement);
                var scene = new THREE.Scene();
                var camera = new THREE.PerspectiveCamera(75, $width / $height, 0.1, 1000);
                camera.position.z = 4;
                var ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
                scene.add(ambientLight);
                var pointLight = new THREE.PointLight(0xffffff, 0.8);
                camera.add(pointLight);
                scene.add(camera);
                var geometry = new THREE.SphereGeometry(1.0, 32, 32);
                var material = new THREE.MeshPhongMaterial({color: 0xffff00});
                var sphere = new THREE.Mesh(geometry, material);
                scene.add(sphere);
                renderer.render(scene, camera);
            })
        }
    """)
    return dom
end