Key points:

  • Both Deno and Node.js execute JavaScript on a C/C ++ based runtime for high performance.
  • Deno is a single binary application that is not compatible with NPM modules, and there is no easy way to incorporate native modules into the application.
  • WebAssembly provides a way to run high-performance code in Deno applications.
  • WebAssembly is a secure, lightweight, and lightweight container for server-side applications.
  • The Rust compiler toolchain provides powerful support for WebAssembly.

The much anticipated Deno project has just released version 1.0. Deno was started by Ryan Dahl, one of the creators of Node.js, to address what Ryan considers “ten things I regret about Node.js.”

Deno does not adopt NPM and the infamous node_modules. Deno is a single binary executable that runs applications written in TypeScript and JavaScript.

But while TypeScript and JavaScript are suitable for most Web applications, they are not good for computationally intensive tasks such as neural network training and reasoning, machine learning, and cryptography. In fact, node.js often needs to use local libraries to perform these tasks (for example, using OpenSSL for encryption).

Without an NPM-like system to incorporate native modules, how can we write server-side applications that require native performance on Deno? WebAssembly will help! In this article, we write high-performance functions in Rust, compile Rust functions into WebAssembly, and then run them in a Deno application.

TL; DR

Clone or fork the Deno Starter template on GitHub. Follow the instructions below and in five minutes you can run the WebAssembly function (written by Rust) in Deno.

Background knowledge

Node.js is so successful because it gives developers the best of both worlds: the ease of use of JavaScript (especially for event-based asynchronous applications) and the high performance of C/C ++. Node.js applications are written in JavaScript, but executed in a native runtime based on C/C ++, such as the Google V8 JavaScript engine and many native library modules. Deno wants to replicate this formula to succeed, but, differently, Deno supports modern technology stacks with TypeScript and Rust.

Deno is a simple, modern, and secure runtime based on V8 JavaScript and TypeScript, written in Rust. – deno. Land sites.

In the famous talk “Ten Things I Regret about Node.js”, Node.js creator Ryan Dahl explains why he started Deno and sees Deno as a competitor or even a replacement for Node.js. Dahl’s regrets centered on how Node.js manages third-party code and modules.

  • A complex build system for connecting C modules to Node.js.
  • package.json.node_modules.index.jsAnd other NPM artifacts are very complex, but this is not necessary.

As a result, Deno has made some very conscious and conscious choices in managing dependencies.

  • Deno is a single binary executable.
  • Applications are written in TypeScript or JavaScript, and dependencies are explicitly declared in the code asimportStatement with the full URL to connect to the dependency source code.
  • Deno is not compatible with the Node.js module.

That’s good. But what about applications that need more performance? In particular, what about AI-as-a-service applications that need to execute complex neural network models in a matter of seconds? In Deno and Node.js, many functions are called via the TypeScript or JavaScript API, but these functions are executed in native code written in Rust or C. In Node.js, there is always the option to call third-party local libraries from the JavaScript API. But can’t we do this in Deno right now?

WebAssembly support in Deno

WebAssembly is a lightweight virtual machine designed to execute portable bytecode at near native speed. You can compile Rust or C C ++ functions into WebAssembly bytecode and then access those functions from TypeScript. For some tasks, this is much faster than executing equivalent functions written in TypeScript. For example, research published by IBM found that using Rust and WebAssembly in some data processing algorithms can increase the execution speed of Node.js by between 1200 and 1500 percent.

Internally, Deno uses Google’s V8 engine. V8 is not only a JavaScript runtime, but also a WebAssembly virtual machine. Deno supports WebAssembly out of the box. Deno provides aN API for TypeScript applications to call functions in WebAssembly.

In fact, some popular Deno components are already implemented in WebAssembly. For example, use Emscripten to compile sqLite’s C source code into WebAssembly to create an SQLite module in Deno. The Deno WASI component enables WebAssembly applications to access underlying operating system resources, such as file systems. This article shows you how to write high-performance Deno applications in Rust and WebAssembly.

Set up the

The first step, of course, is to install Deno, which on most operating systems is a one-line command

$ curl -fsSL https://deno.land/x/install/install.sh | sh

Since we want to write functions with Rust, we also need to install the compiler and tools for the Rust language.

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Finally, the SSvmup tool automates the build process and generates all artifacts, making it easy for Deno applications to call Rust functions. Again, the SSvmup dependency needs to be installed.

$ curl https://raw.githubusercontent.com/second-state/ssvmup/master/installer/init.sh -sSf | sh

Copy the code

Note: SSVMup uses Wasm-Bindgen to automatically generate “glue” code between JavaScript and Rust source code so that JavaScript and Rust can communicate using their respective native data types. Without SSVMup, function arguments and return values are limited to simple types (that is, 32-bit integers) that WebAssembly natively supports. For example, strings or arrays cannot be used without SSVMup and wasm-bindGen.

Hello world

First, let’s take a look at Deno’s Hello World example, getting the Hello World source code and application template from GitHub.

The Rust function, located in the SRC /lib.rs file, simply prefixes the input string with “hello.” Note that the say() function is annotated with # [wasm_bindgen], allowing SSVMup to generate the necessary “pipes.” Based on this, we can call the Rust function from TypeScript.

#[wasm_bindgen]
pub fn say(s: &str) -> String {
  let r = String::from("hello ");
  return r + s;
}
Copy the code

The Deno application is located in the Deno/server.ts file. The application imports Rust’s say() function from the PKG/functions_lib.js file, which is generated by the ssvmup tool. The functions_lib.js file name depends on the Rust project name defined in the Cargo. Toml file.

import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { say } from '.. /pkg/functions_lib.js';

type Resp = {
    body: string;
}

const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
  let r = {} as Resp;
  r.body = say (" World\n");
  req.respond(r);
}
Copy the code

Now, run ssvmup to build the Rust function into a Deno WebAssembly function.

$ ssvmup build --target deno

After ssvmup is completed successfully, you can examine the PKG/functions_lib.js file to see how to use the Deno WebAssembly API to execute the compiled WebAssembly file PKG/functions_lib.wasm.

Next, run the Deno application. Deno needs access to the file system because it needs to load the WebAssembly file. At the same time, Deno also needs to access the network because it needs to receive and respond to HTTP requests.

$ deno run --allow-read --allow-net deno/server.ts

Now, in another terminal window, you can access the Deno Web application and say Hello! Over an HTTP connection.

$ curl http://localhost:8000/

hello World

A complicated example

This getting started template project includes a number of detailed examples showing how to pass complex data between Deno TypeScript and Rust functions. This is the other Rust function in SRC /lib.rs. Note that they are all annotated with # [wasm_bindgen].

#[wasm_bindgen]
pub fn obfusticate(s: String) -> String {
  (&s).chars().map(|c| {
    match c {
      'A' ..= 'M' | 'a' ..= 'm' => ((c as u8) + 13) as char,
      'N' ..= 'Z' | 'n' ..= 'z' => ((c as u8) - 13) as char,
      _ => c
    }
  }).collect()
}

#[wasm_bindgen]
pub fn lowest_common_denominator(a: i32, b: i32) -> i32 {
  let r = lcm(a, b);
  return r;
}

#[wasm_bindgen]
pub fn sha3_digest(v: Vec<u8>) -> Vec<u8> {
  return Sha3_256::digest(&v).as_slice().to_vec();
}

#[wasm_bindgen]
pub fn keccak_digest(s: &[u8]) -> Vec<u8> {
  return Keccak256::digest(s).as_slice().to_vec();
}
Copy the code

Perhaps the most interesting is the create_line() function. This function requires two JSON strings. Each string represents a Point structure and returns a JSON string representing a Line structure. Note that both the Point and Line structures are annotated with Serialize and Deserialize so that the Rust compiler automatically generates the necessary code to support conversion between them and JSON strings.

use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
  x: f32,
  y: f32
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
  points: Vec<Point>,
  valid: bool,
  length: f32,
  desc: String
}

#[wasm_bindgen]
pub fn create_line (p1: &str, p2: &str, desc: &str) -> String {
  let point1: Point = serde_json::from_str(p1).unwrap();
  let point2: Point = serde_json::from_str(p2).unwrap();
  let length = ((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)).sqrt();

  let valid = ifLength = = {0.0false } else { true };
  letline = Line { points: vec! [point1, point2], valid: valid, length: length, desc: desc.to_string() };return serde_json::to_string(&line).unwrap();
}

#[wasm_bindgen]
pub fn say(s: &str) -> String {
  let r = String::from("hello ");
  return r + s;
}
Copy the code

Next, let’s examine the JavaScript program deno/test.ts, which shows how to call the Rust function. String and &str are simple strings in JavaScript, i32 is a number, and Vec

or &[8] is JavaScript Uint8Array. JavaScript objects need to pass json.stringify () or json.parse () before Rust can be passed in or returned from Rust.

import { say, obfusticate, lowest_common_denominator, sha3_digest, keccak_digest, create_line } from '.. /pkg/functions_lib.js';

const encoder = new TextEncoder();

console.log( say("SSVM")); console.log( obfusticate("A quick brown fox jumps over the lazy dog")); console.log( lowest_common_denominator(123, 2) ); console.log( sha3_digest(encoder.encode("This is an important message"))); console.log( keccak_digest(encoder.encode("This is an important message"))); Var p1 = {x:1.5, y:3.8}; Var p2 = {x:2.5, y:5.8}; var line = JSON.parse(create_line(JSON.stringify(p1), JSON.stringify(p2),"A thin red line"));
console.log( line );
Copy the code

After running ssvmup to build the Rust library, running Deno /test.ts in the Deno runtime produces the following output:

$ ssvmup build --target deno
... Building the wasm file and JS shim file inpkg/ ... $ deno run --allow-read deno/test.ts hello SSVM N dhvpx oebja sbk whzcf bire gur ynml qbt 246 Uint8Array(32) [ 87, 27, 231, 209, 189, 105, 251, 49,......]  Uint8Array(32) [ 126, 194, 241, 200, 151, 116, 227, ... ... ] {points: [{x: 1.5, y: 3.8}, {x: 2.5, y: 5.8}], valid:trueLength: 2.2360682, desc:"A thin red line"
}
Copy the code

What’s next?

Now we can create the Rust function and access it from a Deno TypeScript application. Next, we can use Rust functions to write computationally intensive tasks and provide high-performance and secure Web services through Deno. Examples of such services include machine learning and image recognition.

🏆 technology project phase I | talk about Deno some thing…