diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index a2adc137d..a6b74f564 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -1,6 +1,9 @@ name: examples on: + push: + branches: + - main pull_request: branches: - main @@ -49,24 +52,7 @@ jobs: run: | npm install - - name: build (${{ matrix.project.name }}) - working-directory: ${{ matrix.project.dir }} - run: | - npm run build - - - name: compose (${{ matrix.project.name }}) - if: ${{ matrix.project.is-composed }} - working-directory: ${{ matrix.project.dir }} - run: | - npm run compose - - - name: transpile (${{ matrix.project.name }}) - working-directory: ${{ matrix.project.dir }} - run: | - npm run transpile - - - name: run transpiled js (${{ matrix.project.name }}) - if: ${{ matrix.project.composed }} + - name: run all script for (${{ matrix.project.name }}) working-directory: ${{ matrix.project.dir }} run: | - npm run transpiled-js + npm run all diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..7004f4497 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,16 @@ +# Examples + +This folder contains examples of using `jco`, `componentize-js`, and related Javascript WebAssembly runtime tooling. + +The following sections are present: + +- [Example Components](./components) - "Just show me the code" examples of how to build JS projects that use `jco` +- [Guides](./guides) - Walkthroughs of concepts and functionality to keep in mind while using `jco` and associated tooling + +> [!NOTE] +> Note sure what WebAssembly "Components" are, relative to WebAssembly modules? +> +> For an in-depth multi-language guide to the concepts and advanced features behind the Component Model, +> [read the Component Model book][cm-book]. + +[cm-book]: https://component-model.bytecodealliance.org/ diff --git a/examples/components/README.md b/examples/components/README.md new file mode 100644 index 000000000..7201cf313 --- /dev/null +++ b/examples/components/README.md @@ -0,0 +1,14 @@ +## Example Components + +Most (if not all) individual example projects are standard Javascript projects commonplace ecosystem tooling, whether +for server side ([NodeJS][nodejs] -- `npm`, etc) or the browser. + +A brief description of the examples contained in this folder: + +| Example | Description | +|--------------------------------------------------|--------------------------------------------------------------------------------------------------| +| [`add`](./add) | `export`s basic functionality with simple types | +| [`string-reverse`](./string-reverse) | `export`s basic functionality with a slightly more involved WIT interface and more complex types | +| [`string-reverse-upper`](./string-reverse-upper) | `import`s functionality to build more advanced computation to `export` | + +[nodejs]: https://nodejs.org diff --git a/examples/components/add/.gitignore b/examples/components/add/.gitignore index 39d7edaff..66eb10a5c 100644 --- a/examples/components/add/.gitignore +++ b/examples/components/add/.gitignore @@ -1,3 +1,4 @@ node_modules dist -*.wasm \ No newline at end of file +*.wasm +pnpm-lock.yaml \ No newline at end of file diff --git a/examples/components/add/add.mjs b/examples/components/add/add.js similarity index 100% rename from examples/components/add/add.mjs rename to examples/components/add/add.js diff --git a/examples/components/add/package.json b/examples/components/add/package.json index d0252c1dc..041288c8f 100644 --- a/examples/components/add/package.json +++ b/examples/components/add/package.json @@ -3,13 +3,13 @@ "description": "Simple codebase for compiling an add function to WebAssembly with jco", "type": "module", "scripts": { - "build": "jco componentize add.mjs --wit wit/component.wit --world-name component --out add.wasm --disable all", - "transpile": "jco transpile add.wasm -o dist/transpiled ; cp dist/transpiled/add.js dist/transpiled/add.mjs", - "start": "cargo run --manifest-path=../../../example-host/Cargo.toml --release -- 1 2 add.wasm", - "transpiled-js": "node run-transpiled.js" + "build": "jco componentize add.js --wit wit/component.wit --world-name component --out add.wasm --disable all", + "transpile": "jco transpile add.wasm -o dist/transpiled", + "transpiled-js": "node run-transpiled.js", + "all": "npm run build && npm run transpile && npm run transpiled-js" }, "devDependencies": { - "@bytecodealliance/jco": "1.7.0", - "@bytecodealliance/componentize-js": "0.13.0" + "@bytecodealliance/jco": "1.7.1", + "@bytecodealliance/componentize-js": "0.13.1" } } diff --git a/examples/components/add/run-transpiled.js b/examples/components/add/run-transpiled.js index f12d52fc5..5b0ed014d 100644 --- a/examples/components/add/run-transpiled.js +++ b/examples/components/add/run-transpiled.js @@ -1,4 +1,4 @@ // If this import listed below is missing, please run `npm run transpile` -import { add } from "./dist/transpiled/add.mjs"; +import { add } from "./dist/transpiled/add.js"; console.log("1 + 2 = " + add(1, 2)); diff --git a/examples/components/string-reverse-upper/.gitignore b/examples/components/string-reverse-upper/.gitignore index 39d7edaff..66eb10a5c 100644 --- a/examples/components/string-reverse-upper/.gitignore +++ b/examples/components/string-reverse-upper/.gitignore @@ -1,3 +1,4 @@ node_modules dist -*.wasm \ No newline at end of file +*.wasm +pnpm-lock.yaml \ No newline at end of file diff --git a/examples/components/string-reverse-upper/README.md b/examples/components/string-reverse-upper/README.md index 16837b1cb..810b6fb97 100644 --- a/examples/components/string-reverse-upper/README.md +++ b/examples/components/string-reverse-upper/README.md @@ -135,7 +135,7 @@ dist ├── string-reverse-upper.core2.wasm ├── string-reverse-upper.core.wasm ├── string-reverse-upper.d.ts - └── string-reverse-upper.mjs + └── string-reverse-upper.js ``` With this transpiled code available, we can now run native NodeJS code that will *use* the WebAssembly module: diff --git a/examples/components/string-reverse-upper/package.json b/examples/components/string-reverse-upper/package.json index 8747fcda2..a25367d46 100644 --- a/examples/components/string-reverse-upper/package.json +++ b/examples/components/string-reverse-upper/package.json @@ -3,14 +3,15 @@ "description": "Simple codebase for reversing a string and upper-casing it via WebAssembly with jco", "type": "module", "scripts": { - "build": "jco componentize string-reverse-upper.mjs --wit wit/ --world-name revup --out string-reverse-upper.incomplete.wasm --disable all", + "build": "jco componentize string-reverse-upper.js --wit wit/ --world-name revup --out string-reverse-upper.incomplete.wasm --disable all", "build:dep": "cd ../string-reverse ; npm run build", "compose": "npm run build:dep && wac plug --plug ../string-reverse/string-reverse.wasm string-reverse-upper.incomplete.wasm -o string-reverse-upper.wasm", - "transpile": "jco transpile string-reverse-upper.wasm -o dist/transpiled ; mv dist/transpiled/string-reverse-upper.js dist/transpiled/string-reverse-upper.mjs", - "transpiled-js": "node run-transpiled.js" + "transpile": "jco transpile string-reverse-upper.wasm -o dist/transpiled", + "transpiled-js": "node run-transpiled.js", + "all": "npm run build:dep ; npm run build ; npm run compose ; npm run transpile ; npm run transpiled-js" }, "devDependencies": { - "@bytecodealliance/jco": "1.7.0", - "@bytecodealliance/componentize-js": "0.13.0" + "@bytecodealliance/jco": "1.7.1", + "@bytecodealliance/componentize-js": "0.13.1" } } diff --git a/examples/components/string-reverse-upper/run-transpiled.js b/examples/components/string-reverse-upper/run-transpiled.js index 2094362d3..479993249 100644 --- a/examples/components/string-reverse-upper/run-transpiled.js +++ b/examples/components/string-reverse-upper/run-transpiled.js @@ -5,7 +5,7 @@ * npm run build && npm run compose && npm run transpile` * ``` */ -import { reversedUpper } from "./dist/transpiled/string-reverse-upper.mjs"; +import { reversedUpper } from "./dist/transpiled/string-reverse-upper.js"; const result = reversedUpper.reverseAndUppercase("!dlroW olleH"); diff --git a/examples/components/string-reverse-upper/string-reverse-upper.incomplete.wasm b/examples/components/string-reverse-upper/string-reverse-upper.incomplete.wasm index 1fda45bab..b5126c0d4 100644 Binary files a/examples/components/string-reverse-upper/string-reverse-upper.incomplete.wasm and b/examples/components/string-reverse-upper/string-reverse-upper.incomplete.wasm differ diff --git a/examples/components/string-reverse-upper/string-reverse-upper.mjs b/examples/components/string-reverse-upper/string-reverse-upper.js similarity index 97% rename from examples/components/string-reverse-upper/string-reverse-upper.mjs rename to examples/components/string-reverse-upper/string-reverse-upper.js index 529bbe9ca..0c74e6a4d 100644 --- a/examples/components/string-reverse-upper/string-reverse-upper.mjs +++ b/examples/components/string-reverse-upper/string-reverse-upper.js @@ -20,7 +20,7 @@ export const reversedUpper = { * * This function makes use of `reverse-string` which is *imported* from another WebAssembly binary. */ - reverseAndUppercase() { + reverseAndUppercase(s) { return reverseString(s).toLocaleUpperCase(); }, }; diff --git a/examples/components/string-reverse/.gitignore b/examples/components/string-reverse/.gitignore index 39d7edaff..66eb10a5c 100644 --- a/examples/components/string-reverse/.gitignore +++ b/examples/components/string-reverse/.gitignore @@ -1,3 +1,4 @@ node_modules dist -*.wasm \ No newline at end of file +*.wasm +pnpm-lock.yaml \ No newline at end of file diff --git a/examples/components/string-reverse/package.json b/examples/components/string-reverse/package.json index 34a750e36..bd27dfc9b 100644 --- a/examples/components/string-reverse/package.json +++ b/examples/components/string-reverse/package.json @@ -3,12 +3,13 @@ "description": "Simple codebase for reversing a string via WebAssembly with jco", "type": "module", "scripts": { - "build": "jco componentize string-reverse.mjs --wit wit/component.wit --world-name component --out string-reverse.wasm --disable all", - "transpile": "jco transpile string-reverse.wasm -o dist/transpiled ; mv dist/transpiled/string-reverse.js dist/transpiled/string-reverse.mjs", - "transpiled-js": "node run-transpiled.js" + "build": "jco componentize string-reverse.js --wit wit/component.wit --world-name component --out string-reverse.wasm --disable all", + "transpile": "jco transpile string-reverse.wasm -o dist/transpiled", + "transpiled-js": "node run-transpiled.js", + "all": "npm run build; npm run transpile; npm run transpiled-js" }, "devDependencies": { - "@bytecodealliance/jco": "1.7.0", - "@bytecodealliance/componentize-js": "0.13.0" + "@bytecodealliance/jco": "1.7.1", + "@bytecodealliance/componentize-js": "0.13.1" } } diff --git a/examples/components/string-reverse/run-transpiled.js b/examples/components/string-reverse/run-transpiled.js index 73ed065d7..ea0772058 100644 --- a/examples/components/string-reverse/run-transpiled.js +++ b/examples/components/string-reverse/run-transpiled.js @@ -1,5 +1,5 @@ // If this import listed below is missing, please run `npm run transpile` -import { reverse } from "./dist/transpiled/string-reverse.mjs"; +import { reverse } from "./dist/transpiled/string-reverse.js"; const reversed = reverse.reverseString("!dlroW olleH"); diff --git a/examples/components/string-reverse/string-reverse.mjs b/examples/components/string-reverse/string-reverse.js similarity index 100% rename from examples/components/string-reverse/string-reverse.mjs rename to examples/components/string-reverse/string-reverse.js diff --git a/examples/guides/00-tooling-setup.md b/examples/guides/00-tooling-setup.md new file mode 100644 index 000000000..620a9d22e --- /dev/null +++ b/examples/guides/00-tooling-setup.md @@ -0,0 +1,34 @@ +# 00 - Tooling Setup + +To follow along with the guides, you'll need to have the Javscript for WebAssembly toolchain installed, +which means installing [`jco`][jco] and related tooling. + +[`jco`][jco] is a fully native JS tool for working with the emerging WebAssembly + Components specification in JavaScript. + +> [!NOTE] +> [Typescript][ts] can *also* be used, given that it is transpiled to JS first by relevant tooling (`tsc`). +> `jco` includes a `jco types` subcommand for generating typings that can be used with a Typescript codebase. + +[jco]: https://github.com/bytecodealliance/jco +[ts]: https://typescriptlang.org + +### Installing `jco` + +[`jco`][jco] and [`componentize-js`][componentize-js] can be installed with standard NodeJS tooling: + +```console +npm install -g @bytecodealliance/componentize-js @bytecodealliance/jco +``` + +> [!NOTE] +> `jco` and `componentize-js` can be installed in a project-local manner with `npm install -D` + +[ComponentizeJS][componentize-js] provides tooling used by `jco` to transpile JS to Wasm, so installing both packages is required. + +[componentize-js]: https://github.com/bytecodealliance/ComponentizeJS + +### Contribute to this Guide! + +The goal for this guide is to leave zero lingering questions. If you had substantial doubts/unaddressed questions +while going through this guide, [open up an issue](https://github.com/bytecodealliance/jco/issues/new) and we'll improve the docs together. diff --git a/examples/guides/01-building-a-component-with-jco.md b/examples/guides/01-building-a-component-with-jco.md new file mode 100644 index 000000000..a4d161f25 --- /dev/null +++ b/examples/guides/01-building-a-component-with-jco.md @@ -0,0 +1,104 @@ +# 01 - Building a WebAssembly Component with `jco` + +> [!NOTE] +> This guide assumes you have dependencies like `jco` installed already. +> +> Consider referring to the "Tooling setup" guide if your environment hasn't been set up yet. + +The first step in building a WebAssembly component is creating or downloading the interface that +defines what your component can do. This usually means creating or downloading the +[WebAssembly Interface Types ("WIT")][wit] world you would like to "target" with your component. + +## Writing the WIT + +The [example `add` component][examples-add] showcases a component that adds two numbers together. + +The WIT interface looks like the following: + +```wit +package example:adder; + +world component { + export add: func(x: s32, y: s32) -> s32; +} +``` + +> [!NOTE] +> `export`ing the `add` interface meants that runners of the WebAssembly binary will be able to *call* that function. +> +> To learn more about the WIT syntax, check out the full [WIT specification][wit] + +## Writing the Javascript + +Along with this WIT interface, we can write a JavaScript module that implements the exported `add` function in the `adder` world: + +```js +export function add(x, y) { + return x + y; +} +``` +> [!WARN] +> jco only deals with ES modules, so ensure to set `"type": "module"` in your `package.json` if necessary + +> [!NOTE] +> In the code above, the JS module *itself* is the `component` world, and `add` function export is satisfied with the `add` JS function. + +## Building a WebAssembly Component + +With the WIT and Javascript in place, we can use [`jco`][jco] to create a WebAssembly component from the JS module, using `jco componentize`. + +Our component is *so simple* (reminiscent of [Core WebAssembly][wasm-core], which deals primarily in numeric values) +that we're actually *not using* any of the [WebAssembly System Interface][wasi] -- this means that we can `--disable` it when we invoke `jco componentize`. + +From the [`examples/components/add`][examples-add] folder, you can run `jco componentize`: + +```console +jco componentize \ + add.js \ + --wit path/to/add/world.wit \ + --world-name component \ + --out add.wasm \ + --disable all +``` + +> [!NOTE] +> You can exclude the `--disable` option and the component will build just fine! + +You should see output like the following: + +``` +OK Successfully written add.wasm. +``` + +> [!NOTE] +> As the `add` example is a regular [NodeJS][nodejs] project, you can run `npm install && npm run build` +> without having `jco` and `componentize-js` installed globally. + +[wit]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md +[nodejs]: https://nodejs.org/en +[wasi]: https://wasi.dev/ +[wasm-core]: https://webassembly.github.io/spec/core/ +[jco]: https://github.com/bytecodealliance/jco +[examples-add]: https://github.com/bytecodealliance/jco/tree/main/examples/components/add + +## FAQ + +### Is this a Reactor component? + +"Reactor" components are WebAssembly components that can be called repeatedly over time. This serves uses cases like building HTTP handlers, +but also for serving as libraries in other components. + +Reactor components (and components in general) 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 interchangably. + +They're analogous to libraries of functionality rather than an executable (a "command" component). By contrast, command components must export +a `_start` function, and *usually* export the [`wasi:cli/run` interface][github-wasi-cli], so that other + +[docs-wit]: ../design/wit.md +[docs-component-model]: ../design/why-component-model.md +[github-wasi-cli]: https://github.com/WebAssembly/wasi-cli + +### Contribute to this Guide! + +The goal for this guide is to leave zero lingering questions. If you had substantial doubts/unaddressed questions +while going through this guide, [open up an issue](https://github.com/bytecodealliance/jco/issues/new) and we'll improve the docs together. diff --git a/examples/guides/02-running-components-in-js.md b/examples/guides/02-running-components-in-js.md new file mode 100644 index 000000000..209a245b2 --- /dev/null +++ b/examples/guides/02-running-components-in-js.md @@ -0,0 +1,70 @@ +# 02 - Running Components in JavaScript (NodeJS & the Browser) + +> [!NOTE] +> This guide starts where "Building a component with jco" ended. +> +> Consider referring to that guide if anything here is confusing. + +While JavaScript runtimes are available in browsers, they cannot yet execute WebAssembly components, +with most supporting only the [Core WebAssembly Specification][core-wasm]. + +A WebAssembly component built in any language (Javascript or otherwise) must be transpiled into +a [WebAssembly core module][wasm-core-module] with a JavaScript wrapper which *can* be run by +in-browser Wasm runtimes that support the core WebAssembly specification. + +`jco` enables transpilation that supports *both* browser and NodeJS usage for WebAssembly Components. +This means we can WebAssembly components built in Javascript (or other languages) from Javascript-native projects. + +## Transpiling a Component + +Given an existing WebAssembly component (e.g. `add.wasm`), we can "transpile" the component into runnable Javscript by using `jco tranpsile`: + +```console +jco transpile add.wasm -o out-dir +``` + +You should see output similar to the following: + +``` + Transpiled JS Component Files: + + - out-dir/add.core.wasm 10.1 MiB + - out-dir/add.d.ts 0.1 KiB + - out-dir/add.js 1.57 KiB +``` + +> [!NOTE] +> To follow along, see the [`add` example component][examples-add] +> +> With the project pulled locally, you also run `npm run transpile` which +> outputs to the slightly more standard `dist/transpiled` rather than `out-dir` above. + +## Using the component from Javscript (NodeJS) + +Thanks to `jco` transpilation, you can import the resulting `out-dir/add.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: + +```mjs +import { add } from "./out-dir/add.js"; + +console.log("1 + 2 = " + add(1, 2)); +``` + +Now that we have NodeJS compatible Javascript that uses the transpiled component, we can execute it directly +with the NodeJS javascript interpreter (`node`), and you should see the following output: + +``` +1 + 2 = 3 +``` + +[wasm-core-module]: https://webassembly.github.io/spec/core/binary/modules.html +[core-wasm]: https://webassembly.github.io/spec/core/ +[examples-add]: https://github.com/bytecodealliance/jco/tree/main/examples/components/add +[nodejs]: https://nodejs.org + +### Contribute to this Guide! + +The goal for this guide is to leave zero lingering questions. If you had substantial doubts/unaddressed questions +while going through this guide, [open up an issue](https://github.com/bytecodealliance/jco/issues/new) and we'll improve the docs together. diff --git a/examples/guides/03-exporting-functionality-with-rich-types.md b/examples/guides/03-exporting-functionality-with-rich-types.md new file mode 100644 index 000000000..6521a7bc7 --- /dev/null +++ b/examples/guides/03-exporting-functionality-with-rich-types.md @@ -0,0 +1,162 @@ +# 03 - Exporting funcitonality with rich types + +> [!NOTE] +> This guide starts where "Building a Component with jco" ended. +> +> Consider referring to that guide if anything here is confusing. + +`export`ing WIT interfaces for other components (or a WebAssembly host) to use is fundamental to developing +WebAssembly programs. + +Let's examine the [`string-reverse` example project][examples-string-reverse] that exposes functionality for +reversing a string, using slightly more expressive types and a more advanced WIT interface than the `add` example. + +## Writing the WIT + +To build a project like `string-reverse` from the ground up, first we'd start with a WIT like the following: + +```wit +package example:string-reverse@0.1.0; + +@since(version = 0.1.0) +interface reverse { + reverse-string: func(s: string) -> string; +} + +world component { + export reverse; +} +``` + +As a slightly deeper crash course on [WIT][wit], 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:reverse-string/reverse.reverse-string`) takes a string and returns a string +- The `string-reverse` world exports the `reverse` interface (and the `reverse-string` function it contains) + +> [!WARNING] +> How do we *know* that `reverse-string` actually reverses a string? +> +> Unfortunately, that problem is not really solvable at this level -- function signatures +> can only tell us so much about a function. +> +> Of course, with WebAssembly, you *could* run checks at pre-deploy or even runtime if +> you're so inclined, *before* you run any given binary with a production workload. + +## Writing the Javascript + +To implement the `component` world, we'd need code that looks like the following: + +```mjs +export const reverse = { + reverseString(s) { + return s.split("") + .reverse() + .join(""); + } +}; +``` + +> [!WARN] +> jco only deals with ES modules, so ensure to set `"type": "module"` in your `package.json` if necessary + +> [!NOTE] +> To view the full code listing along with instructions, see the [`string-reverse` example folder][examples-string-reverse] + +## Building a WebAssembly Component + +To use `jco` to compile this component, you can run the following from the `string-reverse` folder: + +```console +jco componentize \ + string-reverse.js \ + --wit wit/component.wit \ + --world-name component \ + --out string-reverse.wasm \ + --disable all +``` + +> [!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`. + +You should see output like the following: + +``` +OK Successfully written string-reverse.wasm. +``` + +## Transpiling the WebAssembly Component + +Now that we have a WebAssembly binary, we can *also* use `jco` to run it in a native Javascript +context by *transpiling* the WebAsssembly binary (which could have come from anywhere!) to a Javascript module. + +```console +jco transpile string-reverse.wasm -o dist/transpiled +``` + +You should see the following output: + +``` + Transpiled JS Component Files: + + - dist/transpiled/interfaces/example-string-reverse-reverse.d.ts 0.1 KiB + - dist/transpiled/string-reverse.core.wasm 10.1 MiB + - dist/transpiled/string-reverse.d.ts 0.15 KiB + - dist/transpiled/string-reverse.js 2.55 KiB +``` + +> [!TIP] +> Yes, transpilation *does* produce [Typescript declaration file][ts-decl-file], so you can also use a Typescript-focused workflows. +> +> In the [`string-reverse` example project][examples-string-reverse] you can run this step via `npm run transpile`. + +## Running from Javascript (NodeJS) + +Now that we have a transpiled module, we can run it from any Javascript context that supports core WebAssembly (whether NodeJS or the browser). + + +For NodeJS, we can use code like the following: + +```mjs +import { reverse } from "./dist/transpiled/string-reverse.js"; + +const reversed = reverse.reverseString("!dlroW olleH"); + +console.log(`reverseString('!dlroW olleH') = ${reversed}`); +``` + +> [!NOTE] +> In the [`string-reverse` example project][examples-string-reverse], you can run `npm run transpiled-js` to execute the code above. + +You should see output like the following: + +``` +reverseString('!dlrow olleh') = hello world! +``` + +While it's somewhat redundant in this context, what we've done from NodeJS demonstrates the usefulness +of WebAssembly and the `jco` toolchain. With the help of `jco`, we have: + +- Compiled Javascript to a WebAssembly module (`jco compile`), adhering to an interface defined via WIT +- Converted the compiled WebAssembly module (which could be from *any* language) to a module +that can be used from any compliant JS runtime (`jco transpile`) +- Run the transpiled WebAssembly component from a Javascript native runtime (NodeJS) + +[repo]: https://github.com/bytecodealliance/component-docs +[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 +[wasi]: https://wasi.dev/ +[wit]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md + +### Contribute to this Guide! + +The goal for this guide is to leave zero lingering questions. If you had substantial doubts/unaddressed questions +while going through this guide, [open up an issue](https://github.com/bytecodealliance/jco/issues/new) and we'll improve the docs together. diff --git a/examples/guides/04-importing-and-reusing-components.md b/examples/guides/04-importing-and-reusing-components.md new file mode 100644 index 000000000..24c2ff16f --- /dev/null +++ b/examples/guides/04-importing-and-reusing-components.md @@ -0,0 +1,184 @@ +# Importing and reusing components + +> [!NOTE] +> This guide starts where "Exporting Functionality with Rich Types" ended. +> +> Consider referring to that guide if anything here is confusing. + +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. + +## Writing the WIT + +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. + +We can reuse the reversing functionality *and* export a new interface which enables us to +reverse and upper-case. + +Here's the WIT to make that happen: + +```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; +} +``` + +This time, the `revup` world that we are building *relies* on the `reverse` interface (by importing) in the +package `string-reverse` from the namespace `example`. + +We can make use of *any* WebAssembly component that matches the `reverse` interface, as long as we *compose* +that functionality with the component into the component implementing 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`). + +> [!NOTE] +> In this example, functionality is imported from `interface`s, *not* `world`s. It is possible to reuse `world`s, +> but the syntax is not showcased here. + +## Writing the Javascript + +The Javascript to make this work ([`string-reverse-upper.js` in the `string-reverse-upper` example](https://github.com/bytecodealliance/jco/blob/main/examples/components/string-reverse-upper/string-reverse-upper.js)) looks like this: + +```mjs +import { reverseString } from 'example:string-reverse/reverse@0.1.0'; + +export const reversedUpper = { + reverseAndUppercase() { + return reverseString(s).toLocaleUpperCase(); + }, +}; +``` + +We can build the component with `jco componentize`, from the `string-reverse-upper` folder: + +```console +jco componentize \ + string-reverse-upper.js \ + --wit wit/ \ + --world-name revup \ + --out string-reverse-upper.incomplete.wasm \ + --disable all +``` + +### Confirming the output + +While we've successfully built a WebAssembly component, unlike 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`: + +```console +jco wit string-reverse-upper.incomplete.wasm +``` + +You should see output like the following: + +``` +package root:component; + +world root { + import example:string-reverse/reverse@0.1.0; + + export example:string-reverse-upper/reversed-upper@0.1.0; +} +``` + +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`. + +## Composing two components together + +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`: + +```console +wac plug \ + -o string-reverse-upper.wasm \ + --plug ../string-reverse/string-reverse.wasm \ + string-reverse-upper.incomplete.wasm +``` + +> [!NOTE] +> You can also run this step with `npm run compose`. + +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: + +```wit +package root:component; + +world root { + export example:string-reverse-upper/reversed-upper@0.1.0; +} +``` + +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. + +## Transpiling the component to run in NodeJS (or the Browser) + +We can run this completed component with in any WebAssembly-capable native Javascript environment by using a 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. + +You should see output like the following: + +``` + Transpiled JS Component Files: + + - dist/transpiled/interfaces/example-string-reverse-upper-reversed-upper.d.ts 0.12 KiB + - dist/transpiled/string-reverse-upper.core.wasm 10.1 MiB + - dist/transpiled/string-reverse-upper.core2.wasm 10.1 MiB + - dist/transpiled/string-reverse-upper.d.ts 0.19 KiB + - dist/transpiled/string-reverse-upper.js 6.13 KiB +``` + +> [!TIP] +> 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 +import { reversedUpper } from "./dist/transpiled/string-reverse-upper.js"; + +const result = reversedUpper.reverseAndUppercase("!dlroW olleH"); + +console.log(`reverseAndUppercase('!dlroW olleH') = ${result}`); +``` + +> [!NOTE] +> In the [`string-reverse-upper` example project][examples-string-reverse-upper], you can run `npm run transpiled-js` + +You should see output like the following: + +``` +reverseAndUppercase('!dlroW olleH') = HELLO WORLD! +``` + +[wac]: https://github.com/bytecodealliance/wac +[examples-string-reverse-upper]: https://github.com/bytecodealliance/jco/tree/main/examples/components/string-reverse-upper + +### Contribute to this Guide! + +The goal for this guide is to leave zero lingering questions. If you had substantial doubts/unaddressed questions +while going through this guide, [open up an issue](https://github.com/bytecodealliance/jco/issues/new) and we'll improve the docs together. diff --git a/examples/guides/README.md b/examples/guides/README.md new file mode 100644 index 000000000..a4f756bd8 --- /dev/null +++ b/examples/guides/README.md @@ -0,0 +1,24 @@ +# Guides + +This folder contains walk-throughs that offer a guided tour through concepts and +functionality for using the JS ecosystem WebAssembly tooling (`jco`, `componentize-js`), +often referencing or building up to [example WebAssembly components](./../components) available elsewhere in this repository. + +| Guide | Description | +|----------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| [00 - Tooling setup][00] | Get started with JS WebAssembly tooling | +| [01 - Building a Component with `jco`][01] | Build a simple component ([`add.wasm`][comp-add]) | +| [02 - Running components in Javascript][02] | How to run components from existing JS code | +| [03 - Exporting fucntionality with rich types][03] | Exporting functionality from ([`string-reverse.wasm`][comp-sreverse]) | +| [04 - Importing and reusing components][04] | Importing and Exporting advanced functionality ([`string-reverse-upper.wasm`][comp-sreverse-upper]) | +| | | + +[00]: ./00-tooling-setup.md +[01]: ./01-building-a-component-with-jco.md +[02]: ./02-running-components-in-js.md +[03]: ./03-exporting-functionality-with-rich-types.md +[04]: ./04-importing-and-reusing-components.md + +[comp-add]: ../components/add +[comp-sreverse]: ../components/string-reverse +[comp-sreverse-upper]: ../components/string-reverse-upper