|
| 1 | +--- |
| 2 | +title: "Announcing Jco 1.0" |
| 3 | +author: "Yosh Wuyts" |
| 4 | +date: "2024-02-22" |
| 5 | +github_name: "yoshuawuyts" |
| 6 | +excerpt_separator: <!--end_excerpt--> |
| 7 | +--- |
| 8 | + |
| 9 | +We’re happy to announce the 1.0 release of [Jco][jco]: a native Javascript |
| 10 | +WebAssembly toolchain and runtime built for WebAssembly Components and [WASI |
| 11 | +0.2](https://bytecodealliance.org/articles/WASI-0.2) [^wasi-one-liner]. Jco can |
| 12 | +natively run Wasm Components inside of Node.js, making it easy to take libraries |
| 13 | +written in different programming languages and execute them using the Node.js |
| 14 | +runtime. And by implementing the entirety of the WASI 0.2 API surface, those |
| 15 | +components can access the network, filesystem, and other system APIs available |
| 16 | +in the Node.js runtime. |
| 17 | + |
| 18 | +[^wasi-one-liner]: WebAssembly Components are an extension to WebAssembly to |
| 19 | +create strongly typed interfaces between different programs. WASI 0.2 is a |
| 20 | +re-work of the "WebAssembly System Interfaces" based on WebAssembly Components. |
| 21 | +The previous version of WASI was not based on components, and as a result was |
| 22 | +rather limited in what it could express. |
| 23 | + |
| 24 | +<!--end_excerpt--> |
| 25 | + |
| 26 | +Our goal is for Jco to be a comprehensive tool for all Component-related |
| 27 | +operations for JavaScript. With Jco 1.0 we're stabilizing a Node.js runtime for |
| 28 | +Wasm Components, as well as a toolchain to take Wasm Components written in other |
| 29 | +languages and import them into JavaScript. From here on out we hope to continue |
| 30 | +stabilizing more features for Jco. Some features are already available |
| 31 | +experimentally; this includes native support for the browser, as well as native |
| 32 | +support for compiling JavaScript code into WebAssembly. Other features such as |
| 33 | +support for the [WebAssembly |
| 34 | +registry](https://github.com/bytecodealliance/registry) have not yet started, |
| 35 | +but we expect to add later. |
| 36 | + |
| 37 | +## JavaScript Toolchains in the Bytecode Alliance |
| 38 | + |
| 39 | +Jco is the third JS toolchain project which is part of the Bytecode Alliance. To |
| 40 | +help people navigate this, here is a quick overview of the three projects and |
| 41 | +what their purpose is at the time of writing: |
| 42 | + |
| 43 | +- [Javy](https://github.com/bytecodealliance/javy): A JavaScript → WASI 0.1 |
| 44 | +compiler toolchain built using the [QuickJS JavaScript |
| 45 | +engine](https://bellard.org/quickjs/). This work predates Wasm Components, but |
| 46 | +support may be added in the future. |
| 47 | +- [ComponentizeJS](https://github.com/bytecodealliance/ComponentizeJS): A |
| 48 | +JavaScript → Wasm Component toolchain with full support for WASI 0.2 built |
| 49 | +using the [SpiderMonkey JavaScript engine](https://spidermonkey.dev). This |
| 50 | +project is newer and not yet considered stable. |
| 51 | +- [Jco](https://github.com/bytecodealliance/jco): A comprehensive WebAssembly |
| 52 | +Component toolchain and runtime for JavaScript. This includes a Wasm Component → |
| 53 | +JS toolchain with full support for WASI 0.2. |
| 54 | + |
| 55 | +## Example |
| 56 | + |
| 57 | +To give you a taste of what it's like to use Jco, let’s compile a little Rust |
| 58 | +program to a WASI 0.2.0 component, install Jco for Node.js, and then embed the |
| 59 | +newly built component inside of the runtime. If you want to skip ahead to the |
| 60 | +final result, you can [view it here](https://github.com/yoshuawuyts/playground-jco). |
| 61 | + |
| 62 | +### Dependencies and Repository |
| 63 | + |
| 64 | +Because we're going to be writing Rust code, and using it from Node.js we expect |
| 65 | +you to have both a working [Node.js environment](https://github.com/nvm-sh/nvm) |
| 66 | +and [Rust toolchain](https://rustup.rs) installed. Once you have that, we can |
| 67 | +install [`cargo-component`](https://github.com/bytecodealliance/cargo-component) |
| 68 | +to build our Rust code, and [Jco][jco] to build it for Node.js. |
| 69 | + |
| 70 | +```bash |
| 71 | +$ npm install -g @bytecodealliance/jco # Install the jco cli tool |
| 72 | +$ npm install @bytecodealliance/preview2-shim # Install the jco WASI runtime as a local dep |
| 73 | +$ cargo install cargo-component # Install the `cargo component` subcommand |
| 74 | +``` |
| 75 | + |
| 76 | +We can then proceed to create a new project repository, which has both Rust and JS code: |
| 77 | + |
| 78 | +```sh |
| 79 | +$ cargo component new playground-jco --lib # Create a new Rust component |
| 80 | +$ cd playground-jco # Enter the newly created project |
| 81 | +$ npm init -y # Setup a Node.js environment |
| 82 | +$ touch index.js # Create a JS entrypoint |
| 83 | +$ echo node_modules >> .gitignore # Add node_modules to our .gitignore file |
| 84 | +``` |
| 85 | + |
| 86 | +This should leave you with a structure that roughly looks like this: |
| 87 | + |
| 88 | +```bash |
| 89 | +src/lib.rs # Our Rust library entry point |
| 90 | +wit/world.wit # Contains our WIT IDL definitions |
| 91 | +Cargo.toml # Configures our Rust project |
| 92 | +index.js # Our JS binary entry point |
| 93 | +package.json # Configures our JS Project |
| 94 | +``` |
| 95 | + |
| 96 | +And with that out of the way, we're ready to begin writing some code! |
| 97 | + |
| 98 | +### Defining the WIT interface |
| 99 | + |
| 100 | +There are three steps to this project: defining the WIT interface, writing the |
| 101 | +Rust code to be exported, and writing the JS code to be imported. We'll be |
| 102 | +starting with the WIT interface. For those of you new to Wasm Components: WIT is |
| 103 | +an Interface Description Language (IDL) which allows you to describe programming |
| 104 | +interfaces in a declarative, language agnostic way. Let's define an interface |
| 105 | +which takes a regular string, and converts it to [SHOUTING |
| 106 | +CASE!!1!](https://github.com/badboy/stdshout). |
| 107 | + |
| 108 | +```wit |
| 109 | +package my-org:wit-playground |
| 110 | +
|
| 111 | +world playground { |
| 112 | + export scream: func(input: string) -> string; |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +We can save this to the generated `wit/world.wit` file [^cc-gen], and now we |
| 117 | +have defined a function `scream` which takes a string, and returns a string. If |
| 118 | +this is your first time editing WIT files and you're using VS Code, consider |
| 119 | +installing the [WIT |
| 120 | +IDL](https://marketplace.visualstudio.com/items?itemName=bytecodealliance.wit-idl) |
| 121 | +package to get editor support for WIT files. And with that, it's time to write |
| 122 | +our Rust code! |
| 123 | + |
| 124 | +[^cc-gen]: If you're from the future: `cargo-component` generated this file for |
| 125 | +us in this location. Since it is not yet advertised as "stable", the output |
| 126 | +location for WIT files may change. If no `wit/world.wit` file was generated for |
| 127 | +you, please check a different location to see whether it might be somewhere else. |
| 128 | + |
| 129 | +### Writing a Rust library |
| 130 | + |
| 131 | +As mentioned, we're using the `cargo-component` tool here in this example to |
| 132 | +compile our Rust code to Wasm Components. It isn't yet at 1.0, but it does |
| 133 | +already work well enough for our purposes. So we'll show you how you can use it |
| 134 | +today. If you're from the future (e.g. not February 2024), please check the |
| 135 | +`cargo-component` docs to see whether these instructions have changed, and if so - |
| 136 | +follow those instead. The steps we're going to take here are: define a WIT API |
| 137 | +definition, generate the Rust bindings to it (e.g. Rust traits), and then |
| 138 | +implement those traits to implement the definitions. Even if the exact ways of |
| 139 | +doing this may change in the future, conceptually there will still be ways to |
| 140 | +do similar things. |
| 141 | + |
| 142 | +First things first: we want `cargo component` to generate the bindings for our |
| 143 | +WIT file. This is done via Rust traits, and it will do this automatically when |
| 144 | +we compile. This will yield a compiler error, but that's okay - we'll update the |
| 145 | +code in a second: |
| 146 | + |
| 147 | +```bash |
| 148 | +$ cargo component check |
| 149 | +``` |
| 150 | + |
| 151 | +This will have generated Rust bindings, which on the current version of |
| 152 | +`cargo-component` is places under `lib/bindings.rs`. Don't worry about reading |
| 153 | +that code right now - it does a lot of heavy lifting so we don't have to. The |
| 154 | +important part is that it provides us with traits which we can implement. So |
| 155 | +let's implement those in our code, and write a little upper case function |
| 156 | + |
| 157 | +```rust |
| 158 | +mod bindings; // Teach our program about `src/bindings.rs` |
| 159 | + |
| 160 | +use bindings::Guest; // Import the trait repr of our WIT world |
| 161 | +use wit_bindgen::rt::string::String as WitString; // Import the WIT string type |
| 162 | + |
| 163 | +struct Component; // Define the type to export |
| 164 | +impl Guest for Component { // Import the WIT world + methods |
| 165 | + fn scream(input: WitString) -> WitString { // Implement our `scream` method |
| 166 | + let mut s = input.to_uppercase(); |
| 167 | + s.push_str("!!1!"); |
| 168 | + s.into() |
| 169 | + } |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +Now if we re-run `cargo component check`, it should compile without any |
| 174 | +warnings. That means we're ready to run: |
| 175 | + |
| 176 | +```bash |
| 177 | +$ cargo component build # Compile our Rust library to a Wasm Component |
| 178 | +``` |
| 179 | + |
| 180 | +This will have written the generated Wasm code to |
| 181 | +`target/wasm32-wasi/debug/playground_jco.wasm`. Once Rust lands support for a |
| 182 | +native WASI 0.2 target, expect this to change to |
| 183 | +`target/wasm32-wasi-p2/debug/playground_jco.wasm` or similar, as the current set |
| 184 | +of workarounds will no longer be needed (important if you're reading this |
| 185 | +anytime after March 2024 or so). |
| 186 | + |
| 187 | +### Writing a JavaScript binary |
| 188 | + |
| 189 | +Okay, it's time to use Jco. First we want to take our Rust code, and generate |
| 190 | +JS bindings from it. We can do this using the `jco transpile command`. Let's |
| 191 | +point it at our newly generated Wasm library, like so: |
| 192 | + |
| 193 | +```bash |
| 194 | +$ jco transpile \ # Call `jco transpile` |
| 195 | + target/wasm32-wasi/debug/playground_jco.wasm \ # Point it at our Rust library |
| 196 | + -o target/jco # Write the result to `target/jco` |
| 197 | +``` |
| 198 | + |
| 199 | +On success it will tell you it's generated a number of JS files as part of |
| 200 | +its CLI output: |
| 201 | + |
| 202 | +```text |
| 203 | +Transpiled JS Component Files: |
| 204 | +
|
| 205 | + - target/jco/interfaces/wasi-cli-environment.d.ts 0.09 KiB |
| 206 | + - target/jco/interfaces/wasi-cli-exit.d.ts 0.16 KiB |
| 207 | + - target/jco/interfaces/wasi-cli-stderr.d.ts 0.17 KiB |
| 208 | + - target/jco/interfaces/wasi-cli-stdin.d.ts 0.17 KiB |
| 209 | + - target/jco/interfaces/wasi-cli-stdout.d.ts 0.17 KiB |
| 210 | + - target/jco/interfaces/wasi-clocks-wall-clock.d.ts 0.11 KiB |
| 211 | + - target/jco/interfaces/wasi-filesystem-preopens.d.ts 0.2 KiB |
| 212 | + - target/jco/interfaces/wasi-filesystem-types.d.ts 2.71 KiB |
| 213 | + - target/jco/interfaces/wasi-io-error.d.ts 0.08 KiB |
| 214 | + - target/jco/interfaces/wasi-io-streams.d.ts 0.58 KiB |
| 215 | + - target/jco/playground_jco.core.wasm 1.83 MiB |
| 216 | + - target/jco/playground_jco.core2.wasm 16.5 KiB |
| 217 | + - target/jco/playground_jco.d.ts 0.72 KiB |
| 218 | + - target/jco/playground_jco.js 40 KiB |
| 219 | +``` |
| 220 | + |
| 221 | +The next step is to import this into our Node.js file. In order to do this, we |
| 222 | +first have to update our `package.json` to declare that we're writing an ES |
| 223 | +module. In our `package.json` set the field `"type"` to `"module"`: |
| 224 | + |
| 225 | +```json |
| 226 | +{ |
| 227 | + "name": "playground-jco", |
| 228 | + "version": "1.0.0", |
| 229 | + "type": "module", // ← Add this line |
| 230 | + // ... |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +And then finally, we're ready to write some JS code. we're going to import the |
| 235 | +`scream` function from our Rust library, pass it a string, and then print the |
| 236 | +output of the string. |
| 237 | + |
| 238 | +```js |
| 239 | +import { scream } from "./target/jco/playground_jco.js"; |
| 240 | + |
| 241 | +let msg = scream("chashu would like some tuna"); |
| 242 | +console.log(msg); |
| 243 | +``` |
| 244 | + |
| 245 | +Now if we run this using `node index.js`, we should get the following output: |
| 246 | + |
| 247 | +```text |
| 248 | +CHASHU WOULD LIKE SOME TUNA!!1! |
| 249 | +``` |
| 250 | + |
| 251 | +And with that, we've successfully written our first Rust component for Node.js. |
| 252 | +You can give yourself a little round of applause - because this is *incredibly |
| 253 | +cool*! By leveraging WIT and Wasm, we've been able to define a language-agnostic |
| 254 | +interface which resulted in strongly typed code for both programming languages. |
| 255 | +And by leveraging code generation, all of the hard FFI and data marshalling bits |
| 256 | +have been handled for us - enabling us to focus on what is truly important |
| 257 | +(screaming we want tuna). |
| 258 | + |
| 259 | +In this example we happened to use JavaScript and Rust in order to demo the Jco |
| 260 | +runtime. But what's important to understand is that this could have just as |
| 261 | +easily been using JavaScript and Go. Or Rust and Python. The point of the Wasm |
| 262 | +Components and WASI is that we can connect any number of languages to each other |
| 263 | +via a strongly typed ABI. |
| 264 | + |
| 265 | +We've intentionally kept things fairly simple: one method which takes strings |
| 266 | +in, and returns strings out. However the [latest version of |
| 267 | +WASI](https://bytecodealliance.org/articles/WASI-0.2) (0.2) provides among other |
| 268 | +things access to asynchronous sockets, timers, and http. Using the same broad |
| 269 | +techniques we've shown in this post, it should be possible to for example write |
| 270 | +your networked applications using different languages, all linked to one another |
| 271 | +using strongly typed WIT interfaces. |
| 272 | + |
| 273 | +## Conclusion |
| 274 | + |
| 275 | +In this post we’ve introduced Jco 1.0.0: a native JavaScript runtime which can |
| 276 | +run WASI 0.2 components. We’ve discussed what has been stabilized, which |
| 277 | +features are currently in progress, as well as future directions. Finally we’ve |
| 278 | +shown an example of how to use Jco to embed a Rust library inside of a Node.js |
| 279 | +project. |
| 280 | + |
| 281 | +We're happy to report that several projects in the wild are already succesfully |
| 282 | +using Jco to build their projects with. One particularly impressive project |
| 283 | +is by the inimitable [Catherine "whitequark"](https://github.com/whitequark), |
| 284 | +who has [ported the YoWASP FPGA toolchain to the |
| 285 | +browser](https://mastodon.social/@whitequark/111632628704217359) using Jco. |
| 286 | +[This project](https://vscode.dev/github/YoWASP/toolchain-demo) enables users to |
| 287 | +flash FPGA hardware directly from their browser over WebUSB, and even works on |
| 288 | +mobile devices. |
| 289 | + |
| 290 | +[Jco][jco] is a Bytecode Alliance project, designed and led by Guy Bedford (Fastly). |
| 291 | +The 1.0 release of Jco was made possible with the assistance of Pat Hickey |
| 292 | +(Fastly), Wassim Chegham (Microsoft), Dirk Bäumer (Microsoft), and Yosh Wuyts |
| 293 | +(Microsoft). On behalf of the Bytecode Alliance, we’d like to thank everyone who |
| 294 | +was involved with the conception, design, and development of Jco. And we’re very |
| 295 | +excited for people to be able to start using it in their projects! |
| 296 | + |
| 297 | +[jco]: https://github.com/bytecodealliance/jco |
0 commit comments