Skip to content

Commit 4a540c5

Browse files
yoshuawuytssquillaceguybedford
authored
Announcing jco 1.0 (#87)
* Create 2024-02-19-jco-1.0.md * Update 2024-02-19-jco-1.0.md * Update 2024-02-19-jco-1.0.md * Update 2024-02-19-jco-1.0.md * Update 2024-02-19-jco-1.0.md * Update 2024-02-19-jco-1.0.md * Update 2024-02-19-jco-1.0.md Co-Authored-By: Ralph Squillace <ralph@squillace.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Ralph Squillace <ralph@squillace.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Ralph Squillace <ralph@squillace.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Ralph Squillace <ralph@squillace.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Ralph Squillace <ralph@squillace.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Ralph Squillace <ralph@squillace.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Guy Bedford <598730+guybedford@users.noreply.github.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Guy Bedford <598730+guybedford@users.noreply.github.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Guy Bedford <598730+guybedford@users.noreply.github.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Guy Bedford <598730+guybedford@users.noreply.github.com> * Update _posts/2024-02-19-jco-1.0.md Co-authored-by: Guy Bedford <guybedford@gmail.com> * Update 2024-02-19-jco-1.0.md Co-Authored-By: Guy Bedford <598730+guybedford@users.noreply.github.com> --------- Co-authored-by: Ralph Squillace <ralph@squillace.com> Co-authored-by: Guy Bedford <598730+guybedford@users.noreply.github.com> Co-authored-by: Guy Bedford <guybedford@gmail.com>
1 parent 64438c3 commit 4a540c5

1 file changed

Lines changed: 297 additions & 0 deletions

File tree

_posts/2024-02-19-jco-1.0.md

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
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

Comments
 (0)