Creating a Better “Hello, World”

Most of the information in the book thus far can be applied universally to all kinds of WebAssembly applications written in all kinds of languages. That path diverges in this chapter. From here on out, everything in the book will be specific to Rust.

More importantly, everything you do after this point will rely on tools or libraries created by the Rust community. As interest in WebAssembly with Rust grows, we can only expect this community to grow, and the power and usefulness of its tools and libraries to grow along with it. Even as young as WebAssembly is, you already have quite a few Rust tools at your disposal.

While you have a degree of choice when it comes to JavaScript bindings, the one that you’ll be using in this chapter is wasm-bindgen[17]. This is a combination of a set of crates (Rust’s name for shared libraries, though there’s more nuance to them than that) that support bindings between JavaScript and Rust.

At its core, wasm-bindgen injects a bunch of metadata into your compiled WebAssembly module. Then, a separate command-line tool reads that metadata, strips it out, and uses that information to generate an appropriate JavaScript “wrapper bridge” containing the kinds of functions, classes, and other primitives that the developer wants bound to Rust.

Installing the New Tools

wasm-bindgen uses procedural macros and a few other features that at one point were only available in the nightly build of Rust. Thankfully, during the course of writing this book, Rust’s support for those features is now stable.

Now that you’re going to be writing a bit more JavaScript, you’ll be using features that often call for the use of npm. Refer to the instructions[18] for your operating system to install Node and the Node Package Manager (npm).

Finally, you’ll need to install the wasm-bindgen command-line tool. To do that, you’ll use cargo, Rust’s build tool:

 $ ​​cargo​​ ​​install​​ ​​wasm-bindgen-cli

If through previous experiments you’ve already installed wasm-bindgen and you want to force the installation of the latest version, add --force to the end of the cargo install command. This might take quite a while as there are a large number of dependencies and each one gets compiled after the source is downloaded.

Creating a New Rust WebAssembly Project

In this section you’ll be creating a new WebAssembly module that makes use of wasm-bindgen bindings and its CLI, as well as webpack, npm, and a few other tools. As a self-identified “back end” developer, I get a little nervous when people mention all of these JavaScript build tools. Don’t worry, though, we only need a few, and most of that is just to get a basic web server running. Also as a non-authority on JavaScript, JavaScript developers may find far more optimal ways of accomplishing some of the tasks in this chapter than the way I’ve outlined.

You’ll go through the process of setting up this “Hello, World” piece by piece, and when you’re done, you’ll have a nice template that you can use as scaffolding to build future projects (which will come in handy in the second half of this chapter).

To start, create a new Rust project called bindgenhello (this is in the jsint_bindgenhello directory in the book’s code samples) in a clean root directory:

 $ ​​cargo​​ ​​new​​ ​​bindgenhello​​ ​​--lib

This should look familiar. As with all the other Rust WebAssembly projects, you need to change its library type to cdylib in Cargo.toml. Also, add a reference to wasm-bindgen (and make sure you delete the “2018 edition” line if you have it):

 [package]
 name = ​"bindgenhello"
 version = ​"0.1.0"
 authors = [​"Your Name <[email protected]>"​]
 
 [lib]
 crate-type = ​["cdylib"]
 
 [dependencies]
 wasm-bindgen = ​"0.2"

The last time you went through this exercise, you created a simple function that performed addition. This time, you’ll clean out the lib.rs file and replace it with the following:

 extern​ crate wasm_bindgen;
 use​ ​wasm_bindgen​::​prelude​::*;
 
 // Import 'window.alert'
 #[wasm_bindgen]
 extern​ ​"C"​ {
 fn​ ​alert​(s: &str);
 }
 
 // Export a 'hello' function
 #[wasm_bindgen]
 pub​ ​fn​ ​hello​(name: &str) {
 alert​(&format!(​"Hello, {}!"​, name));
 }

Decorating Rust code with #[wasm_bindgen] triggers the invocation of a compile-time Rust macro. Each time the compiler encounters this macro, it generates some code on your behalf. Some of it will be code that winds up in your .wasm module, but some of it will be metadata used to help generate the corresponding JavaScript output produced by the wasm-bindgen command-line tool.

In this lib.rs file, there are two bindings. The first binds the alert function to the alert JavaScript function. With this binding in place, any Rust code that invokes the alert function will be converted into a bunch of code that invokes the JavaScript alert function from inside a WebAssembly module. Attaching the right window context and making all of the JavaScript pieces work properly is all done for us by wasm-bindgen.

The second binding exposes the hello function. You’ve seen how this kind of function can be exposed before. However, in this case, it takes a reference to a string as a parameter. We know that string parameters aren’t possible in pure WebAssembly, so the generated wrapper code produces the necessary plumbing (in both wast instructions and JavaScript boilerplate) to allow complex data to flow seamlessly between boundaries.

Behind the scenes, memory allocation and disposal functions are created that operate on the module’s linear memory (remember the good old days when you had to do that by hand?). Then, each time wasm-bindgen encounters the need for string allocation, the generated JavaScript invokes those functions. In short, all of the hard work you’ve been doing in the past couple of chapters is now done automatically on your behalf. These wrappers are convenient, of course, but I still firmly believe that you are better off for having learned how things were done “the hard way.”

Go ahead and build this to make sure that you get a valid WebAssembly module:

 $ ​​cargo​​ ​​build​​ ​​--target​​ ​​wasm32-unknown-unknown

There’s one final piece to this compilation that you need to complete when you’re using wasm-bindgen—invoke the CLI to produce a new WebAssembly module and a JavaScript wrapper file:

 $ ​​wasm-bindgen​​ ​​target/wasm32-unknown-unknown/debug/bindgenhello.wasm​​ ​​
 --out-dir​​ ​​.

This drops a new file, bindgenhello_bg.wasm, in the project directory. It also generates the wrapper JavaScript file, bindgenhello.js, and a TypeScript definition (bindgenhello.d.ts) in that directory. Since these are all generated, you might want to exclude them from your version control system, though checking generated code into VCS is actually a pretty nuanced subject, so your mileage may vary. I’ve included a build.sh script in the code samples that produces the .wasm file and then calls wasm-bindgen.

The following function is auto-generated, but it’s worth looking at the wrapper function for hello:

 export​ ​function​ hello(arg0) {
 const​ ptr0 = passStringToWasm(arg0);
 const​ len0 = WASM_VECTOR_LEN;
 try​ {
 return​ wasm.hello(ptr0, len0);
  } ​finally​ {
  wasm.__wbindgen_free(ptr0, len0 * 1);
  }
 }

The memory offset and length of the allocated string are returned from a function called passStringToWasm. This function invokes the generated allocation function inside the WebAssembly module, placing the encoded string in the module’s linear memory and then doing the relevant pointer arithmetic. Having written your own wast code, you should be able to appreciate how great it is to have this code generated on your behalf.

After the hello function is done, the code will free the previously allocated memory via the __wbindgen_free function that wasm-bindgen stuffed into the WebAssembly module for us. With the WebAssembly side of this “Hello, World” done, it’s time to move on to the JavaScript side of the house.

Integrating with JavaScript and npm

In order to run this sample in a browser like you did before, you’ll need a web server and some way of serving up your script content that invokes the wasm module. In addition, for this sample, you’re going to set up a webpack configuration. Follow the appropriate instructions to ensure you’re using the latest version of webpack. If you’re a JavaScript pro (unlike myself), then feel free to use whatever tooling feels most comfortable to you.

There are some handy shortcuts you can use with webpack to do things like automatically generate the index.html. To keep things simple and easier to understand, I’m deliberately not optimizing certain things in this book. You could also choose not to use webpack at all.

The real work happens in the index.js file. This is where it really pays to use additional tools. You can see that it looks like simple, clean, idiomatic JavaScript, even though it’s going through a bridge to integrate with a WebAssembly module. It’s truly the best of both worlds—the Rust code targeting WebAssembly looks like idiomatic Rust, and the JavaScript looks like standard, unmodified JavaScript:

 const​ wasm = ​import​(​'./bindgenhello'​);
 
 wasm
  .then(h => h.hello(​"world!"​))
  .​catch​(console.error);

Next, set up a web pack configuration so that you can use it to manage your JavaScript bundles. Note that the entry point is index.js. Previous versions of this book required some shoe-horning and other shenanigans to get this working, but the WebAssembly ecosystem is always improving, and things are getting simpler every day:

 const​ path = require(​'path'​);
 const​ HtmlWebpackPlugin = require(​'html-webpack-plugin'​);
 const​ webpack = require(​'webpack'​);
 
 module.exports = {
  entry: ​'./index.js'​,
  output: {
  path: path.resolve(__dirname, ​'dist'​),
  filename: ​'index.js'​,
  },
  plugins: [
 new​ HtmlWebpackPlugin(),
 // Have this example work in Edge which doesn't ship `TextEncoder` or
 // `TextDecoder` at this time.
 new​ webpack.ProvidePlugin({
  TextDecoder: [​'text-encoding'​, ​'TextDecoder'​],
  TextEncoder: [​'text-encoding'​, ​'TextEncoder'​]
  })
  ],
  mode: ​'development'
 };

Finally, create a package.json file. In addition to setting up your webpack development web server, you could also use this as a place to automate your build process by calling the build.sh shell script or something similar:

 {
 "scripts"​: {
 "build"​: ​"webpack"​,
 "serve"​: ​"webpack-dev-server"
  },
 "devDependencies"​: {
 "text-encoding"​: ​"^0.7.0"​,
 "html-webpack-plugin"​: ​"^3.2.0"​,
 "webpack"​: ​"^4.11.1"​,
 "webpack-cli"​: ​"^3.1.1"​,
 "webpack-dev-server"​: ​"^3.1.0"
  }
 }

Now you should be able to execute a build script to produce the WebAssembly module and the generated JavaScript files, then npm run serve to start the webpack server. Pointing your WebAssembly-enabled browser at your local host on port 8080 should then pop up a JavaScript alert dialog box. Try this out on your own and bask in the glow of autogenerated JavaScript interop goodness.

With this “Hello, World” project template in hand, it’s time to move on to building something a little more powerful with the help of wasm-bindgen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.143.103