From fb29cd0cb3801eab0ba411c6f99670cc569630a5 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Wed, 13 Aug 2025 13:00:27 -0700 Subject: [PATCH 1/8] Revise JavaScript language support section As with other sections, added more details and factored out code examples into other files. * Fixed two errors in `jco componentize` command The world name was wrong in one of the commands, and "--disable all" should be "--disable=all" (at least with a recent jco version) * Added omitted steps of creating package.json files (without which we get "Cannot use import statement outside a module" error) * Fixed missing semicolon in component.wit file for string-reverse example * Corrected string-reverse example to use split() and join() (the example didn't run before) * Corrected string-reverse-uppercase example to declare reverseAndUppercase(s) rather than reverseAndUppercase() (this example also didn't run before). --- .../examples/tutorial/js/adder/adder.js | 5 + .../examples/tutorial/js/adder/package.json | 5 + .../examples/tutorial/js/adder/run.js | 3 + .../js/string-reverse-upper/component.wit | 16 + .../tutorial/js/string-reverse-upper/run.js | 12 + .../string-reverse-upper.mjs | 26 + .../tutorial/js/string-reverse/component.wit | 10 + .../tutorial/js/string-reverse/package.json | 5 + .../tutorial/js/string-reverse/run.js | 6 + .../js/string-reverse/string-reverse.mjs | 19 + .../src/language-support/javascript.md | 480 +++++++++--------- 11 files changed, 352 insertions(+), 235 deletions(-) create mode 100644 component-model/examples/tutorial/js/adder/adder.js create mode 100644 component-model/examples/tutorial/js/adder/package.json create mode 100644 component-model/examples/tutorial/js/adder/run.js create mode 100644 component-model/examples/tutorial/js/string-reverse-upper/component.wit create mode 100644 component-model/examples/tutorial/js/string-reverse-upper/run.js create mode 100644 component-model/examples/tutorial/js/string-reverse-upper/string-reverse-upper.mjs create mode 100644 component-model/examples/tutorial/js/string-reverse/component.wit create mode 100644 component-model/examples/tutorial/js/string-reverse/package.json create mode 100644 component-model/examples/tutorial/js/string-reverse/run.js create mode 100644 component-model/examples/tutorial/js/string-reverse/string-reverse.mjs diff --git a/component-model/examples/tutorial/js/adder/adder.js b/component-model/examples/tutorial/js/adder/adder.js new file mode 100644 index 00000000..d219abb0 --- /dev/null +++ b/component-model/examples/tutorial/js/adder/adder.js @@ -0,0 +1,5 @@ +export const add = { + add(x, y) { + return x + y; + } +}; diff --git a/component-model/examples/tutorial/js/adder/package.json b/component-model/examples/tutorial/js/adder/package.json new file mode 100644 index 00000000..0c191f7b --- /dev/null +++ b/component-model/examples/tutorial/js/adder/package.json @@ -0,0 +1,5 @@ +{ + "name": "adder-wasm", + "description": "Simple codebase for compiling an add interface to WebAssembly with jco", + "type": "module" +} diff --git a/component-model/examples/tutorial/js/adder/run.js b/component-model/examples/tutorial/js/adder/run.js new file mode 100644 index 00000000..c9d95791 --- /dev/null +++ b/component-model/examples/tutorial/js/adder/run.js @@ -0,0 +1,3 @@ +import { add } from "./dist/transpiled/adder.js"; + +console.log("1 + 2 = " + add.add(1, 2)); diff --git a/component-model/examples/tutorial/js/string-reverse-upper/component.wit b/component-model/examples/tutorial/js/string-reverse-upper/component.wit new file mode 100644 index 00000000..1084e6b0 --- /dev/null +++ b/component-model/examples/tutorial/js/string-reverse-upper/component.wit @@ -0,0 +1,16 @@ +package example:string-reverse-upper@0.1.0; + +@since(version = 0.1.0) +interface reversed-upper { + reverse-and-uppercase: func(s: string) -> string; +} + +world revup { + // + // NOTE, the import below translates to: + // :/@ + // + import example:string-reverse/reverse@0.1.0; + + export reversed-upper; +} diff --git a/component-model/examples/tutorial/js/string-reverse-upper/run.js b/component-model/examples/tutorial/js/string-reverse-upper/run.js new file mode 100644 index 00000000..47999324 --- /dev/null +++ b/component-model/examples/tutorial/js/string-reverse-upper/run.js @@ -0,0 +1,12 @@ +/** + * If this import listed below is missing, please run + * + * ``` + * npm run build && npm run compose && npm run transpile` + * ``` + */ +import { reversedUpper } from "./dist/transpiled/string-reverse-upper.js"; + +const result = reversedUpper.reverseAndUppercase("!dlroW olleH"); + +console.log(`reverseAndUppercase('!dlroW olleH') = ${result}`); diff --git a/component-model/examples/tutorial/js/string-reverse-upper/string-reverse-upper.mjs b/component-model/examples/tutorial/js/string-reverse-upper/string-reverse-upper.mjs new file mode 100644 index 00000000..c938d7aa --- /dev/null +++ b/component-model/examples/tutorial/js/string-reverse-upper/string-reverse-upper.mjs @@ -0,0 +1,26 @@ +/** + * This module is the JS implementation of the `revup` WIT world + */ + +/** + * The import here is *virtual*. It refers to the `import`ed `reverse` interface in component.wit. + * + * These types *do not resolve* when the first `string-reverse-upper` component is built, + * but the types are relevant for the resulting *composed* component. + */ +import { reverseString } from 'example:string-reverse/reverse@0.1.0'; + +/** + * The JavaScript export below represents the export of the `reversed-upper` interface, + * which which contains `revup` as it's primary exported function. + */ +export const reversedUpper = { + /** + * Represents the implementation of the `reverse-and-uppercase` function in the `reversed-upper` interface + * + * This function makes use of `reverse-string` which is *imported* from another WebAssembly binary. + */ + reverseAndUppercase(s) { + return reverseString(s).toLocaleUpperCase(); + }, +}; diff --git a/component-model/examples/tutorial/js/string-reverse/component.wit b/component-model/examples/tutorial/js/string-reverse/component.wit new file mode 100644 index 00000000..577ee5df --- /dev/null +++ b/component-model/examples/tutorial/js/string-reverse/component.wit @@ -0,0 +1,10 @@ +package example:string-reverse@0.1.0; + +@since(version = 0.1.0) +interface reverse { + reverse-string: func(s: string) -> string; +} + +world string-reverse { + export reverse; +} diff --git a/component-model/examples/tutorial/js/string-reverse/package.json b/component-model/examples/tutorial/js/string-reverse/package.json new file mode 100644 index 00000000..2e976ea5 --- /dev/null +++ b/component-model/examples/tutorial/js/string-reverse/package.json @@ -0,0 +1,5 @@ +{ + "name": "string-reverse", + "description": "Simple codebase for compiling a string reversing interface to WebAssembly with jco", + "type": "module" +} diff --git a/component-model/examples/tutorial/js/string-reverse/run.js b/component-model/examples/tutorial/js/string-reverse/run.js new file mode 100644 index 00000000..ea077205 --- /dev/null +++ b/component-model/examples/tutorial/js/string-reverse/run.js @@ -0,0 +1,6 @@ +// If this import listed below is missing, please run `npm run transpile` +import { reverse } from "./dist/transpiled/string-reverse.js"; + +const reversed = reverse.reverseString("!dlroW olleH"); + +console.log(`reverseString('!dlroW olleH') = ${reversed}`); diff --git a/component-model/examples/tutorial/js/string-reverse/string-reverse.mjs b/component-model/examples/tutorial/js/string-reverse/string-reverse.mjs new file mode 100644 index 00000000..cb766321 --- /dev/null +++ b/component-model/examples/tutorial/js/string-reverse/string-reverse.mjs @@ -0,0 +1,19 @@ +/** + * This module is the JS implementation of the `string-reverse` WIT world + */ + +/** + * The JavaScript export below represents the export of the `reverse` interface, + * which which contains `reverse-string` as its primary exported function. + */ +export const reverse = { +/** + * This JavaScript will be interpreted by `jco` and turned into a + * WebAssembly binary with a single export (this `reverse` function). + */ + reverseString(s) { + return s.split("") + .reverse() + .join(""); + } +}; diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index 2390306f..fbec4182 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -5,18 +5,20 @@ workloads in the browser at near-native speed. JavaScript WebAssembly component model support is provided by a combination of tools: -- [StarlingMonkey][sm] a WebAssembly component aware Javascript engine -- [`componentize-js`][componentize-js] a tool for building WebAssembly components from Javascript files -- [`jco`][jco] a multi-tool for componentizing, type generation, and running components in NodeJS and browser contexts +- [StarlingMonkey][sm], a WebAssembly component-aware JavaScript engine +- [`componentize-js`][componentize-js], a tool for building WebAssembly components from JavaScript files +- [`jco`][jco], a multi-tool for componentizing, type generation, and running components in Node.js and browser contexts -Note that [Typescript][ts] can *also* be used, given that it is transpiled to JS first by relevant tooling (`tsc`). -`jco` generates [type declaration files (`.d.ts`)][ts-decl-file] by default, and also has a `jco types` -subcommand which generates typings that can be used with a Typescript codebase. +Note that [TypeScript][ts] can *also* be used, given that it is transpiled to JS first by relevant tooling (`tsc`). +`jco` generates [type declaration files (`.d.ts`)][ts-decl-file] by default, +and also has a `jco types` subcommand which generates typings that can be used with a TypeScript codebase. > [!WARNING] -> While popular projects like [`emscripten`][emscripten] also build WebAssembly modules, those modules are not Component Model aware. +> While popular projects like [`emscripten`][emscripten] also build WebAssembly modules, +> those modules are not Component Model aware. > -> Core WebAssembly modules do not contain the advanced features (rich types, structured language interoperation, composition) +> Core WebAssembly modules do not contain the advanced features +> (rich types, structured language interoperation, composition) > that the component model makes available. [emscripten]: https://emscripten.org/ @@ -28,87 +30,95 @@ subcommand which generates typings that can be used with a Typescript codebase. ## Installing `jco` -Installing [`jco`][jco] (which uses [`componentize-js`][componentize-js] can be done via standard NodeJS project tooling: +[`jco`][jco] (which uses [`componentize-js`][componentize-js] can be installed through +the Node Package Manager (`npm`): ```console npm install -g @bytecodealliance/jco ``` > [!NOTE] -> `jco` and `componentize-js` can be installed in a project-local manner with `npm install -D` +> `jco` and `componentize-js` can be installed in a project-local manner with `npm install -D`. ## Overview of Building a Component with JavaScript Building a WebAssembly component with JavaScript often consists of: -1. Determining which interface our functionality will target (i.e. a [WebAssembly Interface Types ("WIT")][wit] world) -2. Writing JavaScript that satisfies the interface +1. Determining which interface our component will target (i.e. a [WebAssembly Interface Types ("WIT")](../design/wit.md) world) +2. Creating the component by writing JavaScript that satisfies the interface 3. Compiling the interface-compliant JavaScript to WebAssembly ### What is WIT? -[WebAssembly Interface Types ("WIT")][wit] is a featureful Interface Definition Language ("IDL") for defining -functionality, but most of the time, you shouldn't need to write WIT from scratch. Often, it's sufficient to -download a pre-existing interface that defines what your component should do. +[WebAssembly Interface Types ("WIT")](../design/wit.md) is a featureful Interface Definition Language ("IDL") +for defining functionality, but most of the time, you shouldn't need to write WIT from scratch. +Often, it's sufficient to download a pre-existing interface that defines what your component should do. -The [`adder` world][adder-world] contains an interface with a single `add` function that sums two numbers: +The [`adder` world][adder-world] +contains an interface with a single `add` function that sums two numbers. +Create a new directory called `adder` and paste the following WIT code +into a file called `world.wit`. ```wit {{#include ../../examples/tutorial/wit/adder/world.wit}} ``` -> [!NOTE] -> `export`ing the `add` interface means that environments that interact with the resulting WebAssembly component -> will be able to *call* the `add` function (fully qualified: `docs:adder/add.add@0.1.0`). -> -> To learn more about the WIT syntax, check out the [WIT explainer][wit-explainer] +The `export add;` declaration inside the `adder` world means that +environments that interact with the resulting WebAssembly component +will be able to _call_ the `add` function. +Its name must be fully qualified: `docs:adder/add.add@0.1.0`. +The parts of this name are: +* `docs:adder` is the package name. +* `add` is the name of the interface containing the `add` function. +* `add` also happens to be the name of the function itself. +* `@0.1.0` is a version number that must match the declared version number. + +> To learn more about the WIT syntax, check out the [WIT explainer](../design/wit.md). -[adder-world]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit +[adder-world]: https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/tutorial/wit/adder/world.wit [wit-example-world]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit -[wit-explainer]: https://component-model.bytecodealliance.org/design/wit.html ## Implementing a JS WebAssembly Component -To implement the [`adder` world][adder-world], we can write a [JavaScript ES module][mdn-js-module]: +To implement the [`adder` world][adder-world], we can write a [JavaScript ES module][mdn-js-module]. +Paste the following code into a file called `adder.js` in your `adder` directory: ```js -export const add = { - add(x, y) { - return x + y; - } -}; +{{#include ../../examples/tutorial/js/adder/adder.js}} ``` > [!WARNING] -> When building your JavaScript project, ensure to set the `"type":"module"` option in `package.json`, +> If you create a JavaScript project using this file, +> make sure you set the [`"type":"module"` option][package-json] in `package.json`, > as `jco` works exclusively with JavaScript modules. In the code above: -- The `adder` world is analogous to the JavaScript module (file) itself -- The exported `add` object mirrors the `export`ed `add` interface in WIT -- The `add` function mirrors the `add` function inside the `add` interface +- The JavaScript module (file) itself is analogous to the `adder` world +- The exported `add` object corresponds to the `export`ed `add` interface in WIT +- The `add` function defined inside the `add` object corresponds to + the `add` function inside the `add` interface With the WIT and JavaScript in place, we can use [`jco`][jco] to create a WebAssembly component from the JS module, using `jco componentize`. > [!NOTE] -> You can also call `componentize-js` directly -- it supports both API driven usage and has a CLI. +> You can also call [`componentize-js`][componentize-js] directly—it can be used +> both through an API and through the command line. -Our component is *so simple* (reminiscent of [Core WebAssembly][wasm-core], which deals only in numeric -values) that we're actually *not using* any of the [WebAssembly System Interface][wasi] (access to files, network, etc). -This means that we can `--disable` all unneeded WASI functionality when we invoke `jco componentize`: +Our component is *so simple* (reminiscent of [Core WebAssembly][wasm-core], which deals only in numeric values) +that we're actually *not using* any of the [WebAssembly System Interface][wasi] functionality +(access to files, networking, and other system capabilities). +This means that we can `--disable` all unneeded WASI functionality when we invoke `jco componentize`. + +Inside your `adder` directory, execute: ```console -jco componentize \ - --wit path/to/adder/world.wit \ - --world-name example \ - --out adder.wasm \ - --disable all \ - path/to/adder.js +jco componentize --wit world.wit --world-name adder --out adder.wasm + --disable=all adder.js ``` > [!NOTE] -> If you're using `jco` as a project-local dependency, you can run `npx jco` +> If you're using `jco` as a project-local dependency, you can run `npx jco`. You should see output like the following: @@ -116,36 +126,46 @@ You should see output like the following: OK Successfully written adder.wasm. ``` +You should now have an `adder.wasm` file in your `adder` directory. +You can verify that this file contains a component with: + +```bash +$ wasm-tools print adder.wasm |head -1 +(component +``` + > [!WARNING] -> By using `--disable all`, your component won't get access to any WASI interfaces that +> By using `--disable=all`, your component won't get access to any WASI interfaces that > might be useful for debugging or logging. > -> For example, you can't `console.log(...)` or `console.error(...)` without `stdio`; you -> can't use `Math.random()` without `random`; and you can't use `Date.now()` or `new Date()` -> without `clocks`. +> For example, you can't `console.log(...)` or `console.error(...)` without `stdio`; +> you can't use `Math.random()` without `random`; +> and you can't use `Date.now()` or `new Date()` without `clocks`. > > Please note that calls to `Math.random()` or `Date.now()` will return seemingly valid > outputs, but without actual randomness or timestamp correctness. [mdn-js-module]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules +[package-json]: https://nodejs.org/api/packages.html#type ## Running the Component in the `example-host` +> [!NOTE] +> The [`example-host` Rust project][example-host] uses the [Rust toolchain][rust-toolchain], +> in particular [`cargo`][cargo], +> so to run the code in this section you may need to install some more dependencies (like the Rust toolchain). + To run the component we've built, we can use the [`example-host` project][example-host]: -```console -cd component-model/examples/example-host -cargo run --release -- 1 2 ../path/to/adder.wasm -1 + 2 = 3 -``` +{{#include example-host-part1.md}} -> [!WARNING] -> The [`example-host` Rust project][example-host] uses the [Rust toolchain][rust-toolchain], in particular [`cargo`][cargo], -> so to run the code in this section you may need to install some more dependencies (like the Rust toolchain). +The output looks like: + +{{#include example-host-part2.md}} While the output isn't exciting, the code contained in `example-host` does a lot to make it happen: -- Loads the WebAssembly binary at the provided path (in the command above, `../path/to/adder.wasm`) +- Loads the WebAssembly binary at the provided path (in the command above, `/path/to/adder.wasm`) - Calls the `export`ed `add` function inside the `add` interface with arguments - Prints the result @@ -164,13 +184,12 @@ instance .context("Failed to call add function") ``` -A quick reminder on the power and new capabilities afforded by WebAssembly -- we've written, loaded, -instantiated and executed JavaScript from Rust with a strict interface, without the need -for [FFI][ffi], subprocesses or a network call. +A quick reminder on the power and new capabilities afforded by WebAssembly: +we've written, loaded, instantiated and executed JavaScript from Rust with a strict interface, +without the need for [foreign function interfaces][ffi], subprocesses or a network call. [rust-toolchain]: https://www.rust-lang.org/tools/install [example-host]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host -[wit]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md [nodejs]: https://nodejs.org/en [cargo]: https://doc.rust-lang.org/cargo [wasi]: https://wasi.dev/ @@ -179,13 +198,14 @@ for [FFI][ffi], subprocesses or a network call. ## Running a Component from JavaScript Applications (including the Browser) -While JavaScript runtimes available in browsers can execute WebAssembly core modules, they cannot yet -execute WebAssembly *components*, so WebAssembly components (JavaScript or otherwise) must be "transpiled" -into a JavaScript wrapper and one or more [WebAssembly core modules][wasm-core-module] which *can* be run by -available WebAssembly engines. +While JavaScript runtimes available in browsers can execute WebAssembly core modules, +they cannot yet execute WebAssembly *components*, so WebAssembly components (JavaScript or otherwise) +must be "transpiled" into a JavaScript wrapper and one or more [WebAssembly core modules][wasm-core-module] +which *can* be run by browsers. -Given an existing WebAssembly component (e.g. `adder.wasm` which implements the [`adder` world][adder-world]), we can -"transpile" the WebAssembly component into runnable JavaScript by using `jco tranpsile`: +Given an existing WebAssembly component (e.g. `adder.wasm` which implements the [`adder` world][adder-world]), +we can transpile the WebAssembly component into runnable JavaScript by using `jco transpile`. +In your `adder` directory, execute: ```console jco transpile adder.wasm -o dist/transpiled @@ -194,31 +214,47 @@ jco transpile adder.wasm -o dist/transpiled You should see output similar to the following: ``` - Transpiled JS Component Files: + Transpiled JS Component Files: - - dist/transpiled/adder.core.wasm 10.1 MiB - - dist/transpiled/adder.d.ts 0.1 KiB - - dist/transpiled/adder.js 1.57 KiB + - dist/transpiled/adder.core.wasm 10.6 MiB + - dist/transpiled/adder.d.ts 0.11 KiB + - dist/transpiled/adder.js 21.1 KiB + - dist/transpiled/interfaces/docs-adder-add.d.ts 0 ``` > [!NOTE] -> To follow along, see the [`jco` example `adder` component](https://github.com/bytecodealliance/jco/tree/main/examples/components/adder). +> For a complete project containing JS and WIT files similar to the ones you already created, +> see the [`jco` example `adder` component][jco-example]. > -> With the project pulled locally, you also run `npm run transpile` which outputs to `dist/transpiled` +> With this project pulled locally, you also run `npm run transpile`, which outputs to `dist/transpiled`. -Thanks to `jco` transpilation, you can import the resulting `dist/transpiled/adder.js` file and run it from any JavaScript application +Thanks to `jco` transpilation, you can import the resulting `dist/transpiled/adder.js` file +and run it from any JavaScript application using a runtime that supports the [core WebAssembly specification][core-wasm] as implemented for JavaScript. -To use this component from [NodeJS][nodejs], you can write code like the following: +To use this component from [Node.js][nodejs], you can write code like the following: ```mjs -import { add } from "./dist/transpiled/adder.js"; +{{#include ../../examples/tutorial/js/adder/run.js}} +``` + +Pasting this code into a file called `run.js` in your `adder` directory, +you can execute the JavaScript module with `node` directly. +First, you will need to create a `package.json` file +in the same directory: -console.log("1 + 2 = " + add.add(1, 2)); +```json +{{#include ../../examples/tutorial/js/adder/package.json}} ``` -You can execute the JavaScript module with `node` directly: +> [!NOTE] +> Without creating the `package.json` file, or if you omit the `"type": "module"` property, +> you will see an error message like: +> +> `SyntaxError: Cannot use import statement outside a module`. + +Then you can run the module with: ```console node run.js @@ -230,117 +266,105 @@ You should see output like the following: 1 + 2 = 3 ``` -This is directly comparable to the Rust host code mentioned in the previous section. Here, we are able to -use NodeJS as a host for running WebAssembly, thanks to `jco`'s ability to transpile components. +This is directly comparable to the Rust host code mentioned in the previous section. +Here, we are able to use Node.js as a host for running WebAssembly, +thanks to `jco`'s ability to transpile components. -With `jco transpile` any WebAssembly binary (compiled from any language) can be run in JavaScript natively. +With `jco transpile`, any WebAssembly binary (compiled from any language) can be run natively in JavaScript. +[jco-example]: https://github.com/bytecodealliance/jco/tree/main/examples/components/adder [wasm-core-module]: https://webassembly.github.io/spec/core/binary/modules.html [core-wasm]: https://webassembly.github.io/spec/core/ ## Building Reactor Components with `jco` -Reactor components are WebAssembly components that are long running and meant to be called repeatedly over time. -They're analogous to libraries of functionality rather than an executable (a "command" component). +Reactor components are WebAssembly components that are long-running +and meant to be called repeatedly over time. +Unlike "command" components, which are analogous to executables, +reactor components are analogous to libraries of functionality. -Components expose their interfaces via [WebAssembly Interface Types][docs-wit], hand-in-hand with the -[Component Model][docs-component-model] which enables components to use higher level types interchangeably. +Components expose their interfaces via [WebAssembly Interface Types][docs-wit], +hand-in-hand with the [Component Model][docs-component-model] +which enables components to use higher-level types interchangeably. [docs-wit]: ../design/wit.md [docs-component-model]: ../design/why-component-model.md ### Exporting WIT Interfaces with `jco` -Packaging reusable functionality into WebAssembly components isn't useful if we have no way to *expose* that functionality. - -This section offers a slightly deeper dive into the usage of WIT in WebAssembly components that can use the Component Model. +Packaging reusable functionality into WebAssembly components isn't useful +if we have no way to *expose* that functionality. +This section offers a slightly deeper dive into the usage of WIT in WebAssembly components. -As in the previous example, `export`ing WIT interfaces for other components (or a WebAssembly host) to use is fundamental to developing WebAssembly programs. +As in the previous example, `export`ing WIT interfaces for other components (or a WebAssembly host) to use +is fundamental to developing WebAssembly programs. -Let's examine a [`jco` example project called `string-reverse`][jco-examples-string-reverse] that exposes functionality for reversing a string. +Let's examine a [`jco` example project called `string-reverse`][jco-examples-string-reverse] +that exposes functionality for reversing a string. -To build a project like `string-reverse` from the ground up, first we'd start with a WIT like the following: +To build a project like `string-reverse` from the ground up, first we'd start with a WIT like the following. +In a new directory called `string-reverse`, paste this code into a file called `wit/component.wit`: ```wit -package example:string-reverse@0.1.0 - -@since(version = 0.1.0) -interface reverse { - reverse-string: func(s: string) -> string; -} - -world string-reverse { - export reverse; -} +{{#include ../../examples/tutorial/js/string-reverse/component.wit}} ``` -As a slightly deeper crash course on [WIT][wit], here's what the above code describes: +As a slightly deeper crash course on [WIT](../design/wit.md), here's what the above code describes: -- We've defined a namespace called `example` -- We've defined a package called `string-reverse` inside the `example` namespace -- This WIT file corresponds to version `0.1.0` of `example:string-reverse` package -- We've defined an interface called `reverse` which contains *one* function called `reverse-string` -- We specify that the `reverse` interface has existed *since* the `0.1.0` version -- The `reverse-string` function (AKA. `example:string-reverse/reverse.reverse-string`) takes a string and returns a string -- We've defined a `world` called `string-reverse` which exports the functionality provided by the `reverse` interface +- We've defined a namespace called `example`. +- We've defined a package called `string-reverse` inside the `example` namespace. +- This WIT file corresponds to version `0.1.0` of the `example:string-reverse` package. +- We've defined an interface called `reverse` that contains *one* function called `reverse-string`. +- We specify that the `reverse` interface has existed *since* the `0.1.0` version. +- The `reverse-string` function (whose fully qualified name is + `example:string-reverse/reverse.reverse-string`) takes a string and returns a string. +- We've defined a `world` called `string-reverse` that exports the functionality + provided by the `reverse` interface. > [!WARNING] > How do we *know* that `reverse` actually reverses a string? > -> Unfortunately, that problem is not really solvable at this level -- this is between you and the writer of the component that implements the WIT interface. +> Unfortunately, that problem is not really solvable at this level—this is between you +> and the writer of the component that implements the WIT interface. > > Of course, with WebAssembly, you *can* enforce static checks if you're so inclined, *before* you run any given binary. -OK now let's see what the JS code looks like to *implement* the `component` world: - -```js -/** - * This module is the JS implementation of the `string-reverse` WIT world - */ - -/** - * This JavaScript will be interpreted by `jco` and turned into a - * WebAssembly binary with a single export (this `reverse` function). - */ -function reverseString(s) { - return s.reverse(); -} +OK now let's see what the JS code looks like to *implement* the `component` world. +Paste the following code into a file called `string-reverse.mjs`: -/** - * The JavaScript export below represents the export of the `reverse` interface, - * which which contains `reverse-string` as it's primary exported function. - */ -export const reverse = { - reverseString, -}; +```mjs +{{#include ../../examples/tutorial/js/string-reverse/string-reverse.mjs}} ``` +> This code uses `split()` to convert the string into an array of characters, +> reverses the array, and uses `join()` to convert the array back to a string, +> since JavaScript has no built-in string reverse method. + > [!NOTE] -> To view the full code listing along with instructions, see the [`examples/tutorials/jco/string-reverse` folder][jco-examples-string-reverse] +> To view the full code listing along with instructions, see the [`examples/tutorials/jco/string-reverse` folder][jco-examples-string-reverse]. -To use `jco` to compile this component, you can run the following from the `string-reverse` folder: +To use `jco` to compile this component, you can run the following inside your `string-reverse` directory: ```console -npx jco componentize \ - --wit wit/component.wit \ - --world-name component \ - --out string-reverse.wasm \ - --disable all \ - string-reverse.mjs +npx jco componentize --wit wit/component.wit --world-name string-reverse \ + --out string-reverse.wasm --disable=all string-reverse.mjs ``` -> [!NOTE] -> Like the previous example, we're not using any of the advanced [WebAssembly System Interface][wasi] features, so we `--disable` all of them -> -> Rather than typing out the `jco componentize` command manually, you can also run -> the build command with [`npm run build` from the `string-reverse` folder](https://github.com/bytecodealliance/jco/blob/main/examples/components/string-reverse/package.json#L6). - You should see output like the following: ``` OK Successfully written string-reverse.wasm. ``` +> [!NOTE] +> As with the previous example, we're not using any of the advanced [WebAssembly System Interface][wasi] features, +> so we `--disable` all of them. +> +> Rather than typing out the `jco componentize` command manually, you can also run +> the build command with `npm run build` if you use the code from +> [the `string-reverse` folder][string-reverse-package-json]. + + Now that we have a WebAssembly binary, we can *also* use `jco` to run it in a native JavaScript context by *transpiling* the WebAssembly binary (which could have come from anywhere!) to a JavaScript module. @@ -348,7 +372,7 @@ the WebAssembly binary (which could have come from anywhere!) to a JavaScript mo npx jco transpile string-reverse.wasm -o dist/transpiled ``` -You should see the following output: +You should see output that looks like this: ``` Transpiled JS Component Files: @@ -360,25 +384,36 @@ You should see the following output: ``` > [!TIP] -> A gentle reminder that, transpilation *does* produce [Typescript declaration file][ts-decl-file], for use in Typescript projects. +> A gentle reminder: transpilation *does* produce a [TypeScript declaration file][ts-decl-file], +> for use in TypeScript projects. -Now that we have a transpiled module, we can run it from any JavaScript context that supports core WebAssembly (whether NodeJS or the browser). +Now that we have a transpiled module, we can run it from any JavaScript context +that supports core WebAssembly (whether Node.js or the browser). -For NodeJS, we can use code like the following: +For Node.js, we can use code like this. +Paste the following code into a file called `run.js` in your `string-reverse` directory: ```mjs -// If this import listed below is missing, please run `npm run transpile` -import { reverse } from "./dist/transpiled/string-reverse.mjs"; - -const reversed = reverse.reverseString("!dlroW olleH"); - -console.log(`reverseString('!dlroW olleH') = ${reversed}`); +{{#include ../../examples/tutorial/js/string-reverse/run.js}} ``` > [!NOTE] > In the `jco` example project, you can run `npm run transpiled-js` to build the existing code. -Assuming you have the `dist/transpiled` folder populated (by running `jco transpile` in the previous step), you should see output like the following: +As before, we also need a `package.json` file: + +```json +{{#include ../../examples/tutorial/js/string-reverse/package.json}} +``` + +Then run: + +```bash +node run.js +``` + +Assuming you have the `dist/transpiled` folder populated (by running `jco transpile` in the previous step), +you should see output like the following: ``` reverseString('!dlrow olleh') = hello world! @@ -394,16 +429,18 @@ With the help of `jco`, we have: [repo]: https://github.com/bytecodealliance/component-docs [jco-examples-string-reverse]: https://github.com/bytecodealliance/jco/tree/main/examples/components/string-reverse [ts-decl-file]: https://www.typescriptlang.org/docs/handbook/declaration-files/deep-dive.html#declaration-file-theory-a-deep-dive +[string-reverse-package-json]: https://github.com/bytecodealliance/jco/blob/main/examples/components/string-reverse/package.json#L6 ### Advanced: Importing and Reusing WIT Interfaces via Composition -Just as `export`ing functionality is core to building useful WebAssembly components, and similarly `import`ing and -reusing functionality is key to using the strengths of WebAssembly. +Just as `export`ing functionality is core to building useful WebAssembly components, +`import`ing and reusing functionality is key to using the strengths of WebAssembly. Restated, **WIT and the Component Model enable WebAssembly to *compose***. This means we can build on top of functionality that already exists and `export` *new* functionality that depends on existing functionality. -Let's say in addition to the reversing the string (in the previous example) we want to build shared functionality that *also* upper cases the text it receives. +Let's say in addition to eversing the string (in the previous example), +we want to build shared functionality that *also* upper-cases the text it receives. We can reuse the reversing functionality *and* export a new interface which enables us to reverse and upper-case. @@ -413,66 +450,32 @@ functionality for reversing *and* upper-casing a string. Here's the WIT one might write to enable this functionality: ```wit -package example:string-reverse-upper@0.1.0; - -@since(version = 0.1.0) -interface reversed-upper { - reverse-and-uppercase: func(s: string) -> string; -} - -world revup { - // - // NOTE, the import below translates to: - // :/@ - // - import example:string-reverse/reverse@0.1.0; - - export reversed-upper; -} +{{#include ../../examples/tutorial/js/string-reverse-upper/component.wit}} ``` -This time, the `world` named `revup` that we are building *relies* on the interface `reverse` in the package `string-reverse` from the namespace `example`. +This time, the `world` named `revup` that we are building *relies* on the interface `reverse` +in the package `string-reverse` from the namespace `example`. -We can make use of *any* WebAssembly component that matches that interface, as long as we *compose* their -functionality with the component that implements the `revup` world. +We can make use of *any* WebAssembly component that matches that interface, +as long as we *compose* their functionality with the component that implements the `revup` world. The `revup` world `import`s (and makes use) of `reverse` in order to `export` (provide) the `reversed-upper` interface, -which contains the `reverse-and-uppercase` function (in JS, `reverseAndUppercase`). +which contains the `reverse-and-uppercase` function (in JavaScript, `reverseAndUppercase`). > [!NOTE] -> Functionality is imported via the `interface`, *not* the `world`. `world`s can be included/used, but the syntax is slightly different for that. +> Functionality is imported via the `interface`, *not* the `world`. +> `world`s can be included/used, but the syntax is slightly different for that. -The JavaScript to make this work ([`string-reverse-upper.mjs` in `jco/examples`](https://github.com/bytecodealliance/jco/blob/main/examples/components/string-reverse-upper/string-reverse-upper.mjs)) looks like this: +The JavaScript to make this work ([`string-reverse-upper.mjs` in `jco/examples`][string-reverse-upper-mjs]) +looks like this: ```js -/** - * This module is the JS implementation of the `revup` WIT world - */ - -/** - * The import here is *virtual*. It refers to the `import`ed `reverse` interface in component.wit. - * - * These types *do not resolve* when the first `string-reverse-upper` component is built, - * but the types are relevant for the resulting *composed* component. - */ -import { reverseString } from 'example:string-reverse/reverse@0.1.0'; - -/** - * The JavaScript export below represents the export of the `reversed-upper` interface, - * which which contains `revup` as it's primary exported function. - */ -export const reversedUpper = { - /** - * Represents the implementation of the `reverse-and-uppercase` function in the `reversed-upper` interface - * - * This function makes use of `reverse-string` which is *imported* from another WebAssembly binary. - */ - reverseAndUppercase() { - return reverseString(s).toLocaleUpperCase(); - }, -}; +{{#include ../../examples/tutorial/js/string-reverse-upper/string-reverse-upper.mjs}} ``` +If we place the above WIT file in the `wit` subdirectory, we also need to create a +`wit/deps` subdirectory and copy `../string-reverse/wit/component.wit` into `wit/deps`. + We can build the component with `jco componentize`: ```console @@ -481,10 +484,17 @@ npx jco componentize \ --wit wit/ \ --world-name revup \ --out string-reverse-upper.incomplete.wasm \ - --disable all + --disable=all ``` -While we've successfully built a WebAssembly component, unlike the other examples, ours is *not yet complete*. +> If you get an error message, verify that your `wit/component.wit` file +> begins with `package example:string-reverse-upper@0.1.0;`, and that your `wit/deps/` directory +> contains a file beginning with `package example:string-reverse@0.1.0;`. +> In general, your main package should be at the top level of your `wit` directory, +> and any dependencies should be in a subdirectory of that directory. + +Although we've successfully built a WebAssembly component, unlike with the other examples, +ours is *not yet complete*. We can see that if we print the WIT of the generated component by running `jco wit`: @@ -504,11 +514,12 @@ world root { } ``` -This tells us that the component still has *unfulfilled `import`s* -- we *use* the `reverseString` function that's -in `reverse` as if it exists, but it's not yet a real part of the WebAssembly component (hence we've named it `.incomplete.wasm`. +This tells us that the component still has *unfulfilled `import`s*: +we *use* the `reverseString` function that's in `reverse` as if it exists, +but it's not yet a real part of the WebAssembly component (hence we've named it `.incomplete.wasm`). -To compose the two components (`string-reverse-upper/string-reverse-upper.incomplete.wasm` and `string-reverse/string-reverse.wasm` we -built earlier), we'll need the [WebAssembly Composition tool (`wac`)][wac]. We can use `wac plug`: +To compose the two components we built earlier (`string-reverse-upper/string-reverse-upper.incomplete.wasm` and `string-reverse/string-reverse.wasm`), +we'll need the [WebAssembly Composition tool (`wac`)][wac]. We can use `wac plug`: ```console wac plug \ @@ -518,10 +529,16 @@ wac plug \ ``` > [!NOTE] -> You can also run this step with `npm run compose`. +> You can also run this step with `npm run compose`, if using the full project from the `jco` repository. -A new component `string-reverse-upper.wasm` should now be present, which is a "complete" component -- we can check -the output of `jco wit` to ensure that all the imports are satisfied: +A new component `string-reverse-upper.wasm` should now be present, which is a "complete" component. +We can check the output of `npx jco wit` to ensure that all the imports are satisfied: + +```sh +npx jco wit string-reverse-upper.wasm +``` + +You should see output like the following: ```wit package root:component; @@ -531,17 +548,19 @@ world root { } ``` -It's as-if we never imported any functionality at all -- the functionality present in `string-reverse.wasm` has been -*merged into* `string-reverse-upper.wasm`, and it now simply `export`s the advanced functionality. +It's as-if we never imported any functionality at all—the functionality present in `string-reverse.wasm` +has been *merged into* `string-reverse-upper.wasm`, and it now simply `export`s the advanced functionality. -We can run this completed component with in any WebAssembly-capable native JavaScript environment by using a the transpiled result: +We can run this completed component with in any WebAssembly-capable native JavaScript environment +by using the transpiled result: ```console npx jco transpile string-reverse-upper.wasm -o dist/transpiled ``` > [!NOTE] -> In the example project, you can run `npm run transpile` instead, which will also change the extension on `dist/transpiled/string-reverse-upper.js` to `.mjs` +> In the example project, you can run `npm run transpile` instead, +> which will also change the extension on `dist/transpiled/string-reverse-upper.js` to `.mjs`. You should see output like the following: @@ -556,28 +575,18 @@ You should see output like the following: ``` > [!TIP] -> Notice that there are *two* core WebAssembly files? That's because two core WebAssembly modules were involved +> Notice that there are *two* core WebAssembly files. That's because two core WebAssembly modules were involved > in creating the ultimate functionality we needed. To run the transpiled component, we can write code like the following: -```mjs -/** - * If this import listed below is missing, please run - * - * ``` - * npm run build && npm run compose && npm run transpile` - * ``` - */ -import { reversedUpper } from "./dist/transpiled/string-reverse-upper.mjs"; - -const result = reversedUpper.reverseAndUppercase("!dlroW olleH"); - -console.log(`reverseAndUppercase('!dlroW olleH') = ${result}`); +```js +{{#include ../../examples/tutorial/js/string-reverse-upper/run.js}} ``` > [!NOTE] -> In the [`jco` example project](https://github.com/bytecodealliance/jco/tree/main/examples/components/string-reverse-upper), you can run `npm run transpiled-js` +> In the [`jco` example project][jco-examples-string-reverse-upper], +> you can run `npm run transpiled-js`. You should see output like the following: @@ -587,6 +596,7 @@ reverseAndUppercase('!dlroW olleH') = HELLO WORLD! [wac]: https://github.com/bytecodealliance/wac [jco-examples-string-reverse-upper]: https://github.com/bytecodealliance/jco/tree/main/examples/components/string-reverse-upper +[string-reverse-upper-mjs]: https://github.com/bytecodealliance/jco/blob/main/examples/components/string-reverse-upper/string-reverse-upper.mjs [!TIP]: # [!NOTE]: # From 3f5fad315506c0ae5f8c97831c1d2fa52f08d6ad Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 4 Sep 2025 16:55:02 -0700 Subject: [PATCH 2/8] Update component-model/src/language-support/javascript.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/language-support/javascript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index fbec4182..cc9a02b0 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -44,7 +44,7 @@ npm install -g @bytecodealliance/jco Building a WebAssembly component with JavaScript often consists of: -1. Determining which interface our component will target (i.e. a [WebAssembly Interface Types ("WIT")](../design/wit.md) world) +1. Determining which interface our component will target (i.e. given a [WebAssembly Interface Types ("WIT")](../design/wit.md) world) 2. Creating the component by writing JavaScript that satisfies the interface 3. Compiling the interface-compliant JavaScript to WebAssembly From 120b73d3c7864c794613de4220b1d33246e427b6 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 4 Sep 2025 16:55:23 -0700 Subject: [PATCH 3/8] Update component-model/src/language-support/javascript.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/language-support/javascript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index cc9a02b0..b2a39f13 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -66,7 +66,7 @@ into a file called `world.wit`. The `export add;` declaration inside the `adder` world means that environments that interact with the resulting WebAssembly component will be able to _call_ the `add` function. -Its name must be fully qualified: `docs:adder/add.add@0.1.0`. +The fully qualified name of the `add` interface in this context is `docs:adder/add.add@0.1.0`. The parts of this name are: * `docs:adder` is the package name. * `add` is the name of the interface containing the `add` function. From 488a2cc2e9632376ba78c7141138a0a5ce7026f3 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 4 Sep 2025 16:55:40 -0700 Subject: [PATCH 4/8] Update component-model/src/language-support/javascript.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/language-support/javascript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index b2a39f13..f0eed75c 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -68,7 +68,7 @@ environments that interact with the resulting WebAssembly component will be able to _call_ the `add` function. The fully qualified name of the `add` interface in this context is `docs:adder/add.add@0.1.0`. The parts of this name are: -* `docs:adder` is the package name. +* `docs:adder` is the namespace and package, with `docs` being the namespace and `adder` being the package. * `add` is the name of the interface containing the `add` function. * `add` also happens to be the name of the function itself. * `@0.1.0` is a version number that must match the declared version number. From 52dc0d35b4778d0e32180cb3ec98ab06fb0de3d8 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 4 Sep 2025 16:55:49 -0700 Subject: [PATCH 5/8] Update component-model/src/language-support/javascript.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/language-support/javascript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index f0eed75c..f7e8a47e 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -71,7 +71,7 @@ The parts of this name are: * `docs:adder` is the namespace and package, with `docs` being the namespace and `adder` being the package. * `add` is the name of the interface containing the `add` function. * `add` also happens to be the name of the function itself. -* `@0.1.0` is a version number that must match the declared version number. +* `@0.1.0` is a version number that must match the declared version number of the package. > To learn more about the WIT syntax, check out the [WIT explainer](../design/wit.md). From 54e8fc80d448e4e8f751a93fece52a6bf8bb9850 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 4 Sep 2025 16:58:37 -0700 Subject: [PATCH 6/8] Update component-model/src/language-support/javascript.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/language-support/javascript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index f7e8a47e..c51dbba1 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -129,8 +129,8 @@ OK Successfully written adder.wasm. You should now have an `adder.wasm` file in your `adder` directory. You can verify that this file contains a component with: -```bash -$ wasm-tools print adder.wasm |head -1 +```console +$ wasm-tools print adder.wasm | head -1 (component ``` From 44834589f7590f363316e36999267b558320f79e Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 4 Sep 2025 16:59:22 -0700 Subject: [PATCH 7/8] Update component-model/src/language-support/javascript.md Co-authored-by: Victor Adossi <123968127+vados-cosmonic@users.noreply.github.com> --- component-model/src/language-support/javascript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index c51dbba1..0c4f45ae 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -491,7 +491,7 @@ npx jco componentize \ > begins with `package example:string-reverse-upper@0.1.0;`, and that your `wit/deps/` directory > contains a file beginning with `package example:string-reverse@0.1.0;`. > In general, your main package should be at the top level of your `wit` directory, -> and any dependencies should be in a subdirectory of that directory. +> and any dependencies should be in a subdirectory of that directory (normally `deps`). Although we've successfully built a WebAssembly component, unlike with the other examples, ours is *not yet complete*. From 140dcd6e1bbb2e3c884f5f793f4756ed59111c89 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 4 Sep 2025 16:59:49 -0700 Subject: [PATCH 8/8] Re-add line breaks in jco commands --- .../src/language-support/javascript.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md index 0c4f45ae..c54dae8d 100644 --- a/component-model/src/language-support/javascript.md +++ b/component-model/src/language-support/javascript.md @@ -113,8 +113,12 @@ This means that we can `--disable` all unneeded WASI functionality when we invok Inside your `adder` directory, execute: ```console -jco componentize --wit world.wit --world-name adder --out adder.wasm - --disable=all adder.js +jco componentize \ + --wit world.wit \ + --world-name adder \ + --out adder.wasm \ + --disable=all \ + adder.js ``` > [!NOTE] @@ -346,8 +350,12 @@ Paste the following code into a file called `string-reverse.mjs`: To use `jco` to compile this component, you can run the following inside your `string-reverse` directory: ```console -npx jco componentize --wit wit/component.wit --world-name string-reverse \ - --out string-reverse.wasm --disable=all string-reverse.mjs +npx jco componentize \ + --wit wit/component.wit \ + --world-name string-reverse \ + --out string-reverse.wasm \ + --disable=all \ + string-reverse.mjs ``` You should see output like the following: