diff --git a/.gitattributes b/.gitattributes index a0596a9f..138c0689 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,11 +4,6 @@ # Ensure binary files aren't considered as text *.wasm binary -# Genrated code from cargo-component -component-model/examples/tutorial/*/src/bindings.rs linguist-generated - # Generated code from JS tutorial component-model/examples/tutorial/jco/package-lock.json linguist-generated -Cargo-component.lock linguist-language=toml - diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3fbc60a9..d188de05 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -35,22 +35,20 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Rust - run: rustup update stable --no-self-update - - - uses: taiki-e/cache-cargo-install-action@5c9abe9a3f79d831011df7c47177debbeb320405 # v2.1.2 - with: - tool: cargo-component + run: | + rustup update stable --no-self-update + rustup target add wasm32-wasip2 - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 with: shared-key: ${{ hashFiles('${{ matrix.projects.folder }}/Cargo.lock') }} - - name: Cargo bulid the package + - name: Cargo build the package working-directory: ${{ matrix.projects.folder }} if: ${{ matrix.projects.type != 'component' }} run: cargo build - - name: Cargo component build + - name: Cargo build working-directory: ${{ matrix.projects.folder }} if: ${{ matrix.projects.type == 'component' }} - run: cargo component build + run: cargo build diff --git a/component-model/book.toml b/component-model/book.toml index d72758b8..74f0eaf2 100644 --- a/component-model/book.toml +++ b/component-model/book.toml @@ -10,6 +10,10 @@ git-repository-url = "https://github.com/bytecodealliance/component-docs/tree/ma edit-url-template = "https://github.com/bytecodealliance/component-docs/tree/main/component-model/{path}" additional-css = ["theme/head.hbs"] +[output.html.fold] +enable = true +level = 1 + [output.html.redirect] "/creating-and-consuming/composing.html" = "/composing-and-distributing/composing.html" "/creating-and-consuming/distributing.html" = "/composing-and-distributing/distributing.html" @@ -18,6 +22,15 @@ additional-css = ["theme/head.hbs"] "/creating-and-consuming.html" = "/language-support.html" "/runtimes/wasmtime.html" = "/running-components/wasmtime.html" "/runtimes/jco.html" = "/running-components/jco.html" +"/language-support/csharp.html" = "/language-support/building-a-simple-component/csharp.html" +"/language-support/c.html" = "/language-support/building-a-simple-component/c.html" +"/language-support/go.html" = "/language-support/building-a-simple-component/go.html" +"/language-support/javascript.html" = "/language-support/building-a-simple-component/javascript.html" +"/language-support/moonbit.html" = "/language-support/building-a-simple-component/moonbit.html" +"/language-support/other-languages.html" = "/language-support/building-a-simple-component/other-languages.html" +"/language-support/python.html" = "/language-support/building-a-simple-component/python.html" +"/language-support/rust.html" = "/language-support/building-a-simple-component/rust.html" +"/language-support/wat.html" = "/language-support/building-a-simple-component/wat.html" [preprocessor.alerts] diff --git a/component-model/examples/tutorial/README.md b/component-model/examples/tutorial/README.md index 54c37f2e..86f5083e 100644 --- a/component-model/examples/tutorial/README.md +++ b/component-model/examples/tutorial/README.md @@ -42,14 +42,14 @@ To expand the exercise to add more components, add another operator world, expan ## Building and running the example -Use [`cargo-component`](https://github.com/bytecodealliance/cargo-component) and [`wac`](https://github.com/bytecodealliance/wac) to build and compose the calculator component. +Use [`wac`](https://github.com/bytecodealliance/wac) to build and compose the calculator component. ```sh -(cd calculator && cargo component build --release) -(cd adder && cargo component build --release) -(cd command && cargo component build --release) -wac plug calculator/target/wasm32-wasip1/release/calculator.wasm --plug adder/target/wasm32-wasip1/release/adder.wasm -o composed.wasm -wac plug command/target/wasm32-wasip1/release/command.wasm --plug composed.wasm -o final.wasm +(cd calculator && cargo build --target=wasm32-wasip2 --release) +(cd adder && cargo build --target=wasm32-wasip2 --release) +(cd command && cargo build --target=wasm32-wasip2 --release) +wac plug calculator/target/wasm32-wasip2/release/calculator.wasm --plug adder/target/wasm32-wasip2/release/adder.wasm -o composed.wasm +wac plug command/target/wasm32-wasip2/release/command.wasm --plug composed.wasm -o final.wasm ``` Now, run the component with Wasmtime: @@ -67,9 +67,9 @@ wasmtime run final.wasm 1 2 add ```sh mkdir -p deps/docs -cp adder/target/wasm32-wasip1/release/adder.wasm deps/docs/adder-impl.wasm -cp calculator/target/wasm32-wasip1/release/calculator.wasm deps/docs/calculator-impl.wasm -cp command/target/wasm32-wasip1/release/command.wasm deps/docs/command-impl.wasm +cp adder/target/wasm32-wasip2/release/adder.wasm deps/docs/adder-impl.wasm +cp calculator/target/wasm32-wasip2/release/calculator.wasm deps/docs/calculator-impl.wasm +cp command/target/wasm32-wasip2/release/command.wasm deps/docs/command-impl.wasm ``` Now we are ready to construct a WAC file to define our composition. Ours instantiates our three components, declaring @@ -84,12 +84,12 @@ package example:composition; let adder-instance = new docs:adder-impl { }; // Instantiate the calculator-impl component that implements the calculator world. -// In the `new` expression, specify the source of the `add` import to be `adder-instance`'s `add` export. +// In the `new` expression, specify the source of the `add` import to be `adder-instance`'s `add` export. let calculator-instance = new docs:calculator-impl { add: adder-instance.add }; // Instantiate a command-impl component that implements the app world. -// The command component might import other interfaces, such as WASI interfaces, but we want to leave -// those as imports in the final component, so supply `...` to allow those other imports to remain unresolved. +// The command component might import other interfaces, such as WASI interfaces, but we want to leave +// those as imports in the final component, so supply `...` to allow those other imports to remain unresolved. // The command's exports (in this case, `wasi:cli/run`) remain unaffected in the resulting instance. let command-instance = new docs:command-impl { calculate: calculator-instance.calculate,... }; @@ -101,12 +101,12 @@ export command-instance["wasi:cli/run@0.2.0"]; Now, perform your composition by passing the WAC file to `wac compose`. ```sh -wac compose composition.wac -o final.wasm +wac compose composition.wac -o final.wasm ``` > Note, instead of moving all the components to a `deps/docs` directory, you can pass the paths to the components inline > ```sh -> wac compose --dep docs:adder-impl=./adder/target/wasm32-wasip1/release/adder.wasm --dep docs:calculator-impl=./calculator/target/wasm32-wasip1/release/calculator.wasm --dep docs:command-impl=./command/target/wasm32-wasip1/release/command.wasm -o final.wasm composition.wac +> wac compose --dep docs:adder-impl=./adder/target/wasm32-wasip2/release/adder.wasm --dep docs:calculator-impl=./calculator/target/wasm32-wasip2/release/calculator.wasm --dep docs:command-impl=./command/target/wasm32-wasip2/release/command.wasm -o final.wasm composition.wac > ``` Run the component with Wasmtime: diff --git a/component-model/examples/tutorial/adder/.cargo/config.toml b/component-model/examples/tutorial/adder/.cargo/config.toml new file mode 100644 index 00000000..dac24597 --- /dev/null +++ b/component-model/examples/tutorial/adder/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasip2" \ No newline at end of file diff --git a/component-model/examples/tutorial/adder/Cargo.lock b/component-model/examples/tutorial/adder/Cargo.lock index bff0abf6..696e0cfc 100644 --- a/component-model/examples/tutorial/adder/Cargo.lock +++ b/component-model/examples/tutorial/adder/Cargo.lock @@ -6,20 +6,418 @@ version = 4 name = "adder" version = "0.1.0" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + [[package]] name = "bitflags" -version = "2.4.0" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "wit-bindgen-rt" -version = "0.37.0" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc801b991c56492f87ab3086e786468f75c285a4d73017ab0ebc2fa1aed5d82c" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.238.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50143b010bdc3adbd16275710f9085cc80d9c12cb869309a51a98ce2ff96558e" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.238.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a587a83ac49c2feb922b7ec5d504419320d5da41cf0726f44b2968c78fa2ee2a" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.238.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ad4ca2ecb86b79ea410cd970985665de1d05774b7107b214bc5852b1bcbad7" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +dependencies = [ + "bitflags", + "futures", + "once_cell", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df0102d506f94732b6b517015e7e951293ed51e98f1615f15056e5e1a7a4d67" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e128967ed7895a41c5541bd8cd2875472caad6434e6699c1b269302a0d60b39" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea08d8273179bf25a7aa87fc498380f7a7f56a2df93cd1135d0ca36af1eae962" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.238.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d577b6b6ca3d05cf2a0367e85b1cdfb269155022ba272ae5a0e14c1e1cb59e4d" +dependencies = [ + "anyhow", "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.238.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d28fd1ea7579c62574b01b413d80293a0a3f3076d387752cd823a3b0e43e96f0" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] diff --git a/component-model/examples/tutorial/adder/Cargo.toml b/component-model/examples/tutorial/adder/Cargo.toml index 7578216a..4df437f7 100644 --- a/component-model/examples/tutorial/adder/Cargo.toml +++ b/component-model/examples/tutorial/adder/Cargo.toml @@ -1,19 +1,11 @@ [package] name = "adder" version = "0.1.0" -edition = "2021" - -[dependencies] -wit-bindgen-rt = { version = "0.37.0", features = ["bitflags"] } +edition = "2024" [lib] crate-type = ["cdylib"] -[package.metadata.component] -package = "docs:adder" - -[package.metadata.component.dependencies] +[dependencies] +wit-bindgen = "0.45.0" -[package.metadata.component.target] -path = "../wit/adder" -world = "adder" diff --git a/component-model/examples/tutorial/adder/src/lib.rs b/component-model/examples/tutorial/adder/src/lib.rs index 20f59a38..1b098fa8 100644 --- a/component-model/examples/tutorial/adder/src/lib.rs +++ b/component-model/examples/tutorial/adder/src/lib.rs @@ -1,21 +1,29 @@ -#[allow(warnings)] -mod bindings; +mod bindings { + //! This module contains generated code for implementing + //! the `adder` world in `wit/world.wit`. + //! + //! The `path` option is actually not required, + //! as by default `wit_bindgen::generate` will look + //! for a top-level `wit` directory and use the files + //! (and interfaces/worlds) there-in. -// The comments that follow the `use` declaration below -// correlate the rust module path segments with their -// `world.wit` counterparts: -use bindings::exports::docs::adder::add::Guest; -// <- items bundled with `export` keyword -// <- package namespace -// <- package -// <- interface name + // The line below will be expanded as Rust code containing + wit_bindgen::generate!({ + path: "wit/adder/world.wit", + }); -struct Component; + // In the lines below we use the generated `export!()` macro re-use and + use super::AdderComponent; + export!(AdderComponent); +} + +/// Struct off of which the implementation will hang +/// +/// The name of this struct is not significant. +struct AdderComponent; -impl Guest for Component { +impl bindings::exports::docs::adder::add::Guest for AdderComponent { fn add(x: u32, y: u32) -> u32 { x + y } } - -bindings::export!(Component with_types_in bindings); diff --git a/component-model/examples/tutorial/adder/wit b/component-model/examples/tutorial/adder/wit new file mode 120000 index 00000000..8c60207d --- /dev/null +++ b/component-model/examples/tutorial/adder/wit @@ -0,0 +1 @@ +../wit \ No newline at end of file diff --git a/component-model/examples/tutorial/calculator/.cargo/config.toml b/component-model/examples/tutorial/calculator/.cargo/config.toml new file mode 100644 index 00000000..dac24597 --- /dev/null +++ b/component-model/examples/tutorial/calculator/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasip2" \ No newline at end of file diff --git a/component-model/examples/tutorial/calculator/src/bindings.rs b/component-model/examples/tutorial/calculator/src/bindings.rs index ec0a2ec4..5f5c2dcb 100644 --- a/component-model/examples/tutorial/calculator/src/bindings.rs +++ b/component-model/examples/tutorial/calculator/src/bindings.rs @@ -201,9 +201,7 @@ macro_rules! __export_calculator_impl { #[doc(inline)] pub(crate) use __export_calculator_impl as export; #[cfg(target_arch = "wasm32")] -#[unsafe( - link_section = "component-type:wit-bindgen:0.41.0:docs:calculator@0.1.0:calculator:encoded world" -)] +#[unsafe(link_section = "component-type:wit-bindgen:0.41.0:docs:calculator@0.1.0:calculator:encoded world")] #[doc(hidden)] #[allow(clippy::octal_escapes)] pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 308] = *b"\ diff --git a/component-model/examples/tutorial/jco/README.md b/component-model/examples/tutorial/jco/README.md index 37875b10..f774d3c8 100644 --- a/component-model/examples/tutorial/jco/README.md +++ b/component-model/examples/tutorial/jco/README.md @@ -3,7 +3,7 @@ This is a `node` CLI and browser based example implementation of running a component that exports the `calculate` interface from a JavaScript application. It uses [`jco`](https://bytecodealliance.github.io/jco/) to generate JavaScript bindings and shows how the same component can be executed in the browser or locally with Node. For another example of using `jco` with components in multiple environments, see the [`jco` example](https://github.com/bytecodealliance/jco/blob/main/docs/src/example.md). ```sh -# Wasm referenced here was generated by cargo component. +# Wasm referenced here was generated by the Rust WebAssembly toolchain. # See top-level README for commands to generate it. # # We want to *omit* wasm requiring Wasi, diff --git a/component-model/src/SUMMARY.md b/component-model/src/SUMMARY.md index 4dc11ece..7601aa42 100644 --- a/component-model/src/SUMMARY.md +++ b/component-model/src/SUMMARY.md @@ -17,15 +17,26 @@ # Using WebAssembly Components - [Creating Components](./language-support.md) - - [C/C++](./language-support/c.md) - - [C#](./language-support/csharp.md) - - [Go](./language-support/go.md) - - [JavaScript](./language-support/javascript.md) - - [Python](./language-support/python.md) - - [Rust](./language-support/rust.md) - - [MoonBit](./language-support/moonbit.md) - - [WebAssembly Text Format (WAT)](./language-support/wat.md) - - [Other Languages](./language-support/other-languages.md) + - [Building a simple component](./building-a-simple-component.md) + - [C/C++](./language-support/building-a-simple-component/c.md) + - [C#](./language-support/building-a-simple-component/csharp.md) + - [Go](./language-support/building-a-simple-component/go.md) + - [JavaScript](./language-support/building-a-simple-component/javascript.md) + - [Python](./language-support/building-a-simple-component/python.md) + - [Rust](./language-support/building-a-simple-component/rust.md) + - [MoonBit](./language-support/building-a-simple-component/moonbit.md) + - [WebAssembly Text Format (WAT)](./language-support/building-a-simple-component/wat.md) + - [Other Languages](./language-support/building-a-simple-component/other-languages.md) + - [Importing and reusing components](./importing-and-reusing-components.md) + - [Rust](./language-support/importing-and-reusing-components/rust.md) + - [Javascript](./language-support/importing-and-reusing-components/javascript.md) + - [Other Languages](./language-support/importing-and-reusing-components/other-languages.md) + - [Creating runnable components](./creating-runnable-components.md) + - [Rust](./language-support/creating-runnable-components/rust.md) + - [Javascript](./language-support/creating-runnable-components/javascript.md) + - [Other languages](./language-support/creating-runnable-components/other-languages.md) + - [Using WIT resources](./using-wit-resources.md) + - [Rust](./language-support/using-wit-resources/rust.md) - [Running Components](./running-components.md) - [Wasmtime](./running-components/wasmtime.md) - [jco](./running-components/jco.md) diff --git a/component-model/src/building-a-simple-component.md b/component-model/src/building-a-simple-component.md new file mode 100644 index 00000000..b1b75197 --- /dev/null +++ b/component-model/src/building-a-simple-component.md @@ -0,0 +1,26 @@ +# Building a simple component + +This section contains guides on how to build a simple WebAssembly component that implements an adder, +with the following [WIT][docs-wit] interface: + +```wit +{{#include ../examples/tutorial/wit/adder/world.wit}} +``` + +## Languages + +This guide is implemented for various languages: + +| Language | +|----------------------------------------------------------------------------------------| +| [C/C++](./language-support/building-a-simple-component/c.md) | +| [C#](./language-support/building-a-simple-component/csharp.md) | +| [Go](./language-support/building-a-simple-component/go.md) | +| [JavaScript](./language-support/building-a-simple-component/javascript.md) | +| [Python](./language-support/building-a-simple-component/python.md) | +| [Rust](./language-support/building-a-simple-component/rust.md) | +| [MoonBit](./language-support/building-a-simple-component/moonbit.md) | +| [WebAssembly Text Format (WAT)](./language-support/building-a-simple-component/wat.md) | +| [Other Languages](./language-support/building-a-simple-component/other-languages.md) | + +[docs-wit]: ./design/wit.md diff --git a/component-model/src/composing-and-distributing/composing.md b/component-model/src/composing-and-distributing/composing.md index dad522e6..c464b523 100644 --- a/component-model/src/composing-and-distributing/composing.md +++ b/component-model/src/composing-and-distributing/composing.md @@ -62,8 +62,6 @@ Here are some tips to avoid or diagnose errors: because `regex` doesn't have any dependencies, let alone ones that `validator` can satisfy. * Composition cares about interface versions, and current tools are inconsistent about when they infer or inject versions. - For example, if a Rust component exports `test:mypackage`, - `cargo component build` will decorate this with the crate version, e.g. `test:mypackage@0.1.0`. If another Rust component _imports_ an interface from `test:mypackage`, that won't match `test:mypackage@0.1.0`. You can use [`wasm-tools component wit`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) to view the imports and exports embedded in the `.wasm` files and check whether they match up. @@ -155,3 +153,5 @@ You can compose components visually using the builder app at [wasmbuilder.app](h 5. When you have connected all the imports and exports that you want, click the Download Component button to download the composed component as a `.wasm` file. + +[!NOTE]: # diff --git a/component-model/src/creating-runnable-components.md b/component-model/src/creating-runnable-components.md new file mode 100644 index 00000000..f89971fa --- /dev/null +++ b/component-model/src/creating-runnable-components.md @@ -0,0 +1,60 @@ +# Creating Runnable Components + +This section contains language-specific guides on how to create runnable components. + +## Running arbitrary functions from reactor (library-like) components + +In practice, any interface that is exported from a WebAssembly component can be run by either: + +- Creating a custom host/component that imports and reuses the functionality +- Using high level generic tooling like `wasmtime run --invoke` + +For example, given a WebAssembly component which satisfies the following WIT: + +```wit +{{#include ../examples/tutorial/wit/adder/world.wit}} +``` + +Use of the exported `add` function inside the `add` interface requires writing a host or other component that is +built to import and use that functionality. This is exemplified by the [`example-host` available in this repo][example-host]. + +Alternatively tooling that works generically over components `wasmtime run --invoke`: + +```sh +wasmtime run --invoke 'add(1, 2)' add.component.wasm +``` + +Wasmtime contains code that can generically interpret exports, convert arguments to WebAssembly arguments, and execute +an existing component dynamically. + +[example-host](https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/example-host/README.md) + +## Creating components that behave like binaries + +While running arbitrary functions require either a custom host/platform or a dynamic tool like `wasmtime run --invoke`, +components that are treatable as binaries (i.e. a CLI application) can also be built. + +At a high level there are at least two ways to create components that are more like binaries than libraries +(i.e. that are easy to run from a tool like `wasmtime run`): + +1. Creating a "command" component +2. Exporting the [`wasi:cli/run` interface][wasi-cli-run] + +While command components simply have a `_start` export, components that export the [`wasi:cli/run` run interface][wasi-cli-iface-run] +are able to make use of more robust standardized interfaces that mimic a CLI environment like stdout, stderr, via WASI. + +This section explores how to create components that are trivially runanbel (like binaries) in relevant languages. + +[wasi-cli-iface-run]: https://github.com/WebAssembly/wasi-cli/tree/main/wit/run.wit + +## Languages + +This guide is implemented for various languages: + +| Language | +|---------------------------------------------------------------------------------------| +| [Rust](./language-support/creating-runnable-components/rust.md) | +| [Javascript](./language-support/creating-runnable-components/javascript.md) | +| [Other Languages](./language-support/creating-runnable-components/other-languages.md) | + +[docs-wit]: ./design/wit.md diff --git a/component-model/src/importing-and-reusing-components.md b/component-model/src/importing-and-reusing-components.md new file mode 100644 index 00000000..1a32e332 --- /dev/null +++ b/component-model/src/importing-and-reusing-components.md @@ -0,0 +1,28 @@ +# Importing and reusing components + +This section contains language-specific guides on how to reuse existing WebAssembly +components, in particular using an `adder` component to complete a `calculator` component. + +The `adder` component has the following [WIT][docs-wit] interface: + +```wit +{{#include ../examples/tutorial/wit/adder/world.wit}} +``` + +The `calculator` component has the following interface: + +```wit +{{#include ../examples/tutorial/wit/calculator/world.wit}} +``` + +## Languages + +This guide is implemented for various languages: + +| Language | +|-------------------------------------------------------------------------------------------| +| [Rust](./language-support/importing-and-reusing-components/rust.md) | +| [Javascript](./language-support/importing-and-reusing-components/javascript.md) | +| [Other languages](./language-support/importing-and-reusing-components/other-languages.md) | + +[docs-wit]: ./design/wit.md diff --git a/component-model/src/introduction.md b/component-model/src/introduction.md index 992e4019..3dad3d73 100644 --- a/component-model/src/introduction.md +++ b/component-model/src/introduction.md @@ -26,13 +26,13 @@ This documentation is aimed at _users_ of the component model: developers of lib [Interfaces]: ./design/interfaces.md [Worlds]: ./design/worlds.md -[C/C++]: ./language-support/c.md -[C#]: ./language-support/csharp.md -[Go]: ./language-support/go.md -[JavaScript]: ./language-support/javascript.md -[Python]: ./language-support/python.md -[Rust]: ./language-support/rust.md -[MoonBit]: ./language-support/moonbit.md +[C/C++]: ./language-support/building-a-simple-component/c.md +[C#]: ./language-support/building-a-simple-component/csharp.md +[Go]: ./language-support/building-a-simple-component/go.md +[JavaScript]: ./language-support/building-a-simple-component/javascript.md +[Python]: ./language-support/building-a-simple-component/python.md +[Rust]: ./language-support/building-a-simple-component/rust.md +[MoonBit]: ./language-support/building-a-simple-component/moonbit.md [Composing]: ./composing-and-distributing/composing.md [Running]: ./running-components.md diff --git a/component-model/src/language-support.md b/component-model/src/language-support.md index 67e52941..8b2573f5 100644 --- a/component-model/src/language-support.md +++ b/component-model/src/language-support.md @@ -23,22 +23,22 @@ The last section, on WebAssembly Text Format (WAT), details how to write WebAssembly components by hand, without using a higher-level language front-end. - - [C/C++ Tooling](./language-support/c.md) - - [Building a Component with `wit-bindgen` and `wasm-tools`](./language-support/c.md#building-a-component-with-wit-bindgen-and-wasm-tools) - - [Running a Component from C/C++ Applications](./language-support/c.md#running-a-component-from-cc-applications) - - [C# Tooling](./language-support/csharp.md) - - [Go Tooling](./language-support/go.md) - - [JavaScript Tooling](./language-support/javascript.md) - - [Building a Component with `jco`](./language-support/javascript.md#building-a-component-with-jco) - - [Running a Component from JavaScript Applications](./language-support/javascript.md#running-a-component-from-javascript-applications) - - [Python Tooling](./language-support/python.md) - - [Building a Component with `componentize-py`](./language-support/python.md#building-a-component-with-componentize-py) - - [Running components from Python Applications](./language-support/python.md#running-components-from-python-applications) - - [Rust Tooling](./language-support/rust.md) - - [Building a Component with `cargo component`](./language-support/rust.md#building-a-component-with-cargo-component) - - [Running a Component from Rust Applications](./language-support/rust.md#running-a-component-from-rust-appliacations) - - [MoonBit Tooling](./language-support/moonbit.md) - - [WebAssembly Text Format (WAT)](./language-support/wat.md#wat-webassembly-text-format) - - [Building a Component from WAT with `wasm-tools`](./language-support/wat.md#building-a-component-with-wasm-tools) - - [Running a Component with Wasmtime](./language-support/wat.md#running-a-component-with-wasmtime) - - [Other Languages with Component Model Support](./language-support/other-languages.md) + - [C/C++ Tooling](./language-support/building-a-simple-component/c.md) + - [Building a Component with `wit-bindgen` and `wasm-tools`](./language-support/building-a-simple-component/c.md#building-a-component-with-wit-bindgen-and-wasm-tools) + - [Running a Component from C/C++ Applications](./language-support/building-a-simple-component/c.md#running-a-component-from-cc-applications) + - [C# Tooling](./language-support/building-a-simple-component/csharp.md) + - [Go Tooling](./language-support/building-a-simple-component/go.md) + - [JavaScript Tooling](./language-support/building-a-simple-component/javascript.md) + - [Building a Component with `jco`](./language-support/building-a-simple-component/javascript.md#building-a-component-with-jco) + - [Running a Component from JavaScript Applications](./language-support/building-a-simple-component/javascript.md#running-a-component-from-javascript-applications) + - [Python Tooling](./language-support/building-a-simple-component/python.md) + - [Building a Component with `componentize-py`](./language-support/building-a-simple-component/python.md#building-a-component-with-componentize-py) + - [Running components from Python Applications](./language-support/building-a-simple-component/python.md#running-components-from-python-applications) + - [Rust Tooling](./language-support/building-a-simple-component/rust.md) + - [Building a Component](./language-support/building-a-simple-component/rust.md#building-a-component) + - [Running a Component from Rust Applications](./language-support/building-a-simple-component/rust.md#running-a-component-from-rust-appliacations) + - [MoonBit Tooling](./language-support/building-a-simple-component/moonbit.md) + - [WebAssembly Text Format (WAT)](./language-support/building-a-simple-component/wat.md#wat-webassembly-text-format) + - [Building a Component from WAT with `wasm-tools`](./language-support/building-a-simple-component/wat.md#building-a-component-with-wasm-tools) + - [Running a Component with Wasmtime](./language-support/building-a-simple-component/wat.md#running-a-component-with-wasmtime) + - [Other Languages with Component Model Support](./language-support/building-a-simple-component/other-languages.md) diff --git a/component-model/src/language-support/c.md b/component-model/src/language-support/building-a-simple-component/c.md similarity index 96% rename from component-model/src/language-support/c.md rename to component-model/src/language-support/building-a-simple-component/c.md index 446cfb52..3dbc38db 100644 --- a/component-model/src/language-support/c.md +++ b/component-model/src/language-support/building-a-simple-component/c.md @@ -16,7 +16,7 @@ if you're developing your own `.wit` files. For WIT interfaces that are built in to WASI, the code is part of the WebAssembly runtime that you will be using. -C/C++ currently lacks an integrated toolchain (like Rust's [`cargo-component`][cargo-component]). +C/C++ currently lacks an integrated toolchain. However, `wit-bindgen` can generate source-level bindings for Rust, C, Java ([TeaVM](https://teavm.org/)), and [TinyGo](https://tinygo.org/), with the ability to add more language generators in the future. @@ -27,7 +27,6 @@ with the ability to add more language generators in the future. [clang-tgt-wasm32-wasi]: https://clang.llvm.org/docs/ClangCommandLineReference.html#webassembly [llvm]: https://llvm.org/ [wasi]: https://wasi.dev/ -[cargo-component]: https://crates.io/crates/cargo-component [rust]: https://www.rust-lang.org/learn/get-started [sample-wit]: https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/tutorial/wit/adder/world.wit [cargo-config]: https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/example-host/Cargo.toml @@ -41,7 +40,7 @@ First, install the following dependencies: as well as converting between preview1 modules and preview2 components in the optional manual workflow. 3. The [`WASI SDK`](https://github.com/webassembly/wasi-sdk) - * WASI SDK is a WASI enabled C/C++ toolchain which includes a version of the C standard + * WASI SDK is a WASI enabled C/C++ toolchain which includes a version of the C standard library (`libc`) implemented with WASI interfaces, among other artifacts necessary to compile C/C++ to WebAssembly. * On a Linux system, you can skip to the ["Install"](https://github.com/webassembly/wasi-sdk?tab=readme-ov-file#install) section. @@ -92,7 +91,7 @@ Next, create a file named `component.c` with code that implements the `adder` wo that is, code which fulfills the definition of the interface function declared in `adder.h`. ```c -{{#include ../../examples/tutorial/c/adder/component.c}} +{{#include ../../../examples/tutorial/c/adder/component.c}} ``` ## 4. Compile a WebAssembly Preview 2 component with `wasi-sdk`'s `wasm32-wasip2-clang` @@ -232,7 +231,7 @@ For example, if we modify the above code to reference `printf()`, it would compile to a P1 component: ```c -{{#include ../../examples/tutorial/c/adder/component_with_printf.c}} +{{#include ../../../examples/tutorial/c/adder/component_with_printf.c}} ``` However, the module would fail to transform to a P2 component: @@ -315,14 +314,14 @@ The following section requires you to have [a Rust toolchain][rust] installed. > (The `wasmtime` version is specified in [the Cargo configuration file][cargo-config] > for the example host.) -{{#include example-host-part1.md}} +{{#include ../example-host-part1.md}} A successful run should show the following output (of course, the paths to your example host and adder component will vary, and you should substitute `adder.wasm` with `adder.component.wasm` if you followed the manual instructions above): -{{#include example-host-part2.md}} +{{#include ../example-host-part2.md}} ## 7. Run the component from C/C++ Applications @@ -335,7 +334,7 @@ and run by their toolchains, or even composed with a C language command component and run via the `wasmtime` CLI or any other host. -See the [Rust Tooling guide](../language-support/rust.md#running-a-component-from-rust-applications) +See the [Rust Tooling guide](./rust.md#running-a-component-from-rust-applications) for instructions on how to run this component from the Rust `example-host` (replacing the path to `add.wasm` with your `adder.wasm` or `adder.component.wasm` above). diff --git a/component-model/src/language-support/csharp.md b/component-model/src/language-support/building-a-simple-component/csharp.md similarity index 95% rename from component-model/src/language-support/csharp.md rename to component-model/src/language-support/building-a-simple-component/csharp.md index 175be144..afcc258c 100644 --- a/component-model/src/language-support/csharp.md +++ b/component-model/src/language-support/building-a-simple-component/csharp.md @@ -43,7 +43,7 @@ For this example we will use a WIT file containing two worlds Copy and paste the following into a new file called "`wit/component.wit`". ```wit -{{#include ../../examples/tutorial/csharp/adder/world-hostapp.wit}} +{{#include ../../../examples/tutorial/csharp/adder/world-hostapp.wit}} ``` In the `adder.csproj` project file, add a new `` @@ -88,7 +88,7 @@ This is because we've promised an implementation, but haven't yet written one fo To fix this, add the following code in a file called `Component.cs`: ```csharp -{{#include ../../examples/tutorial/csharp/adder/Component.cs}} +{{#include ../../../examples/tutorial/csharp/adder/Component.cs}} ``` Then, we can build our component: @@ -103,12 +103,12 @@ The component will be available at `bin/Debug/net10.0/wasi-wasm/native/adder.was The following section requires you to have [a Rust toolchain][rust] installed. -{{#include example-host-part1.md}} +{{#include ../example-host-part1.md}} A successful run should show the following output (of course, the paths to your example host and adder component will vary): -{{#include example-host-part2.md}} +{{#include ../example-host-part2.md}} [rust]: https://www.rust-lang.org/learn/get-started @@ -135,7 +135,7 @@ cd host-app Copy the following WIT file into a file called `wit/add.wit` in your project: ```wit -{{#include ../../examples/tutorial/csharp/adder/world-hostapp.wit}} +{{#include ../../../examples/tutorial/csharp/adder/world-hostapp.wit}} ``` Add it to your `host-app.csproj` project file as a new `ItemGroup` at the top level: @@ -154,7 +154,7 @@ Now we'll be focusing on the executable side of the application—the `hostapp` Modify `Program.cs` to look like this: ```csharp -{{#include ../../examples/tutorial/csharp/adder/Program.cs}} +{{#include ../../../examples/tutorial/csharp/adder/Program.cs}} ``` Once again, compile your component with `dotnet build`: diff --git a/component-model/src/language-support/go.md b/component-model/src/language-support/building-a-simple-component/go.md similarity index 98% rename from component-model/src/language-support/go.md rename to component-model/src/language-support/building-a-simple-component/go.md index 1f190458..791a69cd 100644 --- a/component-model/src/language-support/go.md +++ b/component-model/src/language-support/building-a-simple-component/go.md @@ -71,7 +71,7 @@ Create a subdirectory called `wit` and paste the following code into a file called `wit/component.wit`: ```wit -{{#include ../../examples/tutorial/go/adder/world2.wit}} +{{#include ../../../examples/tutorial/go/adder/world2.wit}} ``` The line `include wasi:cli/imports@0.2.0` is necessary because @@ -255,7 +255,7 @@ In your `add` directory, create a file called `main.go` and paste the following code into it: ```Go -{{#include ../../examples/tutorial/go/adder/main.go}} +{{#include ../../../examples/tutorial/go/adder/main.go}} ``` Go's `init` function is used to do initialization tasks @@ -315,12 +315,12 @@ The following section requires you to have [a Rust toolchain][rust] installed. To run our add component, we need to use a host program with a WASI runtime that understands the `example` world. -{{#include example-host-part1.md}} +{{#include ../example-host-part1.md}} A successful run should show the following output (of course, the paths to your example host and adder component will vary): -{{#include example-host-part2.md}} +{{#include ../example-host-part2.md}} [example-host]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host [rust]: https://www.rust-lang.org/learn/get-started diff --git a/component-model/src/language-support/building-a-simple-component/javascript.md b/component-model/src/language-support/building-a-simple-component/javascript.md new file mode 100644 index 00000000..bb1c699c --- /dev/null +++ b/component-model/src/language-support/building-a-simple-component/javascript.md @@ -0,0 +1,298 @@ +# JavaScript Tooling + +[WebAssembly][mdn-wasm] was originally developed as a technology for running non-JavaScript +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 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. + +> [!WARNING] +> 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) +> that the component model makes available. + +[emscripten]: https://emscripten.org/ +[ts]: https://typescriptlang.org +[mdn-wasm]: https://developer.mozilla.org/en-US/docs/WebAssembly +[jco]: https://github.com/bytecodealliance/jco +[componentize-js]: https://github.com/bytecodealliance/componentize-js +[sm]: https://github.com/bytecodealliance/StarlingMonkey + +## Installing `jco` + +[`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`. + +## Overview of Building a Component with JavaScript + +Building a WebAssembly component with JavaScript often consists of: + +1. Determining which interface our component will target (i.e. given a [WebAssembly Interface Types ("WIT")][docs-wit] world) +2. Creating the component by writing JavaScript that satisfies the interface +3. Compiling the interface-compliant JavaScript to WebAssembly + +### Building Reactor Components with `jco` + +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. + +[docs-wit]: ../../design/wit.md +[docs-component-model]: ../../design/why-component-model.md + +### What is WIT? + +[WebAssembly Interface Types ("WIT")][docs-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. + +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}} +``` + +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. +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 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 of the package. + +> To learn more about the WIT syntax, check out the [WIT explainer][docs-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 + +## Implementing a JS WebAssembly Component + +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 +{{#include ../../../examples/tutorial/js/adder/adder.js}} +``` + +> [!WARNING] +> 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 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`][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] 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 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`. + +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: + +```console +$ wasm-tools print adder.wasm | head -1 +(component +``` + +> [!WARNING] +> 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`. +> +> 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]: + +{{#include ../example-host-part1.md}} + +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`) +- Calls the `export`ed `add` function inside the `add` interface with arguments +- Prints the result + +The important Rust code looks something like this: + +```rust +let component = Component::from_file(&engine, path).context("Component file not found")?; + +let (instance, _) = Example::instantiate_async(&mut store, &component, &linker) + .await + .context("Failed to instantiate the example world")?; + +instance + .call_add(&mut store, x, y) + .await + .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 [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 +[nodejs]: https://nodejs.org/en +[cargo]: https://doc.rust-lang.org/cargo +[wasi]: https://wasi.dev/ +[wasm-core]: https://webassembly.github.io/spec/core/ +[ffi]: https://en.wikipedia.org/wiki/Foreign_function_interface + +## 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 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 transpile`. +In your `adder` directory, execute: + +```console +jco transpile adder.wasm -o dist/transpiled +``` + +You should see output similar to the following: + +``` + Transpiled JS Component Files: + + - 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] +> 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 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 +using a runtime that supports the [core WebAssembly specification][core-wasm] as implemented for JavaScript. + +To use this component from [Node.js][nodejs], you can write code like the following: + +```mjs +{{#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: + +```json +{{#include ../../../examples/tutorial/js/adder/package.json}} +``` + +> [!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 +``` + +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 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 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/ + +[!TIP]: # +[!NOTE]: # +[!WARNING]: # diff --git a/component-model/src/language-support/moonbit.md b/component-model/src/language-support/building-a-simple-component/moonbit.md similarity index 95% rename from component-model/src/language-support/moonbit.md rename to component-model/src/language-support/building-a-simple-component/moonbit.md index 80481558..25ea431b 100644 --- a/component-model/src/language-support/moonbit.md +++ b/component-model/src/language-support/building-a-simple-component/moonbit.md @@ -76,7 +76,7 @@ mkdir wit Create `wit/world.wit` with the following content: ```wit -{{#include ../../examples/tutorial/wit/adder/world.wit}} +{{#include ../../../examples/tutorial/wit/adder/world.wit}} ``` This WIT definition: @@ -153,7 +153,7 @@ code, which is what we need to complete. Implement the `add` function in `gen/interface/docs/adder/add/stub.mbt`: ```rust -{{#include ../../examples/tutorial/moonbit/adder/stub.mbt}} +{{#include ../../../examples/tutorial/moonbit/adder/stub.mbt}} ``` ## 5. Configure the Build @@ -161,7 +161,7 @@ Implement the `add` function in `gen/interface/docs/adder/add/stub.mbt`: Ensure your `gen/moon.pkg.json` is properly configured for WebAssembly target: ```json -{{#include ../../examples/tutorial/moonbit/adder/moon.pkg.json}} +{{#include ../../../examples/tutorial/moonbit/adder/moon.pkg.json}} ``` ## 5. Build the WebAssembly Component @@ -249,19 +249,19 @@ This allows you to integrate the MoonBit's error handling easier. For example, for the following interface: ```wit -{{#include ../../examples/tutorial/moonbit/adder-with-error/world.wit}} +{{#include ../../../examples/tutorial/moonbit/adder-with-error/world.wit}} ``` Will generate the following type and function ```rust -{{#include ../../examples/tutorial/moonbit/adder-with-error/top.mbt}} +{{#include ../../../examples/tutorial/moonbit/adder-with-error/top.mbt}} ``` which you may use it as: ```rust -{{#include ../../examples/tutorial/moonbit/adder-with-error/stub.mbt}} +{{#include ../../../examples/tutorial/moonbit/adder-with-error/stub.mbt}} ``` ### --ignore-stub diff --git a/component-model/src/language-support/building-a-simple-component/other-languages.md b/component-model/src/language-support/building-a-simple-component/other-languages.md new file mode 100644 index 00000000..1b164b16 --- /dev/null +++ b/component-model/src/language-support/building-a-simple-component/other-languages.md @@ -0,0 +1,14 @@ +# Other Languages with Component Model Support + +This guide is a work in progress and does not example for all langauge toolchains +with WebAssembly component support. + +As more languages continue to add support for the component model, we welcome +contributions of documentation for how to get started using components in those languages. + +## Adding an Example for a Language to this Section + +Are you interested in documenting this section for your language toolchain? +Create a PR to this [repository][repo-pr] that adds the guide (similar to others in this section). + +[repo-pr]: https://github.com/bytecodealliance/component-docs/pulls diff --git a/component-model/src/language-support/python.md b/component-model/src/language-support/building-a-simple-component/python.md similarity index 94% rename from component-model/src/language-support/python.md rename to component-model/src/language-support/building-a-simple-component/python.md index 2b75ddfa..d6f4e45d 100644 --- a/component-model/src/language-support/python.md +++ b/component-model/src/language-support/building-a-simple-component/python.md @@ -17,7 +17,7 @@ Next, create or download the WIT world you would like to target. For this example we will use an [`adder` world][adder-wit] with an `add` function. ```wit -{{#include ../../examples/tutorial/wit/adder/world.wit}} +{{#include ../../../examples/tutorial/wit/adder/world.wit}} ``` Create a new directory for your project and create a subdirectory in it called `wit`. @@ -58,7 +58,7 @@ To implement the `adder` world (in particular the `add` interface that is export put the following code in a file called `app.py`: ```py -{{#include ../../examples/tutorial/python/app.py}} +{{#include ../../../examples/tutorial/python/app.py}} ``` We now can compile our application to a WebAssembly component using the `componentize` subcommand: @@ -77,12 +77,12 @@ Component built successfully The following section requires you to have [a Rust toolchain][rust] installed. -{{#include example-host-part1.md}} +{{#include ../example-host-part1.md}} A successful run should show the following output (of course, the paths to your example host and adder component will vary): -{{#include example-host-part2.md}} +{{#include ../example-host-part2.md}} [rust]: https://www.rust-lang.org/learn/get-started @@ -118,7 +118,7 @@ We can now write a Python program that calls `add`. Copy/paste the following code into a file called `host.py`: ```py -{{#include ../../examples/tutorial/python/host.py}} +{{#include ../../../examples/tutorial/python/host.py}} ``` Run the Python host program: diff --git a/component-model/src/language-support/building-a-simple-component/rust.md b/component-model/src/language-support/building-a-simple-component/rust.md new file mode 100644 index 00000000..3bff6594 --- /dev/null +++ b/component-model/src/language-support/building-a-simple-component/rust.md @@ -0,0 +1,255 @@ +# Building a simple component (Rust) + +[Rust][rust] has first-class support for WebAssembly core and WebAssembly components via the +available targets in the toolchain: + +- `wasm32-unknown-unknown` ([WebAssembly core][wasm-core]) +- `wasm32-wasip1` ([WASI P1][wasip1]) +- `wasm32-wasip2` ([WASI P2][wasip2]) + +[wasm-core]: https://webassembly.github.io/spec/core/ +[wasip1]: https://github.com/WebAssembly/WASI/tree/main/legacy +[wasip2]: https://github.com/WebAssembly/WASI/tree/main/wasip2 + +> [!NOTE] +> To use the targets above, ensure that they are enabled via the Rust toolchain (e.g. `rustup`). +> +> For example, to add the `wasm32-wasip2` target (`rustup toolchain list` can be used to show all available toolchains): +> ``` +> rustup target add wasm32-wasip2 +> ``` + +With built-in support, Rust code (and the standard library) can compile to WebAssembly with native +tooling: + +```sh +cargo build --target wasm32-wasip2 +``` + +> [!WARNING] +> While in the past the use of [`cargo-component`][cargo-component] was recommended, +> the project is in the process of being deprecated as native tooling can be used directly. + +[rust]: https://rust-lang.org +[cargo-component]: https://crates.io/crates/cargo-component + +## 1. Setup + +Install [`wasm-tools`][wasm-tools] to enable introspection and manipulation of WebAssembly binaries: + +```sh +cargo install --locked wasm-tools +``` + +Install [`wasmtime`][wasmtime], a fast and secure runtime for WebAssembly binaries: + +```sh +curl https://wasmtime.dev/install.sh -sSf | bash +``` + +[wasm-tools]: https://github.com/bytecodealliance/wasm-tools#installation +[wasmtime]: https://github.com/bytecodealliance/wasmtime#installation + +## 2. Creating a WebAssembly project in Rust + +Create a new project in Rust with `cargo new`: + +```console +$ cargo new --lib adder + Creating library `adder` package +note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +``` + +When building Rust WebAssembly projects, it is possible to create *either* a binary crate or +a library crate that can compile to WebAssembly (producing a "command" component or a "reactor" +component respectively), in this case opt for a reactor component. + +> [!NOTE] +> The distinction between a command component and a reactor component isn't important yet, +> but they can be considered similar to the difference between a binary and a shared library. + +One thing that we *will* have to add to our Rust project is in `Cargo.toml`, setting the `crate-type`: + +```toml +[lib] +crate-type = ["cdylib"] +``` + +As we are building a reactor component (the "equivalent" of a library of functions), we must use +the [`cdylib`](https://doc.rust-lang.org/reference/linkage.html#r-link.cdylib) (stands for "c dynamic library") crate type that Rust provides. + +## 3. Adding the `add` Interface + +We will create a component in Rust that implements the `add` interface exported by +the [`adder` world][docs-adder] world in the `docs:adder` [WebAssembly Interface types (WIT) package][wit-docs-packages]. + +Create a file called `wit/world.wit` and fill it with the following content: + +```wit +{{#include ../../../examples/tutorial/wit/adder/world.wit}} +``` + +The (WIT) types in this file represent the interface of our component must satisfy (the `adder world`). +We say that our component "exports" the `add` interface (which itself contains a single function `add`). + +Working with these types is similar to other Interface Definition Language (IDL) toolchains (e.g. protobuf), +in that we will need some language level bindings that make the interface easy to implement. + +[wit-docs-packages]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#package-names + +## 4. Generating Bindings for the `adder` Interface + +While the Rust toolchain can compile WebAssembly binaries natively, it cannot (yet) automatically +generate bindings that match our intended (WIT) interface types (`wit/world.wit`). + +We can use [`wit-bindgen`][crates-wit-bindgen] to generate bindings: + +```sh +cargo add wit-bindgen +``` + +> [!NOTE] +> The command above should be run from inside the `adder` directory that was created by +> `cargo new` so as to be sure to add `wit-bindgen` to the dependencies of the right project. Alternatively, you can directly add `wit-bindgen` to the dependencies section of the `Cargo.toml`. +> +> It is also possible to use `wit-bindgen` as a binary via the [`wit-bindgen-cli`][crates-wit-bindgen-cli] +> crate, but here we will focus on a code-first binding build approach. + +Once you have `wit-bindgen` as a part of your Rust project (i.e. in `Cargo.toml`), we can use it to generate Rust code bindings for our WIT interface. Update your `src/lib.rs` file to look like the following: + +```rust +mod bindings { + //! This module contains generated code for implementing + //! the `adder` world in `wit/world.wit`. + //! + //! The `path` option is actually not required, + //! as by default `wit_bindgen::generate` will look + //! for a top-level `wit` directory and use the files + //! (and interfaces/worlds) there-in. + wit_bindgen::generate!({ + path: "wit/world.wit", + }); +} +``` + +Here we create a module called `bindings` that contains the code output by the [`wit_bindgen::generate` macro][rustdoc-wit-bindgen-generate]. +Various `struct`s, `interface`s, `enum`s and more might be generated by `wit_bindgen`, so it's often desirable to sequester those new +types to a module that can be referred to later. + +At present, the code won't *do* much, but that's because we haven't added our implementation yet. + +[crates-wit-bindgen]: https://crates.io/crates/wit-bindgen +[crates-wit-bindgen-cli]: https://crates.io/crates/wit-bindgen-cli +[rustdoc-wit-bindgen-generate]: https://docs.rs/wit-bindgen/latest/wit_bindgen/macro.generate.html + +## 5. Implementing the `adder` world via the generated `Guest` Trait + +We can fill in functionality of the component by implementing `bindings::Guest` trait in `src/lib.rs`. +Your code should look something like the following: + +```rs +{{#include ../../../examples/tutorial/adder/src/lib.rs}} +``` + +There are a few points of note in the code listing above: + +1. The `AdderComponent` struct is introduced, but is only useful as an implementer of the `Guest` trait. +2. The `bindings::exports::docs::adder::add::Guest` trait mirrors the `docs:adder/add` interface that is exported. +3. Given (1) and (2), `AdderComponent` implements (in the WIT sense) the `adder` world, via the generated bindings. +4. The [`export!()` macro][export-macro] is generated by `wit_bindgen::generate!` macro, and does important setup. + - `export!` is easiest used from inside the `bindings` module, *but* we need to refer to the `super::AdderComponent` struct + +> [!NOTE] +> To dive into the code generated by the `wit_bindgen::generate!` macro, you can use the [`cargo-expand` crate][crates-cargo-expand] + +[export-macro]: https://docs.rs/wit-bindgen/latest/wit_bindgen/macro.generate.html#exports-the-export-macro +[crates-cargo-expand]: https://crates.io/crates/cargo-expand + +## 6. Building a Component + +Now, let's build our component, using the native Rust toolchain to build a WASI P2 component. + +```sh +cargo build --target=wasm32-wasip2 +``` + +This performs a debug build, which produces a WebAssembly component to `target/wasm32-wasip2/debug/adder.wasm`: + +```console +du -hs target/wasm32-wasip2/debug/adder.wasm +3.3M target/wasm32-wasip2/debug/adder.wasm +``` + +3 megabytes is *large* for a WebAssembly component for a compiled language like Rust. Let's compile in release mode, +performing more optimizations: + +```sh +cargo build --target=wasm32-wasip2 --release +``` + +After compiling in release mode, we get a much smaller binary: + +```console +$ du -hs target/wasm32-wasip2/release/adder.wasm +16K target/wasm32-wasip2/release/adder.wasm +``` + +Note that you can use many of the optimization options normally available with the Rust toolchain to control binary output. + +> [!WARNING] +> Building with `--release` removes all debug-related information from the resulting `.wasm` file. +> +> When prototyping or testing locally, you might want to avoid `--release` to +> obtain useful backtraces in case of errors (for example, with +> [`wasmtime::WasmBacktraceDetails::Enable`](https://docs.rs/wasmtime/latest/wasmtime/enum.WasmBacktraceDetails.html#variant.Enable)). +> Note: the resulting `.wasm` file will be considerably larger (likely 4MB+). + +## 7. Inspecting the built component + +Now that we have a WIT binary, we can introspect it using WebAssembly component tooling. + +For example, we can `wasm-tools` to output the WIT package of the component, because WebAssembly +components are self-documenting, and contain this information: + +```sh +wasm-tools component wit target/wasm32-wasip2/release/adder.wasm +``` + +The command above should produce the output below: + +```wit +package root:component; + +world root { + export docs:adder/add@0.1.0; +} +package docs:adder@0.1.0 { + interface add { + add: func(x: u32, y: u32) -> u32; + } +} +``` + +## 8. Running the `adder` Component + +To verify that our component works, let's run it from a Rust application that knows how to run a +component targeting the [`adder` world](#adding-the-wit-world). + +The application uses [`wasmtime`][crates-wasmtime] to generate Rust "host"/"embedder" bindings, +bring in WASI worlds, and execute the component. + +With the [`component-docs` repository][repo-component-docs] cloned locally, run the following: + +```console +$ cd examples/example-host +$ cargo run --release -- 1 2 ../add/target/wasm32-wasip1/release/adder.wasm +1 + 2 = 3 +``` + +With this, we have successfully built and run a basic WebAssembly component with Rust 🎉 + +[crates-wasmtime]: https://crates.io/crates/wasmtime +[repo-component-docs]: https://github.com/bytecodealliance/component-docs +[docs-adder]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit +[!NOTE]: # +[!WARNING]: # diff --git a/component-model/src/language-support/wat.md b/component-model/src/language-support/building-a-simple-component/wat.md similarity index 95% rename from component-model/src/language-support/wat.md rename to component-model/src/language-support/building-a-simple-component/wat.md index 039e175a..9cd38030 100644 --- a/component-model/src/language-support/wat.md +++ b/component-model/src/language-support/building-a-simple-component/wat.md @@ -12,7 +12,7 @@ It's useful for writing small examples for testing and experimentation. Here's an example of a module expressed in WAT: ```wat -{{#include ../../examples/tutorial/wat/adder/add.wat}} +{{#include ../../../examples/tutorial/wat/adder/add.wat}} ``` The module contains two top-level declarations, a function and an export. @@ -64,7 +64,7 @@ and simply adds two numbers. Create a file called `adder.wit` whose contents are as follows: ```wit - {{#include ../../examples/tutorial/wit/adder/world.wit}} + {{#include ../../../examples/tutorial/wit/adder/world.wit}} ``` 3. Define an `add` core module in WAT that exports an `add` function that adds two parameters. @@ -72,7 +72,7 @@ and simply adds two numbers. (the same as the example in the WAT section): ```wat -{{#include ../../examples/tutorial/wat/adder/add.wat}} +{{#include ../../../examples/tutorial/wat/adder/add.wat}} ``` 4. Use `wasm-tools` to create a binary core module with component metadata embedded inside it: diff --git a/component-model/src/language-support/creating-runnable-components/javascript.md b/component-model/src/language-support/creating-runnable-components/javascript.md new file mode 100644 index 00000000..6055a24e --- /dev/null +++ b/component-model/src/language-support/creating-runnable-components/javascript.md @@ -0,0 +1,28 @@ +# Creating Runnable Components (Javascript) + +## Exporting `wasi:cli/run` to create + +Components created with `jco` can export the `wasi:cli/run` interface, similarly to WebAssembly components written in other languages. + +Exporting the `wasi:cli/run` interfaces enables ecosystem tooling to interoperate with (and run) the component you've built + (e.g. `wasmtime run`). Components that conform to `wasi:cli/run` can be very concise. + +For example: + +```js +export const run = { + run() { + console.log("Hello World!"); + } +} +``` + +The above component can be made recognizable as "runnable" to `wasi:cli`-aware tooling with the following WIT: + +```wit +package runnable:js-component; + +world component { + export wasi:cli/run@0.2.4; +} +``` diff --git a/component-model/src/language-support/creating-runnable-components/other-languages.md b/component-model/src/language-support/creating-runnable-components/other-languages.md new file mode 100644 index 00000000..288a22ca --- /dev/null +++ b/component-model/src/language-support/creating-runnable-components/other-languages.md @@ -0,0 +1,19 @@ +# Creating Runnable Components (Other Languages) + +This guide is a work in progress and does not have examples for all language toolchains with components support. + +For languages not listed in this guide, it is often possible to create runnable components by +applying the core concepts found in other guides, using the help of the available WebAssembly +toolchain. + +Generally, WebAssembly toolchains in the language in question may contain a way to: + +1. Create a WebAssembly component with the `_start` export (a "command" component) +2. Create a component that exports the `wasi:cli/run` interface + +## Adding an Example for a Language to this Section + +Are you interested in documenting this section for your language toolchain? Create a PR to this [repository][repo-pr] that adds +the guide (similar to others in this section). + +[repo-pr]: https://github.com/bytecodealliance/component-docs/pulls diff --git a/component-model/src/language-support/creating-runnable-components/rust.md b/component-model/src/language-support/creating-runnable-components/rust.md new file mode 100644 index 00000000..996c19dd --- /dev/null +++ b/component-model/src/language-support/creating-runnable-components/rust.md @@ -0,0 +1,192 @@ +# Creating Runnable Components (Rust) + +## Creating a command component + +A _command_ is a component with a specific export that allows it to be executed directly by `wasmtime` +(or other `wasi:cli` hosts). In Rust terms, it's the equivalent of an application (`bin`) package with +a `main` function, instead of a library crate (`lib`) package. + +Command components work by including a WebAssembly core export `_start` that indicates the component +has a natural `main`-like starting point. + +### 1. Create a new Rust binary project + +To create a command with cargo, run: + +```sh +cargo new runnable-example +``` + +Unlike library components, this does _not_ have the `--lib` flag (`--bin` is the default for `cargo new`). + +The created Rust source file is called `main.rs` instead of `lib.rs`, and contains a `main` function. + +You can write Rust in this project, just as you normally would, including importing your own or third-party crates. + +> All the crates that make up your project are linked together at build time, and compiled to a _single_ Wasm component. In this case, all the linking is happening at the Rust level: no WITs or component composition is involved. Only if you import Wasm interfaces do WIT and composition come into play. + +### 2. Write the relevant Rust + +The following code can be inserted into `runnable-example/src/main.rs`: + +```rust +pub fn main() { + eprintln!("Hello World!"); +} +``` + +### 3. Build the component + +To build the component, use `cargo`: + +```sh +cargo build --target=wasm32-wasip2 +``` + +The component can also be built in release mode: + +```console +cargo build --target=wasm32-wasip2 --release +``` + +### 4. Run the component with `wasmtime` + +To run your command component: + +```sh +wasmtime run ./target/wasm32-wasip2/debug/runnable-example.wasm +``` + +## Enabling a library component to be run via the `wasi:cli/run` interface + +Sometimes, it is useful to create a component that can *both* be used as a library (via +exported interface) and executed standalone like a command component. + +While reactor (library-like) components export interfaces that are meant to be used directly, +they can *also* export the [`wasi:cli/run` interface][wasi-cli-iface-run] from [WASI CLI][wasi-cli], +and signal to consumers that the library can also be run similarly to a binary that would run via a +command line interface. + +Unlike command components, library components have no `_start`, but by exporting the `wasi:cli/run` interface, +tooling that recognizes these exports can easily execute a given WebAssembly binary (e.g. `wasmtime run`). + +[wasi-cli-iface-run]: https://github.com/WebAssembly/wasi-cli/tree/main/wit/run.wit +[wasi-cli]: https://github.com/WebAssembly/wasi-cli + +### 1. Create a new Rust library project + +To build a simple component that exports `wasi:cli/run`, first create a new Rust project: + +```sh +cargo new --lib runnable-example +``` + +After creating the new project, ensure it is a `cdylib` crate by updating `runnable-example/Cargo.toml` and adding +the following lines: + +```toml +[lib] +crate-type = ['cdylib'] +``` + +We'll also be generating Rust bindings from WIT interfaces, so add `wit-bindgen`: + +```sh +cargo add wit-bindgen +``` + +### 2. Add the appropriate WIT interfaces + +Then, add the appropriate WIT interfaces. For example a simple component that prints "Hello World", add the following +contents to `runnable-example/wit/component.wit`: + +```wit +package example:runnable; + +interface greet { + greet: func(name: string) -> string; +} + +world greeter { + export greet; + export wasi:cli/run@0.2.7; +} +``` + +Building a library component this way does two things: + +- Enables *other* components/hosts to use the `greet` interface +- Exposes an interface (`wasi:cli/run`) that indicates this component can be run like a CLI + - Note that no guarantees are made about what the component *does* when it runs + +While we created `greet`, `wasi:cli` is a well-known interface. We can resolve this interface to local WIT by +using `wkg`: + +```sh +wkg wit fetch +``` + +At this point, you should have a `wit` folder with a `deps` subfolder and your original `component.wit`. + +The component we will create to satisfy the WIT above can be used as a library, as other components +or platforms can use the `greet` interface export. More importantly, the component can *also* be +recognized as a generically runnable component thanks to `wasi:cli/run`, so it can work +with any tooling (ex. `wasmtime run`) that supports/recognizes the `wasi:cli` interface. + +[!WARNING]: # + +### 3. Write the code for the component + +The following code can be inserted into `runnable-example/src/lib.rs`: + +```rust +mod bindings { + use super::Component; + + wit_bindgen::generate!(); + + export!(Component); +} + +/// Component off of which implementation will hang (this can be named anything) +struct Component; + +/// Implementation for the `greet` interface export +impl bindings::exports::example::runnable::greet::Guest for Component { + fn greet(name: String) -> String { + format!("Hello {name}!") + } +} + +/// Implementation for `wasi:cli/run` interface export +impl bindings::exports::wasi::cli::run::Guest for Component { + fn run() -> Result<(), ()> { + eprintln!("Hello World!"); + Ok(()) + } +} +``` + +### 4. Build the component + +To build the component, use `cargo`: + +```sh +cargo build --target=wasm32-wasip2 +``` + +The component can also be built in release mode: + +```console +cargo build --target=wasm32-wasip2 --release +``` + +### 5. Run the component with `wasmtime` + +You can run the component with `wasmtime`, and unlike a generic reactor component, you do not need to specify +the interface and function to run (`wasi:cli/run` is detected and used automatically): + +```console +$ wasmtime run target/wasm32-wasip2/runnable-example.wasm +Hello World! +``` diff --git a/component-model/src/language-support/importing-and-reusing-components/javascript.md b/component-model/src/language-support/importing-and-reusing-components/javascript.md new file mode 100644 index 00000000..5fb31826 --- /dev/null +++ b/component-model/src/language-support/importing-and-reusing-components/javascript.md @@ -0,0 +1,312 @@ +# Importing and Reusing components (Javascript) + +## Composing existing code for use in a Javascript component + +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 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. + +Let's examine a [`jco` example project called `string-reverse-upper`][jco-examples-string-reverse-upper] that exposes +functionality for reversing *and* upper-casing a string. + +Here's the WIT one might write to enable this functionality: + +```wit +{{#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`. + +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 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. + +The JavaScript to make this work ([`string-reverse-upper.mjs` in `jco/examples`][string-reverse-upper-mjs]) +looks like this: + +```js +{{#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 +npx jco componentize \ + string-reverse-upper.mjs \ + --wit wit/ \ + --world-name revup \ + --out string-reverse-upper.incomplete.wasm \ + --disable=all +``` + +> 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 (normally `deps`). + +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`: + +```console +npx 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`). + +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 \ + -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`, 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 `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; + +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. + +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`. + +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: + +```js +{{#include ../../../examples/tutorial/js/string-reverse-upper/run.js}} +``` + +> [!NOTE] +> In the [`jco` example project][jco-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 +[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 + +## Using `jco transpile` to run components from Javsacript + +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. + +`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. + +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 +{{#include ../../../examples/tutorial/js/string-reverse/component.wit}} +``` + +As a slightly deeper crash course on [WIT][docs-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 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. +> +> 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. +Paste the following code into a file called `string-reverse.mjs`: + +```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 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 +``` + +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. + +```console +npx jco transpile string-reverse.wasm -o dist/transpiled +``` + +You should see output that looks like this: + +``` + 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] +> 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 Node.js or the browser). + +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 +{{#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. + +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! +``` + +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 +[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 diff --git a/component-model/src/language-support/importing-and-reusing-components/other-languages.md b/component-model/src/language-support/importing-and-reusing-components/other-languages.md new file mode 100644 index 00000000..31b92914 --- /dev/null +++ b/component-model/src/language-support/importing-and-reusing-components/other-languages.md @@ -0,0 +1,24 @@ +# Importing and Reusing components (Other Languages) + +This guide is a work in progress and does not have examples for all language toolchains with components support. + +For languages not listed in this guide, it is often possible to import and reuse WebAssembly components +by following the main principles of the other guides where applicable, using the help of the local +WebAssembly toolchain. + +Generally, WebAssembly toolchains in the language in question should contain a way to: + +1. Create components that import other components +2. Create host/platforms that can load and run components +3. Compose together components (possibly during build time) + +Note that generic tooling like [`wac`][wac] can be used to compose components together, regardless of language. + +[wac]: https://github.com/bytecodealliance/wac + +## Adding a New Language to the Guide + +Are you interested in documenting this section for your language toolchain? Create a PR to this [repository][repo-pr] that adds +the guide (similar to others in this section). + +[repo-pr]: https://github.com/bytecodealliance/component-docs/pulls diff --git a/component-model/src/language-support/importing-and-reusing-components/rust.md b/component-model/src/language-support/importing-and-reusing-components/rust.md new file mode 100644 index 00000000..8460402f --- /dev/null +++ b/component-model/src/language-support/importing-and-reusing-components/rust.md @@ -0,0 +1,105 @@ +# Importing and Reusing components (Rust) + +## Importing an interface + +The world file (`wit/world.wit`) we generated doesn't specify any imports. +If your component consumes other components, you can edit the `world.wit` file to import their interfaces. + +For example, suppose you have created and built the adder component as explained in the earlier tutorials and want to use +that component in a calculator component. Here is a partial example world for a calculator that imports the add interface: + +```wit +// in the 'calculator' project + +// wit/world.wit +package docs:calculator; + +interface calculate { + eval-expression: func(expr: string) -> u32; +} + +world calculator { + import docs:adder/add@0.1.0; + + export calculate; +} +``` + +### Referencing the package to import + +Because the `docs:adder` package is in a different project, we must first tell `wkg` how to find it. To do this, we add a +custom `wkg.toml` to our project: + +```toml +[overrides] +"docs:adder" = { path = "../adder/wit" } # directory containing the WIT package +``` + +After adding this configuration file, when we run `wkg wit fetch`, `wkg` will assume that the package `docs:adder` can be found +at the path that is given, and will pull its contents into the local project under `wit/deps`. + + +### Calling the import from Rust + +Now the declaration of `add` in the adder's WIT file is visible to the `calculator` project. + +To invoke the imported `add` interface from the `calculate` implementation: + +```rust +// src/lib.rs + +// Generated code that includes both the import stubs for adder functionality +// and the stubs for exports is generated into the `bindings` module below +// +// Note that while wit_bindgen::generate only creates stubs for imports, +// not implementation -- this component will be built with *unsatisfied* +// (but usable) imports (e.g. the add). +mod bindings { + use super::Component; + wit_bindgen::generate!(); + export!(Component); +} + +/// The struct from which all implementation will hang +struct Component; + +// Implementation of the `docs:calculator/calculate` export +impl bindings::exports::docs::calculator::calculate::Guest for Component { + fn eval_expression(expr: String) -> u32 { + // TODO: Cleverly parse `expr` into values and operations, and evaluate them meticulously. + bindings::docs::calculator::add::add(123, 456) + } +} +``` + +Filling out the implementation of `eval_expression` and actually parsing real expressions is left as an exercise for the reader. + +### Fulfilling the import + +When you build this using `cargo build`, the `add` interface remains unsatisfied (i.e. imported). + +The calculator has taken a dependency on the `add` _interface_, but has not linked the `adder` implementation of +that interface - this is not like referencing the `adder` crate (Indeed, `calculator` could import the `add` interface even if there was no Rust project implementing the WIT file) . + +You can see this by running [`wasm-tools component wit`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) to view the calculator's world: + +``` +# Do a release build to prune unused imports (e.g. WASI) +$ cargo build --target=wasm32-wasip2 --release + +$ wasm-tools component wit ./target/wasm32-wasip1/release/calculator.wasm +package root:component; + +world root { + import docs:adder/add@0.1.0; + + export docs:calculator/calculate@0.1.0; +} +``` + +As the import is unfulfilled, the `calculator.wasm` component could not run by itself in its current form. The next step is to fulfill the `add` import, so that only `calculate` is exported, and the component can be run. + +The process of fulfilling imports via other component's exports is called "composition". Learn more about how to compose the calculator.wasm +with an adder.wasm into a single, self-contained component in the [component composition guide](../../composing-and-distributing/composing.md). + +[!NOTE]: # diff --git a/component-model/src/language-support/javascript.md b/component-model/src/language-support/javascript.md deleted file mode 100644 index c54dae8d..00000000 --- a/component-model/src/language-support/javascript.md +++ /dev/null @@ -1,611 +0,0 @@ -# JavaScript Tooling - -[WebAssembly][mdn-wasm] was originally developed as a technology for running non-JavaScript -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 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. - -> [!WARNING] -> 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) -> that the component model makes available. - -[emscripten]: https://emscripten.org/ -[ts]: https://typescriptlang.org -[mdn-wasm]: https://developer.mozilla.org/en-US/docs/WebAssembly -[jco]: https://github.com/bytecodealliance/jco -[componentize-js]: https://github.com/bytecodealliance/componentize-js -[sm]: https://github.com/bytecodealliance/StarlingMonkey - -## Installing `jco` - -[`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`. - -## Overview of Building a Component with JavaScript - -Building a WebAssembly component with JavaScript often consists of: - -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 - -### What is WIT? - -[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. -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}} -``` - -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. -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 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 of the package. - -> To learn more about the WIT syntax, check out the [WIT explainer](../design/wit.md). - -[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 - -## Implementing a JS WebAssembly Component - -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 -{{#include ../../examples/tutorial/js/adder/adder.js}} -``` - -> [!WARNING] -> 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 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`][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] 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 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`. - -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: - -```console -$ wasm-tools print adder.wasm | head -1 -(component -``` - -> [!WARNING] -> 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`. -> -> 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]: - -{{#include example-host-part1.md}} - -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`) -- Calls the `export`ed `add` function inside the `add` interface with arguments -- Prints the result - -The important Rust code looks something like this: - -```rust -let component = Component::from_file(&engine, path).context("Component file not found")?; - -let (instance, _) = Example::instantiate_async(&mut store, &component, &linker) - .await - .context("Failed to instantiate the example world")?; - -instance - .call_add(&mut store, x, y) - .await - .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 [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 -[nodejs]: https://nodejs.org/en -[cargo]: https://doc.rust-lang.org/cargo -[wasi]: https://wasi.dev/ -[wasm-core]: https://webassembly.github.io/spec/core/ -[ffi]: https://en.wikipedia.org/wiki/Foreign_function_interface - -## 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 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 transpile`. -In your `adder` directory, execute: - -```console -jco transpile adder.wasm -o dist/transpiled -``` - -You should see output similar to the following: - -``` - Transpiled JS Component Files: - - - 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] -> 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 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 -using a runtime that supports the [core WebAssembly specification][core-wasm] as implemented for JavaScript. - -To use this component from [Node.js][nodejs], you can write code like the following: - -```mjs -{{#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: - -```json -{{#include ../../examples/tutorial/js/adder/package.json}} -``` - -> [!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 -``` - -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 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 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. -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. - -[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. - -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. - -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 -{{#include ../../examples/tutorial/js/string-reverse/component.wit}} -``` - -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 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. -> -> 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. -Paste the following code into a file called `string-reverse.mjs`: - -```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 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 -``` - -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. - -```console -npx jco transpile string-reverse.wasm -o dist/transpiled -``` - -You should see output that looks like this: - -``` - 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] -> 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 Node.js or the browser). - -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 -{{#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. - -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! -``` - -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 -[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, -`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 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. - -Let's examine a [`jco` example project called `string-reverse-upper`][jco-examples-string-reverse-upper] that exposes -functionality for reversing *and* upper-casing a string. - -Here's the WIT one might write to enable this functionality: - -```wit -{{#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`. - -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 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. - -The JavaScript to make this work ([`string-reverse-upper.mjs` in `jco/examples`][string-reverse-upper-mjs]) -looks like this: - -```js -{{#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 -npx jco componentize \ - string-reverse-upper.mjs \ - --wit wit/ \ - --world-name revup \ - --out string-reverse-upper.incomplete.wasm \ - --disable=all -``` - -> 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 (normally `deps`). - -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`: - -```console -npx 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`). - -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 \ - -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`, 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 `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; - -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. - -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`. - -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: - -```js -{{#include ../../examples/tutorial/js/string-reverse-upper/run.js}} -``` - -> [!NOTE] -> In the [`jco` example project][jco-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 -[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]: # -[!WARNING]: # diff --git a/component-model/src/language-support/other-languages.md b/component-model/src/language-support/other-languages.md deleted file mode 100644 index 6c383d05..00000000 --- a/component-model/src/language-support/other-languages.md +++ /dev/null @@ -1,14 +0,0 @@ -# Other Languages with Component Model Support - -More languages continue to add support for the component model. The following is a living list of other languages that support the component model and documentation on how to get started using components in those languages. - -[comment]: # (Add first language here) - -## Adding a New Language to the Guide - -Interested in adding another language to this list? Create a PR to this [document](https://github.com/bytecodealliance/component-docs/blob/main/component-model/src/language-support/other-languages.md) that adds an entry with the following information: - -- Language name as a level 3 header -- Link to the project page on Github page -- Link to the project hosted documentation on getting started with components. Feel free to use our example WITs if you want to match the style of this guide. -- Please insert alphabetically \ No newline at end of file diff --git a/component-model/src/language-support/rust.md b/component-model/src/language-support/rust.md deleted file mode 100644 index 165bd39d..00000000 --- a/component-model/src/language-support/rust.md +++ /dev/null @@ -1,563 +0,0 @@ -# Components in Rust - -Rust has first-class support for the component model via the [`cargo-component` tool][cargo-component]. -We will be using the `cargo component` subcommand to create WebAssembly components using Rust as -the component's implementation language. - -> [!NOTE] -> You can find more details about `cargo-component` on [crates.io](https://crates.io/crates/cargo-component). - -## 1. Setup - -Install [`cargo-component`][cargo-component-install]: -```sh -cargo install --locked cargo-component -``` -Install [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools#installation): -```sh -cargo install --locked wasm-tools -``` -Install [`wasmtime`](https://github.com/bytecodealliance/wasmtime#installation): -```sh -curl https://wasmtime.dev/install.sh -sSf | bash -``` - -## 2. Scaffolding a Component - -We will create a component in Rust that implements the `add` function exported -by the [`adder` world][docs-adder] world in the `docs:adder` -[package](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#package-names). - -First, we will create a new WebAssembly component package called `add`: -```sh -cargo component new add --lib && cd add -``` - -## 3. Adding the WIT world - -We now need to change our generated `wit/world.wit` to match `docs:adder`: -```wit -{{#include ../../examples/tutorial/wit/adder/world.wit}} -``` - -The `package.metadata.component` section of our `Cargo.toml` should be changed -to the following: - -```toml -[package.metadata.component] -package = "docs:adder" -``` - -## 4. Generating bindings - -Now that we've updated our `world.wit` and `Cargo.toml`, we can re-generate -bindings with the command below: - -```sh -cargo component bindings -``` - -`cargo-component` will generate bindings for our -world and create a `Guest` trait that a component should -implement. - -## 5. Implementing the `Guest` trait - -Implement the `Guest` trait in `src/lib.rs`, using the scaffolded code. Your code should look something like the following: - -```rs -{{#include ../../examples/tutorial/adder/src/lib.rs}} -``` - -## 6. Building a Component - -Now, let's build our component, being sure to optimize with a release build: - -```sh -cargo component build --release -``` - -> [!WARNING] -> Building with `--release` removes all debug-related information from the resulting `.wasm` file. -> -> When prototyping or testing locally, you might want to avoid `--release` to -> obtain useful backtraces in case of errors (for example, with -> [`wasmtime::WasmBacktraceDetails::Enable`](https://docs.rs/wasmtime/latest/wasmtime/enum.WasmBacktraceDetails.html#variant.Enable)). -> Note: the resulting `.wasm` file will be considerably larger (likely 4MB+). - -You can use `wasm-tools` to output the WIT package of the component: - -```sh -wasm-tools component wit target/wasm32-wasip1/release/add.wasm -``` - -The command above should produce the output below: - -```wit -package root:component; - -world root { - export docs:adder/add@0.1.0; -} -package docs:adder@0.1.0 { - interface add { - add: func(x: u32, y: u32) -> u32; - } -} -``` - - -### Running a Component - -To verify that our component works, lets run it from a Rust application that knows how to run a -component targeting the [`adder` world](#adding-the-wit-world). - -The application uses [`wasmtime`](https://github.com/bytecodealliance/wasmtime) crates to generate -Rust bindings, bring in WASI worlds, and execute the component. - -```console -$ cd examples/example-host -$ cargo run --release -- 1 2 ../add/target/wasm32-wasip1/release/adder.wasm -1 + 2 = 3 -``` - -## Importing an interface - -The world file (`wit/world.wit`) we generated doesn't specify any imports. -If your component consumes other components, you can edit the `world.wit` file to import their interfaces. - -> [!NOTE] -> This section is about importing custom WIT interfaces from library components. -> By default, `cargo-component` imports any required [WASI interfaces](https://wasi.dev/interfaces) -> for us without needing to explicitly declare them. - - -For example, suppose you have created and built an adder component as explained in the [exporting an interface section](#exporting-an-interface-with-cargo-component) and want to use that component in a calculator component. Here is a partial example world for a calculator that imports the add interface: - -```wit -// in the 'calculator' project - -// wit/world.wit -package docs:calculator; - -interface calculate { - eval-expression: func(expr: string) -> u32; -} - -world calculator { - export calculate; - import docs:adder/add@0.1.0; -} -``` - -### Referencing the package to import - -Because the `docs:adder` package is in a different project, we must first tell `cargo component` how to find it. To do this, add the following to the `Cargo.toml` file: - -```toml -[package.metadata.component.target.dependencies] -"docs:adder" = { path = "../adder/wit" } # directory containing the WIT package -``` - -> [!NOTE] -> The path for `docs:adder` is relative to the `wit` _directory_, not to the `world.wit` file. -> -> A WIT package may be spread across multiple files in the same directory; `cargo component` will search them all. - -### Calling the import from Rust - -Now the declaration of `add` in the adder's WIT file is visible to the `calculator` project. To invoke the imported `add` interface from the `calculate` implementation: - -```rust -// src/lib.rs -mod bindings; - -use bindings::exports::docs::calculator::calculate::Guest; - -// Bring the imported add function into scope -use bindings::docs::calculator::add::add; - -struct Component; - -impl Guest for Component { - fn eval_expression(expr: String) -> u32 { - // Cleverly parse `expr` into values and operations, and evaluate - // them meticulously. - add(123, 456) - } -} -``` - -### Fulfilling the import - -When you build this using `cargo component build`, the `add` interface remains imported. The calculator has taken a dependency on the `add` _interface_, but has not linked the `adder` implementation of that interface - this is not like referencing the `adder` crate. (Indeed, `calculator` could import the `add` interface even if there was no Rust project implementing the WIT file.) You can see this by running [`wasm-tools component wit`](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-component) to view the calculator's world: - -``` -# Do a release build to prune unused imports (e.g. WASI) -$ cargo component build --release - -$ wasm-tools component wit ./target/wasm32-wasip1/release/calculator.wasm -package root:component; - -world root { - import docs:adder/add@0.1.0; - - export docs:calculator/calculate@0.1.0; -} -``` - -As the import is unfulfilled, the `calculator.wasm` component could not run by itself in its current form. To fulfill the `add` import, so that -only `calculate` is exported, you would need to [compose the `calculator.wasm` with some `adder.wasm` into a single, self-contained component](../composing-and-distributing/composing.md). - -## Creating a command component with `cargo component` - -A _command_ is a component with a specific export that allows it to be executed directly by `wasmtime` (or other `wasi:cli` hosts). In Rust terms, it's the equivalent of an application (`bin`) package with a `main` function, instead of a library crate (`lib`) package. - -To create a command with cargo component, run: - -```sh -cargo component new -``` - -Unlike library components, this does _not_ have the `--lib` flag. You will see that the created project is different too: - -- It doesn't contain a `.wit` file. `cargo component build` will automatically export the `wasi:cli/run` interface for Rust `bin` packages, and hook it up to `main`. -- Because there's no `.wit` file, `Cargo.toml` doesn't contain a `package.metadata.component.target` section. -- The Rust file is called `main.rs` instead of `lib.rs`, and contains a `main` function instead of an interface implementation. - -You can write Rust in this project, just as you normally would, including importing your own or third-party crates. - -> All the crates that make up your project are linked together at build time, and compiled to a _single_ Wasm component. In this case, all the linking is happening at the Rust level: no WITs or component composition is involved. Only if you import Wasm interfaces do WIT and composition come into play. - -To run your command component: - -```sh -cargo component build -wasmtime run ./target/wasm32-wasip1/debug/.wasm -``` - -> **WARNING:** If your program prints to standard out or error, you may not see the printed output! Some versions of `wasmtime` have a bug where they don't flush output streams before exiting. To work around this, add a `std::thread::sleep()` with a 10 millisecond delay before exiting `main`. - -### Importing an interface into a command component - -As mentioned above, `cargo component build` doesn't generate a WIT file for a command component. If you want to import a Wasm interface, though, you'll need to create a WIT file and a world, plus reference the packages containing your imports: - -1. Add a `wit/world.wit` to your project, and write a WIT world that imports the interface(s) you want to use. For example: - - ```wit - package docs:app; - - world app { - import docs:calculator/calculate@0.1.0; - } - ``` - - > `cargo component` sometimes fails to find packages if versions are not set explicitly. For example, if the calculator WIT declares `package docs:calculator` rather than `docs:calculator@0.1.0`, then you may get an error even though `cargo component build` automatically versions the binary export. - -2. Edit `Cargo.toml` to tell `cargo component` about the new WIT file: - - ```toml - [package.metadata.component.target] - path = "wit" - ``` - - (This entry is created automatically for library components but not for command components.) - -3. Edit `Cargo.toml` to tell `cargo component` where to find external package WITs: - - ```toml - [package.metadata.component.target.dependencies] - "docs:calculator" = { path = "../calculator/wit" } - "docs:adder" = { path = "../adder/wit" } - ``` - - > If the external package refers to other packages, you need to provide the paths to them as well. - -4. Use the imported interface in your Rust code: - - ```rust - use bindings::docs::calculator::calculate::eval_expression; - - fn main() { - let result = eval_expression("1 + 1"); - println!("1 + 1 = {result}"); - } - ``` - -5. [Compose the command component with the `.wasm` components that implement the imports.](../composing-and-distributing/composing.md) - -6. Run the composed component: - - ```console - $ wasmtime run ./my-composed-command.wasm - 1 + 1 = 579 # might need to go back and do some work on the calculator implementation - ``` - -## Using user-defined types - -[User-defined types](../design/wit.md#user-defined-types) map to Rust types as follows. - -| WIT type | Rust binding | -|------------|--------------| -| `record` | `struct` with public fields corresponding to the record fields | -| `variant` | `enum` with cases corresponding to the variant cases | -| `enum` | `enum` with cases corresponding to the enum cases, with no data attached | -| `resource` | [See below](#using-resources) | -| `flags` | Opaque type supporting bit flag operations, with constants for flag values | - -For example, consider the following WIT: - -```wit -interface types { - enum operation { - add, - sub, - mul, - div - } - - record expression { - left: u32, - operation: operation, - right: u32 - } - - eval: func(expr: expression) -> u32; -} -``` - -When exported from a component, this could be implemented as: - -```rust -impl Guest for Implementation { - fn eval(expr: Expression) -> u32 { - // Record fields become public fields on a struct - let (l, r) = (expr.left, expr.right); - match expr.operation { - // Enum becomes an enum with only unit cases - Operation::Add => l + r, - Operation::Sub => l - r, - Operation::Mul => l * r, - Operation::Div => l / r, - } - } -} -``` - -## Using resources - -[Resources](../design/wit.md#resources) are handles to entities that live outside the component, for example in a host, or in a different component. - -### Example - -In this section, our example resource will be a [Reverse Polish Notation (RPN)](https://en.wikipedia.org/wiki/Reverse_Polish_notation) calculator. (Engineers of a certain vintage will remember this from handheld calculators of the 1970s.) A RPN calculator is a stateful entity: a consumer pushes operands and operations onto a stack maintained within the calculator, then evaluates the stack to produce a value. The resource in WIT looks like this: - -```wit -package docs:rpn@0.1.0; - -interface types { - enum operation { - add, - sub, - mul, - div - } - - resource engine { - constructor(); - push-operand: func(operand: u32); - push-operation: func(operation: operation); - execute: func() -> u32; - } -} - -world calculator { - export types; -} -``` - -### Implementing and exporting a resource in a component - -To implement the calculator using `cargo component`: - -1. Create a library component as shown in previous sections, with the WIT given above. - -2. Define a Rust `struct` to represent the calculator state: - - ```rust - use std::cell::RefCell; - - struct CalcEngine { - stack: RefCell>, - } - ``` - - > Why is the stack wrapped in a `RefCell`? As we will see, the generated Rust trait for the calculator engine has _immutable_ references to `self`. But our implementation of that trait will need to mutate the stack. So we need a type that allows for interior mutability, such as `RefCell` or `Arc>`. - -3. The generated bindings (`bindings.rs`) for an exported resource include a trait named `GuestX`, where `X` is the resource name. (You may need to run `cargo component build` to regenerate the bindings after updating the WIT.) For the calculator `engine` resource, the trait is `GuestEngine`. Implement this trait on the `struct` from step 2: - - ```rust - use bindings::exports::docs::rpn::types::{GuestEngine, Operation}; - - impl GuestEngine for CalcEngine { - fn new() -> Self { - CalcEngine { - stack: RefCell::new(vec![]) - } - } - - fn push_operand(&self, operand: u32) { - self.stack.borrow_mut().push(operand); - } - - fn push_operation(&self, operation: Operation) { - let mut stack = self.stack.borrow_mut(); - let right = stack.pop().unwrap(); // TODO: error handling! - let left = stack.pop().unwrap(); - let result = match operation { - Operation::Add => left + right, - Operation::Sub => left - right, - Operation::Mul => left * right, - Operation::Div => left / right, - }; - stack.push(result); - } - - fn execute(&self) -> u32 { - self.stack.borrow_mut().pop().unwrap() // TODO: error handling! - } - } - ``` - -4. We now have a working calculator type which implements the `engine` contract, but we must still connect that type to the `engine` resource type. This is done by implementing the generated `Guest` trait. For this WIT, the `Guest` trait contains nothing except an associated type. You can use an empty `struct` to implement the `Guest` trait on. Set the associated type for the resource - in our case, `Engine` - to the type which implements the resource trait - in our case, the `CalcEngine` `struct` which implements `GuestEngine`. Then use the `export!` macro to export the mapping: - - ```rust - struct Implementation; - impl Guest for Implementation { - type Engine = CalcEngine; - } - - bindings::export!(Implementation with_types_in bindings); - ``` - -This completes the implementation of the calculator `engine` resource. Run `cargo component build` to create a component `.wasm` file. - -### Importing and consuming a resource in a component - -To use the calculator engine in another component, that component must import the resource. - -1. Create a command component as shown in previous sections. - -2. Add a `wit/world.wit` to your project, and write a WIT world that imports the RPN calculator types: - - ```wit - package docs:rpn-cmd; - - world app { - import docs:rpn/types@0.1.0; - } - ``` - -3. Edit `Cargo.toml` to tell `cargo component` about the new WIT file and the external RPN package file: - - ```toml - [package.metadata.component] - package = "docs:rpn-cmd" - - [package.metadata.component.target] - path = "wit" - - [package.metadata.component.target.dependencies] - "docs:rpn" = { path = "../wit" } # or wherever your resource WIT is - ``` - -4. The resource now appears in the generated bindings as a `struct`, with appropriate associated functions. Use these to construct a test app: - - ```rust - #[allow(warnings)] - mod bindings; - use bindings::docs::rpn::types::{Engine, Operation}; - - fn main() { - let calc = Engine::new(); - calc.push_operand(1); - calc.push_operand(2); - calc.push_operation(Operation::Add); - let sum = calc.execute(); - println!("{sum}"); - } - ``` - -You can now build the command component and [compose it with the `.wasm` component that implements the resource.](../composing-and-distributing/composing.md). You can then run the composed command with `wasmtime run`. - -### Implementing and exporting a resource implementation in a host - -If you are hosting a Wasm runtime, you can export a resource from your host for guests to consume. Hosting a runtime is outside the scope of this book, so we will give only a broad outline here. This is specific to the Wasmtime runtime; other runtimes may express things differently. - -1. Use `wasmtime::component::bindgen!` to specify the WIT you are a host for: - - ```rust - wasmtime::component::bindgen!({ - path: "../wit" - }); - ``` - -2. Tell `bindgen!` how you will represent the resource in the host via the `with` field. This can be any Rust type. For example, the RPN engine could be represented by a `CalcEngine` struct: - - ```rust - wasmtime::component::bindgen!({ - path: "../wit", - with: { - "docs:rpn/types/engine": CalcEngine, - } - }); - ``` - - > If you don't specify the host representation for a resource, it defaults to an empty enum. This is rarely useful as resources are usually stateful. - -3. If the representation type isn't a built-in type, define it: - - ```rust - struct CalcEngine { /* ... */ } - ``` - -4. As a host, you will already be implementing a `Host` trait. You will now need to implement a `HostX` trait (where `X` is the resource name) _on the same type_ as the `Host` trait: - - ```rust - impl docs::rpn::types::HostEngine for MyHost { - fn new(&mut self) -> wasmtime::component::Resource { /* ... */ } - fn push_operand(&mut self, self_: wasmtime::component::Resource) { /* ... */ } - // etc. - } - ``` - - > **Important:** You implement this on the 'overall' host type, *not* on the resource representation! Therefore, the `self` reference in these functions is to the 'overall' host type. For instance methods of the resource, the instance is identified by a second parameter (`self_`), of type `wasmtime::component::Resource`. - -5. Add a `wasmtime::component::ResourceTable` to the host: - - ```rust - struct MyHost { - calcs: wasmtime::component::ResourceTable, - } - ``` - -6. In your resource method implementations, use this table to store and access instances of the resource representation: - - ```rust - impl docs::rpn::types::HostEngine for MyHost { - fn new(&mut self) -> wasmtime::component::Resource { - self.calcs.push(CalcEngine::new()).unwrap() // TODO: error handling - } - fn push_operand(&mut self, self_: wasmtime::component::Resource) { - let calc_engine = self.calcs.get(&self_).unwrap(); - // calc_engine is a CalcEngine - call its functions - } - // etc. - } - ``` - -[cargo-component]: https://github.com/bytecodealliance/cargo-component -[cargo-component-install]: https://github.com/bytecodealliance/cargo-component#install -[docs-adder]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit -[!NOTE]: # -[!WARNING]: # diff --git a/component-model/src/language-support/using-wit-resources/rust.md b/component-model/src/language-support/using-wit-resources/rust.md new file mode 100644 index 00000000..8288ee02 --- /dev/null +++ b/component-model/src/language-support/using-wit-resources/rust.md @@ -0,0 +1,238 @@ +# Using WIT Resources (Rust) + +[Resources](../../design/wit.md#resources) are handles to entities that live outside the component (i.e. in a host, or other component). + +## An example stack-based Reverse Polish Notation (RPN) calculator + +In this section, our example resource will be a [Reverse Polish Notation (RPN)](https://en.wikipedia.org/wiki/Reverse_Polish_notation) calculator. (Engineers of a certain vintage will remember this from handheld calculators of the 1970s.) + +A RPN calculator is a stateful entity: a consumer pushes operands and operations onto a stack +maintained within the calculator, then evaluates the stack to produce a value. + +In WIT, the resource looks like the following: + +```wit +package docs:rpn@0.1.0; + +interface types { + enum operation { + add, + sub, + mul, + div + } + + resource engine { + constructor(); + push-operand: func(operand: u32); + push-operation: func(operation: operation); + execute: func() -> u32; + } +} + +world calculator { + export types; +} +``` + +## Implementing and exporting a resource in a component + +To implement the calculator in Rust: + +1. Create a library component as shown in previous sections, with the WIT given above. + +2. Define a Rust `struct` to represent the calculator state: + + ```rust + use std::cell::RefCell; + + struct CalcEngine { + stack: RefCell>, + } + ``` + + > Why is the stack wrapped in a `RefCell`? As we will see, the generated Rust trait for the calculator engine has _immutable_ references to `self`. But our implementation of that trait will need to mutate the stack. So we need a type that allows for interior mutability, such as `RefCell` or `Arc>`. + +3. The generated bindings (`bindings.rs`) for an exported resource include a trait named `GuestX`, where `X` is the resource name. For the calculator `engine` resource, the trait is `GuestEngine`. Implement this trait on the `struct` from step 2: + + ```rust + mod bindings { + use super::Component; + wit_bindgen::generate!(); + export!(Component); + } + + use bindings::exports::docs::rpn::types::{GuestEngine, Operation}; + + impl GuestEngine for CalcEngine { + fn new() -> Self { + CalcEngine { + stack: RefCell::new(vec![]) + } + } + + fn push_operand(&self, operand: u32) { + self.stack.borrow_mut().push(operand); + } + + fn push_operation(&self, operation: Operation) { + let mut stack = self.stack.borrow_mut(); + let right = stack.pop().unwrap(); // TODO: error handling! + let left = stack.pop().unwrap(); + let result = match operation { + Operation::Add => left + right, + Operation::Sub => left - right, + Operation::Mul => left * right, + Operation::Div => left / right, + }; + stack.push(result); + } + + fn execute(&self) -> u32 { + self.stack.borrow_mut().pop().unwrap() // TODO: error handling! + } + } + ``` + +4. We now have a working calculator type which implements the `engine` contract, but we must still connect that type to the `engine` resource type. This is done by implementing the generated `Guest` trait. For this WIT, the `Guest` trait contains nothing except an associated type. You can use an empty `struct` to implement the `Guest` trait on. Set the associated type for the resource - in our case, `Engine` - to the type which implements the resource trait - in our case, the `CalcEngine` `struct` which implements `GuestEngine`. Then use the `export!` macro to export the mapping: + + ```rust + // ... bindings & CalcEngine impl code ... + + struct Component; + + impl bindings::Guest for Component { + type Engine = CalcEngine; + } + + bindings::export!(Component); + ``` + +This completes the implementation of the calculator `engine` resource. Run `cargo build --target=wasm32-wasip2` to create a component `.wasm` file. + +## Importing and consuming a resource in a component + +To use the calculator engine in another component, that component must import the resource. + +1. [Create a runnable component](../creating-runnable-components/rust.md) as shown in previous sections. + +2. Add a `wit/component.wit` to your project, and write a WIT world that imports the RPN calculator types: + + ```wit + package docs:rpn-cmd; + + world app { + import docs:rpn/types@0.1.0; + } + ``` + +3. Create a `wkg.toml` file to enable retrieving the relevant WIT files for `docs:rpn` (which contains the `engine` resource): + + ```toml + [overrides] + "docs:rpn" = { path = "../path/to/docs-rpn/wit" } + ``` + + After doing this, you can run `wkg wit fetch` to ensure all WIT is available locally. + +4. The resource now appears in the generated bindings as a `struct`, with appropriate associated functions. Use these to construct a test app: + + ```rust + mod bindings { + use super::Component; + wit_bindgen::generate!(); + export!(Component); + } + + use bindings::docs::rpn::types::{Engine, Operation}; + + fn main() { + let calc = Engine::new(); + calc.push_operand(1); + calc.push_operand(2); + calc.push_operation(Operation::Add); + let sum = calc.execute(); + println!("{sum}"); + } + ``` + +Building the component as is creates a WebAssembly component with an *unsatisfied import* -- namely the `docs:rpn/types` import. + +After building the component, it must be [composed with a `.wasm` component that implements the resource.](../../composing-and-distributing/composing.md). After composition creates a component with no unsatisfied imports, the composed command component can be run with `wasmtime run`. + +Alternatively, a host that can provide the `docs:rpn/types` import (and related resource) can also be used to run the component +in it's "incomplete" state (as the host will "complete" the componnt by providing the expected import). + +## Implementing and exporting a resource implementation in a host + +If you are hosting a Wasm runtime, you can export a resource from your host for guests to consume. + +Hosting a runtime is outside the scope of this book, so we will give only a broad outline here. This is specific +to the Wasmtime runtime; other runtimes may express things differently. + +1. Use `wasmtime::component::bindgen!` to specify the WIT you are a host for: + + ```rust + wasmtime::component::bindgen!({ + path: "../wit" + }); + ``` + +2. Tell `bindgen!` how you will represent the resource in the host via the `with` field. This can be any Rust type. For example, the RPN engine could be represented by a `CalcEngine` struct: + + ```rust + wasmtime::component::bindgen!({ + path: "../wit", + with: { + "docs:rpn/types/engine": CalcEngine, + } + }); + ``` + + > If you don't specify the host representation for a resource, it defaults to an empty enum. This is rarely useful as resources are usually stateful. + +3. If the representation type isn't a built-in type, define it: + + ```rust + struct CalcEngine { /* ... */ } + ``` + +4. As a host, you will already be implementing a `Host` trait. You will now need to implement a `HostX` trait (where `X` is the resource name) _on the same type_ as the `Host` trait: + + ```rust + impl docs::rpn::types::HostEngine for MyHost { + fn new(&mut self) -> wasmtime::component::Resource { /* ... */ } + fn push_operand(&mut self, self_: wasmtime::component::Resource) { /* ... */ } + // etc. + } + ``` + + > **Important:** You implement this on the 'overall' host type, *not* on the resource representation! Therefore, the `self` reference in these functions is to the 'overall' host type. For instance methods of the resource, the instance is identified by a second parameter (`self_`), of type `wasmtime::component::Resource`. + +5. Add a `wasmtime::component::ResourceTable` to the host: + + ```rust + struct MyHost { + calcs: wasmtime::component::ResourceTable, + } + ``` + +6. In your resource method implementations, use this table to store and access instances of the resource representation: + + ```rust + impl docs::rpn::types::HostEngine for MyHost { + fn new(&mut self) -> wasmtime::component::Resource { + self.calcs.push(CalcEngine::new()).unwrap() // TODO: error handling + } + fn push_operand(&mut self, self_: wasmtime::component::Resource) { + let calc_engine = self.calcs.get(&self_).unwrap(); + // calc_engine is a CalcEngine - call its functions + } + // etc. + } + ``` + +[docs-adder]: https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/adder/world.wit + +[!NOTE]: # +[!WARNING]: # diff --git a/component-model/src/tutorial.md b/component-model/src/tutorial.md index 60b4244a..6936f8dc 100644 --- a/component-model/src/tutorial.md +++ b/component-model/src/tutorial.md @@ -104,51 +104,74 @@ The WebAssembly host expects it to export the [`wasi:cli/run` interface](https://github.com/WebAssembly/wasi-cli/blob/main/wit/run.wit), which is the equivalent of the [`main` function][wiki-entrypoint] to WASI. -[`cargo-component`][cargo-component] will automatically resolve a Rust `bin` package -with a `main` function to a component with `wasi:cli/run` exported. Scaffold a new Wasm application -with a `command` component: +To build a command component, [`cargo`][cargo] should be configured to build a `--lib` crate which +adheres to the `wasi:cli` interface. Crucially the component must export `wasi:cli/run` + +To scaffold a new runnable Wasm application: ```console -cargo component new command --command +cargo new --lib example +cd example ``` -This component will implement the [`app`](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/calculator.wit) world, which +This component will implement the [`app`](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/wit/calculator/world.wit) world, which imports the `calculate` interface. -In `Cargo.toml`, point `cargo-component` to the WIT file and specify that it should pull in bindings -for the `app` world from the path to `calculator.wit`: +In `Cargo.toml`, configure this crate as a `cdylib`: ```toml -[package.metadata.component.target] -path = "../wit/calculator/world.wit" -world = "app" +[lib] +crate-type = "cdylib" +``` +In addition, we'll need to modify the WIT interface that *imports* the calculation function and makes use of it to be runnable: + +```diff +world app { + import calculate; ++ export wasi:cli/run@0.2.7; +} ``` + Since the calculator world imports the `add` interface, the command component needs to pull in the `adder` WIT as a dependency, as well. +To do this, we can [`wkg`][wkg] from [`wasm-pkg-tools`][wasm-pkg-tools]. We must first configure wkg to find the `docs:adder` repository: + ```toml -[package.metadata.component.target.dependencies] -"docs:adder" = { path = "../wit/adder" } +# $XDG_CONFIG_HOME/wasm-pkg/config.toml +default_registry = "ghcr.io" + +[namespace_registries] +# Tell wkg that the component-book WITs can be found at ghcr.io/bytecodealliance/docs +docs = { registry = "docs", metadata = { preferredProtocol = "oci", "oci" = {registry = "ghcr.io", namespacePrefix = "bytecodealliance/" } } } ``` -Now, implement a command line application that: + +> [!NOTE] +> We have published the [`docs:adder` WIT package](https://github.com/orgs/bytecodealliance/packages/container/package/docs%2Fadder) to GHCR ahead of time, so it is easy to find/access via the configuration above. +> +> To make your own custom WITs available, please use the [`wkg publish`](./composing-and-distributing/distributing.md#distributing-wit-and-components-by-package-name-with-wkg-publish) command. + +With the project scaffolded, now we must actually *implement* a command line application that: 1. Takes in three arguments: two operands and the name of an operator ("1 2 add") 2. Parses the operator name and ensures it is supported in the `op` enum 3. Calls the `calculate` interface's `eval_expression`, passing in the arguments. -For reference, see a completed [example](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/command/). +For reference, see the [completed code listing](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/tutorial/command/). +[wkg]: https://github.com/bytecodealliance/wasm-pkg-tools/tree/main/crates/wkg +[wasm-pkg-tools]: https://github.com/bytecodealliance/wasm-pkg-tools/tree/main [wiki-entrypoint]: https://en.wikipedia.org/wiki/Entry_point -[cargo-component]: https://crates.io/crates/cargo-component +[cargo]: https://doc.rust-lang.org/cargo ## Composing the calculator Now, we are ready to bring our components together into one runnable calculator component, using -`wac`. +`wac`. We will: 1. Compose the calculator component with the add component to satisfy the calculator component's `adder` import -2. Compose that resolved calculator component once more with the command component to satisfy the command component's `calculate` import. +2. Compose that resolved calculator component once more with the command component to satisfy the command component's `calculate` import. The result is a fully-formed command component that has all its imports satisfied and has a single export (the `wasi:cli/run` interface), which can be executed by [`wasmtime`][wasmtime]. @@ -182,3 +205,5 @@ expanded enum. Another extension of this tutorial could be to remove the `op` enum and instead modify `eval-expression` to take in a string that can then be parsed to determine which operator component to call. Maybe this parser is a component of its own?! + +[!NOTE]: # diff --git a/component-model/src/using-wit-resources.md b/component-model/src/using-wit-resources.md new file mode 100644 index 00000000..f857d726 --- /dev/null +++ b/component-model/src/using-wit-resources.md @@ -0,0 +1,51 @@ +# Using WIT resources + +This section contains language-specific guides on how to use [WIT][docs-wit] resources. + +Resources represent functionality that is implemented only on one side of a component boundary, +for example in another component or in the underlying platform/host. + +An example of a resource: + +```wit +package docs:calc-resource@0.1.0; + +interface types { + enum operation { + add, + sub, + mul, + div, + } + + variant execute-error { + divide-by-zero, + unexpected(string), + } + + resource stack-calculator { + constructor(); + push-operand: func(operand: u32); + push-operation: func(operation: operation); + execute: func() -> result; + } +} + +world calculator { + export types; +} +``` + +Hosts or Components implementing the stack-based calculator above do share the *resource* (the `stack-calculator` entity) +with components that `import` that functionality, but do *not* share the actual implementation. All calls to `stack-calculator` +resources resolve inside the component that `export`ed the functionality. + +## Languages + +This guide is implemented for various languages: + +| Language | +|--------------------------------------------------------| +| [Rust](./language-support/using-wit-resources/rust.md) | + +[docs-wit]: ./design/wit.md