Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion component-model/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ title = "The WebAssembly Component Model"
[output.html]
git-repository-url = "https://github.com/bytecodealliance/component-docs/tree/main/component-model"
edit-url-template = "https://github.com/bytecodealliance/component-docs/tree/main/component-model/{path}"
additional-css = ["theme/head.hbs"]
additional-css = ["theme/head.hbs", "theme/tabs.css", "theme/tabs-overrides.css", "theme/version-notice.css"]
additional-js = ["theme/tabs.js", "theme/tabs-url-sync.js", "theme/tabs-a11y.js"]

[output.html.fold]
enable = true
Expand All @@ -34,4 +35,6 @@ level = 1

[preprocessor.alerts]

[preprocessor.tabs]

[output.linkcheck]
33 changes: 29 additions & 4 deletions component-model/src/running-components/wasmtime.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
# Wasmtime

<div class="version-notice">

This page has content for both **WASI P2** and **WASI P3**. Use the tabs below to switch between versions where they differ.

</div>

{{#tabs global="wasi-version" }}
{{#tab name="WASI P2" }}
[Wasmtime](https://github.com/bytecodealliance/wasmtime/) is the reference implementation of the Component Model.
It supports running components that implement the [`wasi:cli/command` world](https://github.com/WebAssembly/wasi-cli/blob/main/wit/command.wit)
and serving components that implement the [`wasi:http/proxy` world](https://github.com/WebAssembly/wasi-http/blob/main/wit/proxy.wit).
It supports running components that implement the [`wasi:cli/command` world](https://github.com/WebAssembly/WASI/blob/main/proposals/cli/wit/command.wit)
and serving components that implement the [`wasi:http/proxy` world](https://github.com/WebAssembly/WASI/blob/main/proposals/http/wit/proxy.wit).
Wasmtime can also invoke functions exported from a component.
{{#endtab }}
{{#tab name="WASI P3" }}
[Wasmtime](https://github.com/bytecodealliance/wasmtime/) is the reference implementation of the Component Model. WASI P3 runtime support is available in Wasmtime 43 and later, which implements the WASI 0.3 ABI (`async func`, `stream<T>`, `future<T>`).

It supports running components that implement the [`wasi:cli/command` world](https://github.com/WebAssembly/WASI/blob/main/proposals/cli/wit-0.3.0-draft/command.wit). Support for serving components in the [`wasi:http/service` world](https://github.com/WebAssembly/WASI/blob/main/proposals/http/wit-0.3.0-draft/worlds.wit) and the new [`wasi:http/middleware` world](https://github.com/WebAssembly/WASI/blob/main/proposals/http/wit-0.3.0-draft/worlds.wit), which both imports and exports the HTTP handler interface, is in development. See [the tracking issue](https://github.com/bytecodealliance/wit-bindgen/issues/1554) for current status.
Wasmtime can also invoke functions exported from a component.
{{#endtab }}
{{#endtabs }}

## Running command components with Wasmtime
To run a command component with Wasmtime, execute:
Expand All @@ -21,8 +37,10 @@ See the [Wasmtime guide](https://docs.wasmtime.dev/) for information on granting

## Running HTTP components with Wasmtime

You can execute components that implement the [HTTP proxy world](https://github.com/WebAssembly/wasi-http/blob/main/wit/proxy.wit) with the `wasmtime serve` subcommand.
[The Wasmtime CLI](https://github.com/bytecodealliance/wasmtime) supports serving these components as of `v14.0.3`.
{{#tabs global="wasi-version" }}
{{#tab name="WASI P2" }}
You can execute components that implement the [HTTP proxy world](https://github.com/WebAssembly/WASI/blob/main/proposals/http/wit/proxy.wit) with the `wasmtime serve` subcommand.
[The Wasmtime CLI](https://github.com/bytecodealliance/wasmtime) supports serving these components as of `v18.0.0`.

To run a HTTP component with Wasmtime, execute:
```sh
Expand All @@ -34,6 +52,13 @@ Try out building and running HTTP components with one of these tutorials
1. [Hello WASI HTTP tutorial](https://github.com/sunfishcode/hello-wasi-http) - build and serve a simple Rust-based HTTP component

2. [HTTP Auth Middleware tutorial](https://github.com/fermyon/http-auth-middleware#running-with-wasmtime) - compose a HTTP authentication middleware component with a business logic component
{{#endtab }}
{{#tab name="WASI P3" }}
Wasmtime 43 and later provide runtime support for the WASI P3 ABI. The `wasmtime serve` subcommand currently targets the [`wasi:http/proxy` world](https://github.com/WebAssembly/WASI/blob/main/proposals/http/wit/proxy.wit) from WASI P2; support for serving components in the WASI P3 [`wasi:http/service`](https://github.com/WebAssembly/WASI/blob/main/proposals/http/wit-0.3.0-draft/worlds.wit) and [`wasi:http/middleware`](https://github.com/WebAssembly/WASI/blob/main/proposals/http/wit-0.3.0-draft/worlds.wit) worlds is in progress. See [the tracking issue](https://github.com/bytecodealliance/wit-bindgen/issues/1554) for current status.

For an overview of the WASI P3 HTTP interfaces, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev.
{{#endtab }}
{{#endtabs }}

## Running components with custom exports

Expand Down
114 changes: 114 additions & 0 deletions component-model/theme/tabs-a11y.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
(function () {
'use strict';

// Layer ARIA semantics and keyboard navigation onto the mdbook-tabs
// plugin's markup. The plugin emits plain <button> / <div> elements
// without tab/tablist/tabpanel roles; this script fills that gap.

let uidCounter = 0;
function nextUid() {
uidCounter += 1;
return uidCounter;
}

function initContainer(container) {
const nav = container.querySelector('.mdbook-tabs');
if (nav && !nav.hasAttribute('role')) {
nav.setAttribute('role', 'tablist');
}

const tabs = Array.from(container.querySelectorAll('.mdbook-tab'));
const panes = Array.from(container.querySelectorAll('.mdbook-tab-content'));

tabs.forEach(function (tab) {
if (tab.hasAttribute('role')) return;
const uid = nextUid();
const tabId = 'mdbook-tab-' + uid;
const paneId = 'mdbook-tabpanel-' + uid;

tab.setAttribute('role', 'tab');
tab.id = tabId;

const pane = panes.find(function (p) {
return p.dataset.tabname === tab.dataset.tabname;
});
if (pane) {
pane.setAttribute('role', 'tabpanel');
pane.id = paneId;
if (!pane.hasAttribute('tabindex')) {
pane.setAttribute('tabindex', '0');
}
tab.setAttribute('aria-controls', paneId);
pane.setAttribute('aria-labelledby', tabId);
}
});
}

function syncContainer(container) {
container.querySelectorAll('.mdbook-tab').forEach(function (tab) {
const isActive = tab.classList.contains('active');
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
// Roving tabindex: only the active tab is reachable via Tab key;
// arrow keys move focus within the tablist.
tab.setAttribute('tabindex', isActive ? '0' : '-1');
});
container.querySelectorAll('.mdbook-tab-content').forEach(function (pane) {
pane.setAttribute('aria-hidden', pane.classList.contains('hidden') ? 'true' : 'false');
});
}

function syncAll() {
document.querySelectorAll('.mdbook-tabs-container').forEach(syncContainer);
}

function handleKeyDown(event) {
const tab = event.target.closest && event.target.closest('.mdbook-tab');
if (!tab) return;
const nav = tab.parentElement;
if (!nav || !nav.classList.contains('mdbook-tabs')) return;

const tabs = Array.from(nav.querySelectorAll('.mdbook-tab'));
const currentIndex = tabs.indexOf(tab);
if (currentIndex < 0) return;

let newIndex = null;
switch (event.key) {
case 'ArrowRight':
case 'Right':
newIndex = (currentIndex + 1) % tabs.length;
break;
case 'ArrowLeft':
case 'Left':
newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
break;
case 'Home':
newIndex = 0;
break;
case 'End':
newIndex = tabs.length - 1;
break;
}
if (newIndex === null) return;

event.preventDefault();
tabs[newIndex].focus();
// Automatic activation: mirrors common tab widgets (Bootstrap, Radix,
// etc.) where focusing a tab also selects it.
tabs[newIndex].click();
}

document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.mdbook-tabs-container').forEach(initContainer);
syncAll();

document.addEventListener('keydown', handleKeyDown);

// Re-sync ARIA state after every click. Clicks may propagate across
// multiple containers when a global="..." state is shared, so we
// resync every container, not just the one that was clicked.
document.addEventListener('click', function (event) {
if (!event.target.closest || !event.target.closest('.mdbook-tab')) return;
syncAll();
});
});
})();
68 changes: 68 additions & 0 deletions component-model/theme/tabs-overrides.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* Custom overrides to the mdbook-tabs plugin's default styling.
Kept separate from theme/tabs.css so plugin updates (via
`mdbook-tabs install`) do not overwrite these rules. */

/* Container: left rail marking version-specific content. */
.mdbook-tabs-container {
border-left: 3px solid var(--sidebar-active, var(--table-border-color));
padding-left: 1.25rem;
}

/* Nav: thin baseline below the tab row. */
.mdbook-tabs {
border-bottom: 1px solid var(--table-border-color);
}

/* Tab button: explicit foreground color and transparent active
indicator so hover and active states have somewhere to land. */
.mdbook-tab {
color: var(--fg);
border-bottom: 2px solid transparent;
}

@media (prefers-reduced-motion: no-preference) {
.mdbook-tab {
transition: color 120ms ease, border-color 120ms ease;
}
}

.mdbook-tab:hover:not(.active) {
color: var(--sidebar-active, var(--fg));
}

.mdbook-tab.active {
color: var(--sidebar-active, var(--fg));
border-bottom-color: var(--sidebar-active, var(--fg));
}

/* Rust theme: --sidebar-active (#e69f67) has low contrast against the
warm tan tab backgrounds. Fall back to --fg for the label text; the
accent still carries the active signal via the bottom border. */
.rust .mdbook-tab.active {
color: var(--fg);
}

.rust .mdbook-tab:hover:not(.active) {
color: var(--fg);
border-bottom-color: var(--sidebar-active, var(--fg));
}

/* Print: show every tab pane with a heading-like label, and hide the
interactive nav since clicking has no meaning on paper. */
@media print {
.mdbook-tabs {
display: none;
}

.mdbook-tab-content.hidden {
display: block;
}

.mdbook-tab-content::before {
content: attr(data-tabname);
display: block;
font-size: 1.3em;
font-weight: bold;
margin: 1em 0 0.5em;
}
}
75 changes: 75 additions & 0 deletions component-model/theme/tabs-url-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
(function () {
'use strict';

const GLOBAL_NAME = 'wasi-version';
const PARAM_NAME = 'wasi';
const SLUG_TO_NAME = { p2: 'WASI P2', p3: 'WASI P3' };
const NAME_TO_SLUG = { 'WASI P2': 'p2', 'WASI P3': 'p3' };

function readSlugFromUrl() {
const value = new URLSearchParams(window.location.search).get(PARAM_NAME);
return value && SLUG_TO_NAME[value] ? value : null;
}

function writeSlugToUrl(slug) {
const url = new URL(window.location.href);
url.searchParams.set(PARAM_NAME, slug);
history.replaceState(null, '', url.toString());
}

function activateByName(name) {
const tab = document.querySelector(
'.mdbook-tabs-container[data-tabglobal="' + GLOBAL_NAME + '"] ' +
'.mdbook-tab[data-tabname="' + name + '"]'
);
if (tab) {
tab.click();
}
}

// If `element` lives inside a tab pane, activate that pane's tab.
// Returns true if a tab was activated.
function activateTabContainingElement(element) {
const pane = element.closest && element.closest('.mdbook-tab-content');
if (!pane) return false;
const container = pane.closest('.mdbook-tabs-container');
if (!container) return false;
const name = pane.dataset.tabname;
if (!name) return false;
const tab = container.querySelector('.mdbook-tab[data-tabname="' + name + '"]');
if (!tab) return false;
tab.click();
return true;
}

document.addEventListener('DOMContentLoaded', function () {
// Apply URL-param tab selection first.
const slug = readSlugFromUrl();
if (slug) {
activateByName(SLUG_TO_NAME[slug]);
}

// If the page was loaded with a hash pointing to an element inside a
// hidden tab pane, activate that pane and re-scroll. Hash wins over
// the ?wasi= param since the hash targets a specific element.
if (window.location.hash.length > 1) {
const targetId = decodeURIComponent(window.location.hash.slice(1));
const target = document.getElementById(targetId);
if (target && activateTabContainingElement(target)) {
target.scrollIntoView();
}
}

// Reflect wasi-version tab clicks back into the URL.
document.addEventListener('click', function (event) {
const tab = event.target.closest && event.target.closest('.mdbook-tab');
if (!tab) return;
const container = tab.closest('.mdbook-tabs-container');
if (!container || container.dataset.tabglobal !== GLOBAL_NAME) return;
const newSlug = NAME_TO_SLUG[tab.dataset.tabname];
if (newSlug) {
writeSlugToUrl(newSlug);
}
});
});
})();
25 changes: 25 additions & 0 deletions component-model/theme/tabs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.mdbook-tabs {
display: flex;
}

.mdbook-tab {
background-color: var(--table-alternate-bg);
padding: 0.5rem 1rem;
cursor: pointer;
border: none;
font-size: 1.6rem;
line-height: 1.45em;
}

.mdbook-tab.active {
background-color: var(--table-header-bg);
font-weight: bold;
}

.mdbook-tab-content {
padding: 1rem 0rem;
}

.mdbook-tab-content table {
margin: unset;
}
Loading
Loading