Introduction

Trunk is a WebAssembly (WASM) web application bundler for Rust. Trunk uses a simple, optional-config pattern for building & bundling WASM, JS snippets & other assets (images, css, scss) via a source HTML file.

Or in layman's terms: Rusty things go in, webby things come out.

Getting started

The following subsections explain what is required to install and use trunk, and how you can start with a basic project.

Pre-requisites

While trunk tries to fetch tools automatically as needed (unless you're running with --offline), some pre-requisites may be required, depending on your environment.

Rust

It might be obvious, but trunk requires an installation of Rust. Not only when installing trunk itself from sources, but also for compiling the Rust-based projects to WebAssembly.

The instructions of installing Rust may vary based on your operating system, a reasonable default comes from the Rust project: https://www.rust-lang.org/learn/get-started

Once installed, you should have the following tools available on your command line:

  • rustup
  • cargo

WebAssembly target

By default, the Rust installation will only install the target for your current machine. However, in this case, we want to cross-compile to WebAssembly. Therefore, it is required to install the target wasm32-unknown-unknown. Assuming you have installed Rust using the standard process and can use rustup, you can add the target using:

rustup target add wasm32-unknown-unknown

Installation

trunk is a standard Rust command line tool and can be installed using standard Rust tooling (cargo), by downloading a pre-compiled binary, or through some distribution packagers.

Installing from source

As trunk uses a standard Rust build and release process, you can install trunk just the "standard way". The following sections will give some examples.

trunk supports a build time features, they are:

rustls (default)
Use rustls for client and server sockets
native-tls
Enable the use of the system native TLS stack for client sockets, and `openssl` for server sockets
update_check (default)
Enable the update check on startup

Installing a release from crates.io

As trunk is released on crates.io, it can be installed by simply executing:

cargo install --locked trunk

Installing from git directly

Using cargo you can also install directly from git:

cargo install --git https://github.com/trunk-rs/trunk trunk

This will build and install the most recent commit from the main branch. You can also select a specific commit:

cargo install --git https://github.com/trunk-rs/trunk trunk --rev <commit>

Or a specific tag:

cargo install --git https://github.com/trunk-rs/trunk trunk --tag <tag>

Installing from the local directory

Assuming you have checked out the trunk repository, even with local changes, you can install a local build using:

cargo install --path . trunk

Installing a pre-compiled binary from trunk

Pre-compiled releases have the default features enabled.

Download from GitHub releases

trunk published compiled binaries for various platforms during the release process. They can be found in the GitHub release section of trunk. Just download and extract the binary as you would normally do.

Using cargo binstall

cargo-binstall allows to install pre-compiled binaries in a more convenient way. Given a certain pattern, it can detect the version from crates.io and then fetch the matching binary from a GitHub release. trunk supports this pattern. So assuming you have installed cargo-binstall already, you can simpy run:

cargo binstall trunk

Distributions

Trunk is released by different distributions. In most cases, a distribution will build their own binaries and might not keep default feature flags. It might also be that an update to the most recent version might be delayed by the distribution's release process.

As distributions will have their own update management, most likely Trunk's update check is disabled.

Brew

trunk is available using brew and can be installed using:

brew install trunk

Fedora

Starting with Fedora 40, trunk can be installed by executing:

sudo dnf install trunk

Nix OS

Using Nix, trunk can be installed using:

nix-env -i trunk

Update check

Since: 0.19.0-alpha.2.

Trunk has an update check built in. By default, it will check the trunk crate on crates.io for a newer (non-pre-release) version. If one is found, the information will be shown in the command line.

This check can be disabled entirely, by not enabling the cargo feature update_check. It can also be disabled during runtime using the environment variable TRUNK_SKIP_VERSION_CHECK, or using the command line switch --skip-version-check.

The actual check with crates.io is only performed every 24 hours.

A basic project

For building the web application, trunk is running a combination of tools, mainly cargo build and wasm-bindgen. Therefore, having a simple cargo project and an index.html file as an entry-point is sufficient to get you started.

Creating a project

Start with creating a fresh Rust project and change into that folder:

cargo new trunk-hello-world
cd trunk-hello-world

Add some dependencies for the web:

cargo add wasm-bindgen console_error_panic_hook
cargo add web_sys -F Window,Document,HtmlElement,Text

Inside the newly created project, create a file src/main.rs with the following content:

use web_sys::window;

fn main() {
    console_error_panic_hook::set_once();

    let document = window()
        .and_then(|win| win.document())
        .expect("Could not access the document");
    let body = document.body().expect("Could not access document.body");
    let text_node = document.create_text_node("Hello, world from Vanilla Rust!");
    body.append_child(text_node.as_ref())
        .expect("Failed to append text");
}

Create an index.html in the root of project:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>Hello World</title>
</head>
<body>
</body>
</html>

Then, start trunk inside that project to have it built and served:

trunk serve --open

This should compile the project, run wasm_bindgen, create an index.html based on the original file which loads and initializes the application.

The application itself is pretty basic, simply getting the document's body and adding a note.

Next steps

Most likely, you do not want to manually update the DOM tree of your application. You might want to add some assets, tweak the build process, use some more browser APIs, perform some HTTP requests, use existing crates for the web, and maybe even interface with the JavaScript world. However, all of this is an extension to this basic project we just created.

Here are some pointers:

Commands

Trunk ships with a set of CLI commands to help you in your development workflows.

build

trunk build runs a cargo build targeting the wasm32 instruction set, runs wasm-bindgen on the built WASM, and spawns asset build pipelines for any assets defined in the target index.html.

Trunk leverages Rust's powerful concurrency primitives for maximum build speeds & throughput.

watch

trunk watch does the same thing as trunk build, but also watches the filesystem for changes, triggering new builds as changes are detected.

serve

trunk serve does the same thing as trunk watch, but also spawns a web server.

clean

trunk clean cleans up any build artifacts generated from earlier builds.

config show

trunk config show prints out Trunk's current config, before factoring in CLI arguments. Nice for testing & debugging.

tools show

trunk tools show prints out information about tools required by trunk and the project. It shows which tools are expected and which are found.

Configuration

Important

Trunk's configuration has massively changed in the 0.21.0 release. The goal was not to break anything, but it might have happened anyway. Also does the layering system work a bit different now.

It might also be that the documentation still mentions only Trunk.toml. If that's the case, then this now includes all other configuration file variants as well.

Trunk supports a layered configuration system. The base comes from a reasonable set of defaults, overridden by a configuration file, overridden command line arguments.

Technically speaking, there's a project configuration struct, which has reasonable defaults. Trunk will try to locate a configuration file and load if into this struct. It will then override this configuration with settings from the command line parser (which includes environment variables).

Configuration files

Trunk will try to locate a configuration file. Either in the local directory, or by using the global argument --config, which can accept either a file, or a directory. If the argument is a file, then this file will be used directly. Otherwise, Trunk will load the first file found, searching for:

  • Trunk.toml
  • .trunk.toml
  • Trunk.yaml
  • .trunk.yaml
  • Trunk.json
  • .trunk.json

If neither of those files is found, Trunk will use the metadata from the Cargo.toml, which defaults to an empty set of metadata.

The directory of the configuration file will become the project root, and all relative files will be resolved based on that project root.

Formats

Trunk's configuration is limited to a JSON compatible model. This means you can easily translate between those different formats.

For example, having the following Trunk.toml configuration:

[build]
dist = "dist"
[serve]
port = 8080

Would be the following in YAML:

build:
  dist: "dist"
serve:
  port: 8080

Also Cargo.toml is based on that model. However, it moves that data down into the package.metadata.trunk section. The example above would become:

[package.metadata.trunk.build]
dist = "dist"
[package.metadata.trunk.serve]
port = 8080

Command line arguments (and environment variables)

Command line arguments can override part of the configuration. Not all configuration aspects can be overridden by the command line arguments though. Command line arguments include the use of environment variables.

Trunk supports --help on all levels of commands and sub-commands. This will show you the available options, as well as the names of the environment variables to use instead.

All relative paths will be resolved against the project root, as evaluated by loading the configuration.

Migration from pre 0.21.0 the best approach to moving forward

While the goal was to support all fields from Trunk.toml, the command line arguments as well as the environment variables, it still is a version breaking the API. In some cases, it just made little sense, and so those fields got marked "deprecated". They trigger a warning today and might be removed in one of the next releases.

Ideally, you don't need to change anything. In some ideal cases, you don't even need any configuration. In case you do, you now have some more choices. You can keep using TOML, you may hide it using .trunk.* variant. You can use YAML or JSON to leverage the JSON schema that is generated. Or if you're a fan of keeping everything in Cargo.toml, that's fine too. The choice is yours.

Important

You need to take care when working with older versions of Trunk though. If you use an older version of Trunk (before 0.21.0) with a project using the newer configuration files, then that version would not consider those files and might consider default settings, due to the missing Trunk.toml file.

Required version

Starting with 0.19.0-alpha.2, it is possible to enforce having a certain version of trunk building the project.

As new features get added to trunk, this might be helpful to ensure that the version of trunk building the current is actually capable of doing so. This can be done using the trunk-version (or using the alias trunk_version) on the root level of the Trunk.toml file.

The version format is a "version requirement", the same format you might know from Cargo's version field on dependencies.

This also supports pre-release requirements, which allows adopting upcoming features early.

Note

Versions prior do 0.19.0-alpha.2 currently do not support this check, and so they will silently ignore such an error for now.

Configuration Schema

Trunk provides a JSON schema for the configuration model. This can be added to e.g. a YAML file using the following syntax:

$schema: "./schema.json"

Obtaining the schema

You can generate the schema by running:

trunk config generate-schema

Or directly write it to a file:

trunk config generate-schema path/to/file

Editor/IDE support

Your editor/IDE needs to support this functionality. Trunk only provides the schema. The following sections provide some information on how to use this.

IntelliJ (and alike)

IntelliJ based IDEs (including Rust Rover) do support JSON schemas in YAML and JSON files. You only need to reference the schema, like:

$schema: "./schema.json"

Hooks

If you find that you need Trunk to perform an additional build action that isn't supported directly, then Trunk's flexible hooks system can be used to launch external processes at various stages in the pipeline.

Build steps

This is a brief overview of Trunk's build process for the purpose of describing when hooks are executed. Please note that the exact ordering may change in the future to add new features.

  • Step 1 — Read and parse the HTML file.
  • Step 2 — Produce a plan of all assets to be built.
  • Step 3 — Build all assets in parallel.
  • Step 4 — Finalize and write assets to staging directory.
  • Step 5 — Write HTML to staging directory.
  • Step 6 - Replace dist directory contents with staging directory contents.

The hook stages correspond to this as follows:

  • pre_build: takes place before step 1.
  • build: takes place at the same time as step 3, executing in parallel with asset builds.
  • post_build: takes place after step 5 and before step 6.

Hook execution

Hooks can be declared exclusively in Trunk.toml, and consist of a stage, command and command_arguments:

  • stage: (required) one of pre_build, build or post_build. It specifies when in Trunk's build pipeline the hook is executed.
  • command: (required) the name or path to the desired executable.
  • command_arguments: (optional, defaults to none) any arguments to be passed, in the given order, to the executable.

At the relevant point for each stage, all hooks for that stage are spawned simultaneously. After this, Trunk immediately waits for all the hooks to exit before proceeding, except in the case of the build stage, described further below.

All hooks are executed using the same stdin and stdout as trunk. The executable is expected to return an error code of 0 to indicate success. Any other code will be treated as an error and terminate the build process. Additionally, the following environment variables are provided to the process:

  • TRUNK_PROFILE: the build profile in use. Currently, either debug or release.
  • TRUNK_HTML_FILE: the full path to the HTML file (typically index.html in TRUNK_SOURCE_DIR) used by trunk.
  • TRUNK_SOURCE_DIR: the full path to the source directory in use by Trunk. This is always the directory in which TRUNK_HTML_FILE resides.
  • TRUNK_STAGING_DIR: the full path of the Trunk staging directory.
  • TRUNK_DIST_DIR: the full path of the Trunk dist directory.
  • TRUNK_PUBLIC_URL: the configured public URL for Trunk.

OS-specific overrides

Often times you will want to perform the same build step on different OSes, requiring different commands. A typical example of this is using the sh command on Linux, but cmd on Windows. To accomodate this, you can optionally create OS-specific overrides for each hook. To do this, specify the default hook, then directly below it create a [hooks.<os>] entry where <os> can be one of windows, macos, or linux. Within this entry you must specify only the command and command_argumnets keys. You may provide multiple overrides for each hook. i.e. One for windows, one for macos, and one for linux.

Assets

Declaring assets to be processed by Trunk is simple and extensible.

All link assets to be processed by Trunk must follow these three rules:

  • Must be declared as a valid HTML link tag.
  • Must have the attribute data-trunk.
  • Must have the attribute rel="{type}", where {type} is one of the asset types listed below.

This will typically look like: <link data-trunk rel="{type}" href="{path}" ..other options here.. />. Each asset type described below specifies the required and optional attributes for its asset type. All <link data-trunk .../> HTML elements will be replaced with the output HTML of the associated pipeline.

rust

rel="rust": Trunk will compile the specified Cargo project as WASM and load it. This is optional. If not specified, Trunk will look for a Cargo.toml in the parent directory of the source HTML file.

  • href: (optional) the path to the Cargo.toml of the Rust project. If a directory is specified, then Trunk will look for the Cargo.toml in the given directory. If no value is specified, then Trunk will look for a Cargo.toml in the parent directory of the source HTML file.
  • data-target-name: (optional) the name of the target artifact to load. If the Cargo project has multiple targets (binaries and library), this value can be used to select which one should be used by trunk.
  • data-bin: (optional) the name of the binary to compile and load. If the Cargo project has multiple binaries, this value can be used to specify that a specific binary should be compiled (using --bin) and used by trunk. This implicitly includes data-target-name.
  • data-type: (optional) specifies how the binary should be loaded into the project. Can be set to main or worker. main is the default. There can only be one main link. For workers a wasm-bindgen javascript wrapper and the wasm file (with _bg.wasm suffix) is created, named after the binary name (if provided) or project name. See one of the webworker examples on how to load them.
  • data-cargo-features: (optional) Space or comma separated list of cargo features to activate.
  • data-cargo-no-default-features: (optional) Disables the default Cargo features.
  • data-cargo-all-features: (optional) Enables all Cargo features.
    • Neither compatible with data-cargo-features nor data-cargo-no-default-features.
  • data-wasm-opt: (optional) run wasm-opt with the set optimization level. The possible values are 0, 1, 2, 3, 4, s, z or an empty value for wasm-opt's default. Set this option to 0 to disable wasm-opt explicitly. The values 1-4 are increasingly stronger optimization levels for speed. s and z (z means more optimization) optimize for binary size instead. Only used in --release mode.
  • data-wasm-opt-params: (optional) run wasm-opt with the additional params. Only used in --release mode.
  • data-keep-debug: (optional) instruct wasm-bindgen to preserve debug info in the final WASM output, even for --release mode. This may conflict with the use of wasm-opt, so to be sure, it is recommended to set data-wasm-opt="0" when using this option.
  • data-no-demangle: (optional) instruct wasm-bindgen to not demangle Rust symbol names.
  • data-reference-types: (optional) instruct wasm-bindgen to enable reference types.
  • data-weak-refs: (optional) instruct wasm-bindgen to enable weak references.
  • data-typescript: (optional) instruct wasm-bindgen to output Typescript bindings. Defaults to false.
  • data-bindgen-target: (optional) specifies the value of the wasm-bindgen flag --target (see link for possible values). Defaults to no-modules. The main use-case is to switch to web with data-type="worker" which reduces backwards compatibility but with some advantages.
  • data-loader-shim: (optional) instruct trunk to create a loader shim for web workers. Defaults to false.
  • data-cross-origin: (optional) the crossorigin setting when loading the code & script resources. Defaults to plain anonymous.
  • data-integrity: (optional) the integrity digest type for code & script resources. Defaults to plain sha384.
  • data-wasm-no-import: (optional) by default, Trunk will generate an import of functions exported from Rust. Enabling this flag disables this feature. Defaults to false.
  • data-wasm-import-name: (optional) the name of the global variable where the functions imported from WASM will be available (under the window object). Defaults to wasmBindings (which makes them available via window.wasmBindings.<functionName>).
  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...
  • data-initializer: (optional) Path to the (module) JavaScript file of the initializer.
  • data-cargo-profile: (optional) A cargo profile to use, instead of the default, for both release or dev mode.
  • data-cargo-profile-release: (optional) A cargo profile to use, instead of the default, for the release mode. Overrides the data-cargo-profile setting.
  • data-cargo-profile-dev: (optional) A cargo profile to use, instead of the default, for the dev mode. Overrides the data-cargo-profile setting.

sass/scss

rel="sass" or rel="scss": Trunk uses the official dart-sass for compilation. Just link to your sass files from your source HTML, and Trunk will handle the rest. This content is hashed for cache control. The href attribute must be included in the link pointing to the sass/scss file to be processed.

  • data-inline: (optional) this attribute will inline the compiled CSS from the SASS/SCSS file into a <style> tag instead of using a <link rel="stylesheet"> tag.
  • data-integrity: (optional) the integrity digest type for code & script resources. Defaults to plain sha384.
  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...

css

rel="css": Trunk will copy linked css files found in the source HTML without content modification. This content is hashed for cache control. The href attribute must be included in the link pointing to the css file to be processed.

  • In the future, Trunk will resolve local @imports, will handle minification (see trunk#7), and we may even look into a pattern where any CSS found in the source tree will be bundled, which would enable a nice zero-config "component styles" pattern. See trunk#3 for more details.
  • data-integrity: (optional) the integrity digest type for code & script resources. Defaults to plain sha384.
  • data-no-minify: (optional) Opt-out of minification. Also see: Minification.
  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...

tailwind

rel="tailwind-css": Trunk uses the official tailwindcss cli for compilation. Just link to your tailwind css files from your source HTML, and Trunk will handle the rest. This content is hashed for cache control. The href attribute must be included in the link pointing to the sass/scss file to be processed.

  • data-inline: (optional) this attribute will inline the compiled CSS from the tailwind compilation into a <style> tag instead of using a <link rel="stylesheet"> tag.
  • data-integrity: (optional) the integrity digest type for code & script resources. Defaults to plain sha384.
  • data-no-minify: (optional) Opt-out of minification. Also see: Minification.
  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...

icon

rel="icon": Trunk will copy the icon image specified in the href attribute to the dist dir. This content is hashed for cache control.

  • data-integrity: (optional) the integrity digest type for code & script resources. Defaults to plain sha384.
  • data-no-minify: (optional) Opt-out of minification. Also see: Minification.
  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...

inline

rel="inline": Trunk will inline the content of the file specified in the href attribute into index.html. This content is copied exactly, no hashing is performed.

  • type: (optional) – If not present, the type is inferred by the file extension.
    • html, svg
    • css: CSS wrapped in style tags
    • js: JavaScript wrapped in script tags
    • mjs, module: JavaScript wrapped in script tags with type="module"

copy-file

rel="copy-file": Trunk will copy the file specified in the href attribute to the dist dir. This content is copied exactly, no hashing is performed.

  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...

copy-dir

rel="copy-dir": Trunk will recursively copy the directory specified in the href attribute to the dist dir. This content is copied exactly, no hashing is performed.

  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...

Script Asset Types

Script assets are bit more diverse.

Script Assets

Classic script assets processed by Trunk must follow these three rules:

  • Must be declared as a valid HTML script tag.
  • Must have the attribute data-trunk.
  • Must have the attribute src, pointing to a script file

Attention

A valid HTML script tag always has an end tag (like <script></script>). A self-closing script tag (like <script />) is not avalid HTML script tag and will trigger a warning an may create a non-working HTML file.

This will typically look like: <script data-trunk src="{path}" ..other options here..></script>. All <script data-trunk ...></script> HTML elements will be replaced with the output HTML of the associated pipeline.

Trunk will copy script files found in the source HTML without content modification. This content is hashed for cache control. The src attribute must be included in the script pointing to the script file to be processed.

  • data-no-minify: (optional) Opt-out of minification. Also see: Minification.
  • data-target-path: (optional) Path where the output is placed inside the dist dir. If not present, the directory is placed in the dist root. The path must be a relative path without ...

JS Snippets

JS snippets generated from the wasm-bindgen JS snippets feature are automatically copied to the dist dir, hashed and ready to rock. No additional setup is required. Just use the feature in your application, and Trunk will take care of the rest.

Images & Other Resources

Images and other resource types can be copied into the dist dir by adding a link like this to your source HTML: <link data-trunk rel="copy-file" href="path/to/image"/>. Any normal file type is supported. This will cause Trunk to find the target resource, and copy it to the dist dir unmodified. No hashing will be applied. The link itself will be removed from the HTML. To copy an entire directory of assets/images, you can use the following HTML: <link data-trunk rel="copy-dir" href="path/to/images-dir"/>.

This will allow your WASM application to reference images directly from the dist dir, and Trunk will ensure that the images are available in the dist dir to be served.

Note

As Trunk continues to mature, we will find better ways to include images and other resources. Hashing content for cache control is great, we just need to find a nice pattern to work with images referenced in Rust components. Please contribute to the discussion over in trunk#9! See you there.

Directives

You can instruct Trunk to write the URL passed to --public-url to the HTML output by adding this to your <head>: <base data-trunk-public-url/>.

Trunk will set the href attribute of the element to the public URL. This changes the behavior of relative URLs to be relative to the public URL instead of the current location.

You can also access this value at runtime using document.baseURI which is useful for apps that need to know the base URL on which they're hosted (e.g. for routing).

Minification

Trunk supports minifying assets. This is disabled by default and can be controlled on various levels.

In any case, Trunk does not perform minification itself, but delegates the process to dependencies which do the actual implementation. In cases where minification breaks things, it will, most likely, be an issue with that dependency.

Starting with Trunk 0.20.0, minification is disabled by default. It can be turned on from the command line using the --minify (or -M) switch. Alternatively, it can be controlled using the build.minify field in the Trunk.toml file. The value of this field is an enum, with the following possible values: never (default, never minify), on_release (minify when running Trunk with --release), always (always minify).

When minification is enabled, all assets known to trunk (this excludes the copy-dir and copy-file opaque blobs to Trunk), will get minified. It is possible to opt out of this process on a per-asset basis using the data-no-minify attribute (see individual asset configuration). In this case, the asset will never get minified.

Sub-resource integrity

Trunk can automatically generate hashes of files and add the integrity attribute for resources fetched by the web application. This is enabled by default, but can be overridden using the data-integrity attribute. See the different asset types.

The following values are available:

  • none
  • sha256
  • sha384 (default)
  • sha512

Advanced

There are some more advanced topics, which will be described in the following subsections.

JavaScript interoperability

Trunk will create the necessary JavaScript code to bootstrap and run the WebAssembly based application. It will also include all JavaScript snippets generated by wasm-bindgen for interfacing with JavaScript functionality.

By default, functions exported from Rust, using wasm-bingen, can be accessed in the JavaScript code through the global variable window.wasmBindings. This behavior can be disabled, and the name can be customized. For more information see the rust asset type.

Order of initialization

The bindings will only be available and working when the application initialization has been completed.

If your WebAssembly application renders code into the web page/DOM tree, which then calls from JavaScript into the WebAssembly application, then this will not be an issue, as the application is already initialized.

However, if you want to call into the WebAssembly application from, for example, the index.html file itself, then you must delay that call until the application is started.

This can be ensured by executing that code with the TrunkApplicationStartup event. Also see Startup Event.

Startup event

The initializer code snippet of Trunk will emit an event when the WebAssembly application has been loaded and started.

Note

This event is independent of the initializer functionality.

Definition

The event is called TrunkApplicationStarted and is executed after the WebAssembly has been loaded and initialized.

The event will have custom details:

{
  wasm // The web assembly instance
}

Example

The following snippet can be used to run code after the initialization of the WebAssembly application:

<script type="module">
  addEventListener("TrunkApplicationStarted", (event) => {
  console.log("application started - bindings:", window.wasmBindings, "WASM:", event.detail.wasm);
  // wasm_ffi is a function exported from WASM to JavaScript
  window.wasmBindings.wasm_ffi();
  // You can also run this via the WASM instance in the details
  // event.detail.wasm.wasm_ffi();
});
</script>

Also see the vanilla example: https://github.com/trunk-rs/trunk/tree/main/examples/vanilla.

Initializer

Since: 0.19.0-alpha.1.

Trunk supports tapping into the initialization process of the WebAssembly application. By default, this is not active and works the same way as with previous versions.

The default process is that trunk injects a small JavaScript snippet, which imports the JavaScript loader generated by wasm_bindgen and calls the init method. That will fetch the WASM blob and run it.

The downside of this is, that during this process, there's no feedback for the user. Neither when it takes a bit longer to load the WASM file, nor when something goes wrong.

Now it is possible to tap into this process by setting data-initializer to a JavaScript module file. This module file is required to (default) export a function, which returns the "initializer" instance. Here is an example:

export default function myInitializer () {
  return {
    onStart: () => {
      // called when the loading starts
    },
    onProgress: ({current, total}) => {
      // the progress while loading, will be called periodically.
      // "current" will contain the number of bytes of the WASM already loaded
      // "total" will either contain the total number of bytes expected for the WASM, or if the server did not provide
      //   the content-length header it will contain 0.
    },
    onComplete: () => {
      // called when the initialization is complete (successfully or failed)
    },
    onSuccess: (wasm) => {
      // called when the initialization is completed successfully, receives the `wasm` instance
    },
    onFailure: (error) => {
      // called when the initialization is completed with an error, receives the `error`
    }
  }
};

For a full example, see: https://github.com/trunk-rs/trunk/tree/main/examples/initializer.

Library crate

Aside from having a main function, it is also possible to up your project as a cdylib project. In order to do that, add the following to your Cargo.toml:

[lib]
crate-type = ["cdylib", "rlib"]

And then, define the entrypoint in your lib.rs like (does not need to be async):

#![allow(unused)]
fn main() {
#[wasm_bindgen(start)]
pub async fn run() {}
}

Base URLs, public URLs, paths & reverse proxies

Since: 0.19.0-alpha.3.

Originally trunk had a single --public-url, which allowed to set the base URL of the hosted application. Plain and simple. This was a prefix for all URLs generated and acted as a base for trunk serve.

Unfortunately, life isn't that simple and naming is hard.

Today trunk was three paths:

  • The "public base URL": acting as a prefix for all generated URLs
  • The "serve base": acting as a scope/prefix for all things served by trunk serve
  • The "websocket base": acting as a base path for the auto-reload websocket

All three can be configured, but there are reasonable defaults in place. By default, the serve base and websocket base default to the absolute path of the public base. The public base will have a slash appended if it doesn't have one. The public base can be one of:

  • Unset/nothing/default (meaning /)
  • An absolute URL (e.g. http://domain/path/app)
  • An absolute path (e.g. /path/app)
  • A relative path (e.g. foo or ./)

If the public base is an absolute URL, then the path of that URL will be used as serve and websocket base. If the public base is a relative path, then it will be turned into an absolute one. Both approaches might result in a dysfunctional application, based on your environment. There will be a warning on the console. However, by providing an explicit value using serve-base or ws-base, this can be fixed.

Why is this necessary and when is it useful? It's mostly there to provide all the knobs/configurations for the case that weren't considered. The magic of public-url worked for many, but not for all. To support such cases, it is now possible to tweak all the settings, at the cost of more complexity. Having reasonable defaults should keep it simple for the simple cases.

An example use case is a reverse proxy in front of trunk serve, which can't be configured to serve the trunk websocket at the location trunk serve expects it. Now, it is possible to have --public-url to choose the base when generating links, so that it looks correct when being served by the proxy. But also use --serve-base / to keep serving resource from the root.

Backend Proxy

Trunk ships with a built-in proxy which can be enabled when running trunk serve. There are two ways to configure the proxy, each discussed below. All Trunk proxies will transparently pass along the request body, headers, and query parameters to the proxy backend.

Proxy CLI Flags

The trunk serve command accepts two proxy related flags.

--proxy-backend specifies the URL of the backend server to which requests should be proxied. The URI segment of the given URL will be used as the path on the Trunk server to handle proxy requests. E.G., trunk serve --proxy-backend=http://localhost:9000/api/ will proxy any requests received on the path /api/ to the server listening at http://localhost:9000/api/. Further path segments or query parameters will be seamlessly passed along.

--proxy-rewrite specifies an alternative URI on which the Trunk server is to listen for proxy requests. Any requests received on the given URI will be rewritten to match the URI of the proxy backend, effectively stripping the rewrite prefix. E.G., trunk serve --proxy-backend=http://localhost:9000/ --proxy-rewrite=/api/ will proxy any requests received on /api/ over to http://localhost:9000/ with the /api/ prefix stripped from the request, while everything following the /api/ prefix will be left unchanged.

--proxy-insecure allows the --proxy-backend url to use a self-signed certificate for https (or any officially invalid certs, including expired). This would be used when proxying to https such as trunk serve --proxy-backend=https://localhost:3001/ --proxy-insecure where the ssl cert was self-signed, such as with mkcert, and routed through an https reverse proxy for the backend, such as local-ssl-proxy or caddy.

--proxy-no-sytem-proxy bypasses the system proxy when contacting the proxy backend.

--proxy-ws specifies that the proxy is for a WebSocket endpoint.

Config File

The Trunk.toml config file accepts multiple [[proxy]] sections, which allows for multiple proxies to be configured. Each section requires at least the backend field, and optionally accepts the rewrite and ws fields, both corresponding to the --proxy-* CLI flags discussed above.

As it is with other Trunk config, a proxy declared via CLI will take final precedence and will cause any config file proxies to be ignored, even if there are multiple proxies declared in the config file.

The following is a snippet from the Trunk.toml file in the Trunk repo:

[[proxy]]
rewrite = "/api/v1/"
backend = "http://localhost:9000/"

Contributing

Anyone and everyone is welcome to contribute! Please review the CONTRIBUTING.md document for more details. The best way to get started is to find an open issue, and then start hacking on implementing it. Letting other folks know that you are working on it, and sharing progress is a great approach. Open pull requests early and often, and please use GitHub's draft pull request feature.