Jco Example Workflow

Given an existing Wasm Component, jco provides the tooling necessary to work with this Component fully natively in JS.

Jco also provides an experimental feature for generating components from JavaScript by wrapping ComponentizeJS in the jco componentize command.

To demonstrate a full end-to-end component, we can create a JavaScript component embedding Spidermnokey then run it in JavaScript.

Installing Jco

Either install Jco globally:

$ npm install -g @bytecodealliance/jco
$ jco --version
1.0.3

Or install it locally and use npx to run it:

$ npm install @bytecodealliance/jco
$ npx jco --version
1.0.3

Local usage can be preferable to ensure the project is reproducible and self-contained, but requires replacing all jco shell calls in the following example with either ./node_modules/.bin/jco or npx jco.

Installing ComponentizeJS

To use ComponentizeJS, it must be separately installed, globally or locally depending on whether Jco was installed globally or locally. Globally:

$ npm install -g @bytecodealliance/componentize-js

Or locally:

$ npm install @bytecodealliance/componentize-js

Now the jco componentize command will be ready to use.

Creating a Component with ComponentizeJS

This Cowsay component uses the following WIT file (WIT is the typing language used for defining Components):

cowsay.wit

package local:cowsay;
world cowsay {
  export cow: interface {
    enum cows {
      default,
      owl
    }
    say: func(text: string, cow: option<cows>) -> string;
  }
}

We can implement this with the following JS:

cowsay.js

export const cow = {
  say (text, cow = 'default') {
    switch (cow) {
      case 'default':
return `${text}
  \\   ^__^
    \\  (oo)\\______
      (__)\\      )\/\\
          ||----w |
          ||     ||
`;
      case 'owl':
return `${text}
   ___
  (o o)
 (  V  )
/--m-m-
`;
    }
  }
};

To turn this into a component run:

$ jco componentize cowsay.js --wit cowsay.wit -o cowsay.wasm

OK Successfully written cowsay.wasm with imports ().

Inspecting Component WIT

As a first step, we might like to look instead this binary black box of a Component and see what it actually does.

$ jco wit cowsay.wasm
package root:component;

world root {
  export cow: interface {
    enum cows {
      default,
      owl,
    }

    say: func(text: string, cow: option<cows>) -> string;
  }
}

Transpiling to JS

To execute the Component in a JS environment, use the jco transpile command to generate the JS for the Component:

$ jco transpile cowsay.wasm -o cowsay

Transpiled JS Component Files:

 - cowsay/cowsay.core.wasm     7.61 MiB
 - cowsay/cowsay.d.ts          0.07 KiB
 - cowsay/cowsay.js            2.62 KiB
 - cowsay/interfaces/cow.d.ts  0.21 KiB

Now the Component can be directly imported and used as an ES module:

test.js

import { cow } from './cowsay/cowsay.js';

console.log(cow.say('Hello Wasm Components!'));

For Node.js to allow us to run native ES modules, we must first create or edit the local package.json file to include a "type": "module" field:

package.json

{
  "type": "module"
}

The above JavaScript can now be executed in Node.js:

$ node test.js

 Hello Wasm Components!
  \   ^__^
    \  (oo)\______
      (__)\      )/\
          ||----w |
          ||     ||

Passing in the optional second parameter, we can change the cow:

test.js

import { cow } from './cowsay/cowsay.js';

console.log(cow.say('Hello Wasm Components!', 'owl'));
$ node test.js

 Hello Wasm Components!
   ___
  (o o)
 (  V  )
/--m-m-

It can also be executed in a browser via a module script:

<script type="module" src="test.js"></script>

There are a number of custom transpilation options available, detailed in the API section.