• Stars
    star
    1,262
  • Rank 37,273 (Top 0.8 %)
  • Language
  • License
    Apache License 2.0
  • Created about 5 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Know JS, want to try Rust, but not sure where to start? This is for you!

Rust for JavaScript peeps

Rust for JavaScript peeps

Translations: Chinese

Introduction

People seem to like Rust a lot! But if you're coming from JavaScript, not everything may make a lot of sense at first. But no problem; this guide is for you!

Probably the first question is: "What is Rust?" You may have heard it being described as: "A systems programming language", or "A modern alternative to C++". And while I don't think those descriptions are wrong, I don't think they tell the full story either.

The way I think about Rust is as a language with a wide range of applications. On the one end we have a language with a package manager, which makes it convenient to write web applications, create data processing pipelines, and create user interfaces. But on the other end we have a language that can precisely manipulate memory layout, call into kernel APIs, and even write inline assembly β€” the things for which in JavaScript you'd need to write native C++ extensions.

In this guide we'll be focusing on the bits which are most similar to JavaScript: writing classes, functions, and control flow. The idea is that if we can get you to a spot where you're comfortable, you can use that as a starting point to dive deeper into Rust, and gradually learn more about what the language has to offer.

Quick Start

Alright. So you want to write Rust? Step one is to get yourself a working environment. This means installing tools. Here's an overview of what you need (more or less in-order):

Installing Rust

rustup: this is like nvm for Node, but officially supported and really well done! It helps you install and manage Rust compiler versions.

Installing Rustup also installs a valid compiler toolchain, which gives you 3 new commands in total

  • $ rustup: runs "rustup" to manage your compiler toolchain
  • $ rustc: which is the Rust compiler. You'll never need to call this because of:
  • $ cargo: cargo is to Rust, what npm is to Node. It builds, compiles, installs and more. Getting to know cargo well is usually time well-spent!

Installing packages

cargo-edit provides essential extensions to cargo. In particular: it allows you to run cargo add which works similar to npm install. In rust-lingo, "packages" are called "crates".

cargo install works similar to npm install -g. And when you run cargo add only your Cargo.toml file (Rust's package.json file) is updated with the right versions. Run cargo build or cargo check to actually download and compile dependencies.

You can install cargo-edit by running:

$ cargo install cargo-edit

Formatting code

rustfmt is Rust's version of prettier. Everyone uses it, and even if the default config might take some getting used to, it's what everyone uses.

It's a binary component that hooks into the compiler, so it needs to be installed with rustup:

$ rustup component add rustfmt

This should take a few seconds on a fast connection. Whenever you update your rust version, rustfmt will also be updated.

Important commands are:

$ cargo fmt                    # runs rustfmt to format your code
$ cargo fmt -- --check         # do a dry-run, outputting a diff of changes that would be made
$ cargo fmt -- --edition=2018  # pass this flag if you're doing stuff with async/await

linting code

clippy is a "linting" tool for Rust, similar to standard's style lints. cargo fmt takes care of formatting. rustc takes care of correctness. But clippy is in charge of helping you write "idiomatic" Rust.

This doesn't mean every lint in Clippy is perfect. But when you're getting started it can be suuuper helpful to run!

Editors and IDE support

Rust has a language-server implementation in the form of rust-analyzer. This provides IDE support for just about any editor. If you're not sure which editor to start with: consider using VSCode! - It has really good integration with language servers, and should make it easy to get started writing Rust.

Testing

Cargo ships with a cargo test command, which will run both doctests and files under test/. The Rust book has a whole chapter dedicated to testing you should read on this. But to get you started, you can copy this boilerplate into any file into your src/ directory, and cargo test will pick it up:

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn smoke_test() {
        assert_eq!(1, 1);
    }
}

This creates a module within your file which only compiles when cargo test is run. It imports all functions and types from the outer scope to the mod test scope, and then defines a single test smoke_test which is executed. You can add more test by writing more #[test] functions.

Creating new projects

You can create new projects using cargo new or cargo init. new creates a new directory, init outputs files in the current directory. It's pretty basic, but it's useful to get started with. If you want to write a library you can pass either command the --lib flag. By default you'll create binaries (applications with a main function that can be run).

There's also the newer cargo-generate project. This is a more powerful version of the built-in cargo commands, and allows you to pick from templates. You may not need this if you're just messing around, but it's probably good to be aware of.

Publishing

cargo publish works like npm publish. The central repository is called crates.io and is very similar to NPM. Importantly it's not owned by a scummy for-profit company, but is instead part of the Rust project.

If you've built something nice in Rust, consider going ahead and publishing it to Crates.io. All that's needed is a GitHub account to sign up, and you're good to go!

Documentation

Most docs in JS seem to either be written in a README.md, or as part of some special website. In Rust documentation is generated automatically using rustdoc.

You can run rustdoc through $ cargo doc. Every package on crates.io also has documentation generated for you on docs.rs. It's even versioned, so you can check out older documentation too. For example: you can find async-std's docs under docs.rs/async-std.

Writing docs in Rust is by using "doc comments" (/// instead of the regular // comments). You'll see a bunch in the rest of this guide. Important documentation commands are:

$ cargo doc            # generate docs
$ cargo doc --open     # generate docs and then open them
$ rustup doc --std     # open the stdlib docs offline
$ rustup doc --book    # open the "Rust Programming Language" offline

watching projects

It can sometimes be tedious to run cargo check after every change. Which is why cargo-watch exists. You can install it by running:

$ cargo install cargo-watch

Important cargo-watch commands are:

$ cargo watch              # Run "cargo check" on every change
$ cargo watch -x "test"    # Run "cargo test" on every change

Terminology

Before we continue, let's establish some quick terminology:

  • Struct: like an "object" in JS. It can both be a data-only type. But can also work like a "class" with methods (both inherent and static).
  • Vec: like a JS "array".
  • Slice: like a "view" into a TypedArray in JS. You can't grow them, but you can create a "view of a view" (slice of a slice).
  • Traits: essentially the answer to the question: "what if a class could inherit from multiple other classes". Traits are also referred to as "mixins" in other languages. They don't allocate any data, but only provide methods and type definitions related to those methods.

Thinking in Rust

Aside of the obvious type system stuff, I think there are a few core differences between Rust and JS:

Object-Oriented everything

In Rust everything is object-oriented. Imports are always done through namespaces, and namespaces kind of behave like structs.

// println comes from the "std" namespace -- print something to the screen.
std::println!("hello world");

// Call the "new" method from the `HashMap` type from the `std::collections`
// namespace
let hashmap = std::collections::HashMap::new();

Classes & Structs

Structs don't have "constructor" methods the way JS do; instead you define a method that returns Self (which is a shorthand for the name of the struct).

/// How to instantiate:
/// ```js
/// let rect = new Rectangle(5, 10);
/// ```
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
/// How to instantiate:
/// ```rs
/// let rect = Rectangle::new(5, 10);
/// ```
pub struct Rectangle {
    height: usize,
    width: usize,
}

impl Rectangle {
    /// Create a new instance.
    pub fn new(height: usize, width: usize) -> Self {
        Self { height, width }
    }
}

Expressions!

Everything is an expression. Which is to say: blocks suddenly have a lot more meaning, and you can do some fun substitutions.

These are all equivalent:

let y = 1 + 1;
let x = y * y;
if x == 4 {
    println!("hello world");
}
// If we omit the `;` from a statement, it becomes the return value of the
// block it's in.
let x = {
    let y = 1 + 1;
    y * y
};
if x == 4 {
    println!("hello world");
}
// Expressions means that you can inline almost anything. Don't actually do this
// please.
if {
    let y = 1 + 1;
    y * y
} == 4 {
    println!("hello world");
}

Know your self

There are 3 kinds of self: self, &self, &mut self.

pub struct Rectangle {
    height: usize,
    width: usize,
}

impl Rectangle {
    pub fn new(height: usize, width: usize) -> Self {
        Self { height, width }
    }

    /// Get the height
    ///
    /// We want to reference the value as read-only,
    /// so we use `&self` for shared access.
    pub fn height(&self) -> usize {
        self.height
    }

    /// Set the height
    ///
    /// We want to reference the value as "writable", so we use
    /// `&mut self` for exclusive access.
    pub fn set_height(&mut self, height: usize) -> usize {
        self.height = height
    }

    /// Get the height + width as a tuple.
    ///
    /// We want to "consume" the current struct, and return its internal parts.
    /// So instead of taking a reference, we take an owned value `self` after
    /// which the struct can no longer be used, and return a tuple (anonymous
    /// struct) containing its internals.
    pub fn parts(self) -> (usize, usize) {
        (self.height, self.width)
    }
}

This is the core of everything around the borrow checker. If you have exclusive access to a variable, nobody else can have access to that variable too and you can mutate it. If you have shared access to a variable, others may too, but you're not allowed to update the value. That's how data races are prevented!

There's some escape hatches using RefCell, Mutex and other things to get around this; but they apply clever tricks internally to uphold the same guarantees at runtime rather than compile-time. Less efficient, but same rules!

That's it! Everything else is basically an application of these rules.

Handling null values

In JavaScript you can use null to show that a value hasn't been initialized yet. Rust doesn't have null, instead you need to manually mark values which can be uninitialized by using the Option type. This is generally how you do it in JavaScript:

let cat = {
    name: "chashu",
    favorite_food: null // we can initialize a key as "null"
}
cat.favorite_food = "tuna"; // ... and then later assign values to them.

In Rust we need to go through the Option type for this.

// Define our the shape of our type.
struct Cat {
    name: String,
    favorite_food: Option<String>,
}

// Create a new instance of our type. Note that it needs to be mutable
// so we can change values on it later. 
let mut cat = Cat {
    name: "Chashu".to_string(),
    favorite_food: None // this is short for `Option::None`
};

// ... and then we assign a value here.
cat.favorite_food = Some("tuna".to_string()); // this is short for `Option::Some`

This is our first look at enums: types which encapsulate a piece of state. Here we see the enum Option, which can either be None to mark no value has been set, or Some which contains an inner value.

Switch cases and if/else blocks

Rust not only has if/else for control flow, it also has a concept of match. This is somewhat similar to JavaScript's switch statement. Let's translate some JavaScript control flow to Rust:

let num = 1
switch (num) {
    case 0:
        break; // handle case 0
    case 1:
        break; // handle case 1
    default:
        throw new Error('oops')
}

And converting this to Rust we could write it as:

let num = 1;
match num {
    0 => todo!("handle case 0"),
    1 => todo!("handle case 1"),
    _ => panic!("oops"),
}

This creates a case for 0, for 1, and provides a fallback case for all other numbers. If we didn't add the fallback case the compiler would not let us compile!

Match blocks are useful to quickly match on patterns. We can compare numbers to other numbers, but also strings with each other, and importantly: compare enum variants. Say we wanted to check whether the Option we defined earlier was a Some variant, or a None variant we could write it like this:

struct Cat {
    name: String,
    favorite_food: Option<String>,
}

fn has_favorite_food(cat: Cat) -> bool {
    match cat.favorite_food {
        Some(_) => true, // return `true` if our cat has a favorite food
        None => false,   // return `false` if no favorite food has been provided
    }
}

This looks whether our Option is Some or None, and returns a different bool depending on the case. We're not using the return keyword for this because match statements are expressions too, so simply by omitting the semicolon the booleans becomes the return values for our function.

Say we wanted to do something with the string within Some, we could give it a variable name. We could for example print different messages depending on the inner value.

match cat.favorite_food {
    Some(s) => println!("our favorite food is: {}", s),
    None => println!("we have no favorite food yet :("),
}

match statements are really common in Rust (more common than switch statements in JavaScript), so it's worth playing around with them to see what you can do. Can you make a match statement work for different string values? What about defining enums? What if an enum contains another enum, can you match on that?

State machines (enums)

What we saw with Rust's Option is that we can have two variants: Some and None. This is kind of like a small state machine. Probably the simplest example of a state machine in Rust is if we write our own version for bool. We know this can be in one of two states: true and false. We could write it as an enum like so:

// our bool enum, we're using uppercase names because the lowercase
// names are keywords
enum Bool {
    True,
    False,
}

Now booleans in Rust are a bit special, and not actually implemented like an enum. But Option is, and the definition of it is this:

enum Option<T> {
    None,
    Some(T),
}

There's some new syntax here <T>, which means this type is generic. But all you need to know about this now is that we can store a different type (like String) inside of it. Bool doesn't store any values, so it doesn't need any generics.

Handling errors

If Option is Rust's enum to handle null, then Result is Rust's enum to handle Error. Instead of Some and None, it returns Ok and Error. The definition for Result is:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

This might look pretty overwhelming: we now have two generic arguments! But you don't need to worry too much about this yet. For now all you need to remember is that Ok can return a value, and Err can return a different value. For a practical example, let's read a file using Node.js:

const fs = require('fs')

// try to read a file
try {
    let file = fs.readFileSync("./README.md")
    console.log(`read ${file.length} chars`)
} catch(e) {
    console.error(e) // handle error
}

And translating this to Rust we can write it using fs::read_to_string:

use std::fs;

fn main() {
    match fs::read_to_string("./README.md") {
        Ok(file) => println!("read {} chars", file.len()),
        Err(e) => eprintln!("{}", e), // handle error
    };
}

Overall error handling with Rust's match can feel pretty similar to JavaScript's try...catch. The main difference you'll notice is that functions which can error in Rust will always return Result in their signature.

Re-throwing errors

In JavaScript we can catch errors using try...catch. And from within catch blocks we can throw errors again:

let file
try {
    file = readFileSync(path)
} catch (e) {
    throw e
}
// use `file` here

If we don't wrap our functions in try...catch blocks, errors will be automatically re-thrown from the calling function until we hit another try...catch block or the root of the program.

function inner(path) {
    let buf = fs.readFileSync(path)
    buf.length
}

function outer() {
    try {
        inner("README.md")
        inner("uh-oh")
    } catch (e) {
        console.error(e)
    }
}

outer()

Re-throwing errors is pretty common in JavaScript: you (or the framework you use) usually has some top-level try...catch block that catches all errors from within the framework. It's enough to use throw somewhere within your user code, and it'll be picked up by the framework.

As we mentioned: in Rust functions which can error must return Result, and we can handle Result using match. We can write a re-throw in Rust like this:

let file = match fs::read_to_string(path) {
    Ok(file) => file,
    Err(e) => return Err(e),
};

This will either assign the Ok value to the variable file (of type String) so we can continue to use it in our function. Or if we have an Err we immediately return from our function using Err. However if we had to write this everytime we wanted to re-throw an error, it'd be a lot of code to write. So for that Rust has the "try" operator: ?. With it we can replace the match { return } block with a call to ? like so:

let file = fs::read_to_string(path)?;

This isn't quite as easy to write as JavaScript's automatic rethrowing. But it can be helpful in debugging and reading code, as every call which could throw an error is neatly marked with ?.

async/await

You may be aware that Rust has support for async/.await much like JavaScript has. Since this is a guide for folks coming from JavaScript who are new to Rust, my advice is this: become comfortable with the basics of non-async Rust before using async Rust. Async Rust is very much a work-in-progress. A lot of things don't work yet, or can yield errors which require contextual knowledge. We've come a long way, but we have a long way to go still.

With that warning out of the way, let's quickly cover some of the basics!

In JavaScript async/await is used to write async code which reads much like synchronous code. For example we can rewrite our file reading example from synchronous to asynchronous like so:

const fs = require('fs/promises')

(async function () {
    try {
        let file = await fs.readFile("./README.md")
        console.log(`read ${file.length} chars`)
    } catch(e) {
        console.error(e)
    }
})()

Here we see we can rewrite our synchronous call to be asynchronous by importing the "promise" version of readFile. And then we can call it from within an async function by adding the await call in front of it.

Unlike Node.js, Rust doesn't have asynchronous IO bindings built-in yet. So we have to import a third-party library from crates.io which provides those bindings for us. The async-std library was designed as a drop-in asynchronous replacement for the stdlib (I'm a co-author), so let's use that for our example:

use async_std::fs;

#[async_std::main]
async fn main() {
    match fs::read_to_string("./README.md").await {
        Ok(file) => println!("read {} chars", file.len()),
        Err(e) => eprintln!("{}", e),
    };
}

Much like JavaScript, we import an async version of fs, wrap our code in an async function, and then call it with .await. Just like the try operator (?), so too does await go at the end of the call.

Internally JavaScript's async/await, and Rust's async/.await are implemented fairly similarly: JavaScript's async desugars to "Promises" and "generators". Rust's async desugars to "Futures" and "generators". The main difference between the two systems though is that JavaScript's "Promises" start executing the moment they're created, while Rust's Futures only start when they're .awaited.

In that sense a Rust Future is more similar to a JavaScript "thenable". While a JavaScript Promise is more similar to a Rust Task.

Passing configuration and options

Instead of using opts or default values, most things use builders instead. Kind of the way superagent works:

let opts = {
  method: 'GET',
  headers: {
    'X-API-Key': 'foobar',
    'Accept': 'application/json'
  }
};

try {
  let res = await fetch('/api/pet', opts);
} catch(err) {
  throw err
}
superagent.post('/api/pet')
  .set('X-API-Key', '<secret>')
  .set('Accept', 'application/json')
  .end((err, res) => {
    // Calling the end function will send the request
  });

We can do the same in Rust. Here is an example using the surf HTTP client:

let res = surf::post("/api/pet")
    .header("X-API-Key", "<secret>")
    .header("Accept", "application/json")
    .await?; // Calling .await will send the request

Internally builders generally take self and return self as the output so you can chain the methods together.

Outro

Hopefully, this is somewhat useful for JS peeps looking at Rust. There's a lot more that should be written here, but hopefully, this is somewhat helpful!

License

MIT OR Apache-2.0

More Repositories

1

vmd

πŸ™ preview markdown files
JavaScript
1,181
star
2

notes

notes on things
839
star
3

tiny-guide-to-non-fancy-node

A tiny guide to non fancy, high-value Node.js things
735
star
4

futures-concurrency

Structured concurrency operations for async Rust
Rust
403
star
5

github-standard-labels

Create a standard set of issue labels for a GitHub project
JavaScript
241
star
6

sheet-router

fast, modular client-side router
JavaScript
223
star
7

pretty-hot-ranking-algorithm

Algorithm that measures how relevant a given data set is, kinda like Reddit
JavaScript
204
star
8

markdown-to-medium

Publish markdown to medium
JavaScript
198
star
9

html

Type-safe HTML support for Rust
HTML
186
star
10

barracks

🚞 action dispatcher for unidirectional data flows
JavaScript
177
star
11

virtual-html

🌴 HTML β†’ virtual-dom
JavaScript
174
star
12

es2020

Because in hindsight we don't need most of ES6
JavaScript
126
star
13

dotfiles-linux-2019

Linux desktop config
Shell
126
star
14

miow

A zero-overhead Windows I/O library, focusing on IOCP
Rust
111
star
15

fsm-event

🎰 stateful finite state machine
JavaScript
91
star
16

changelog

Changelog generator
Rust
85
star
17

vel

minimal virtual-dom library
JavaScript
84
star
18

fd-lock

Advisory cross-platform file locks using file descriptors
Rust
69
star
19

exponential-backoff

Exponential backoff generator with jitter.
Rust
66
star
20

memdb

Thread-safe in-memory key-value store.
Rust
64
star
21

previewify

Preview tool for applications
JavaScript
62
star
22

server-router

Server router
JavaScript
61
star
23

speaking

Slides, proposals and more for talks I give
JavaScript
57
star
24

electron-collection

Set of helper modules to build Electron applications
JavaScript
57
star
25

cache-element

Cache an HTML element that's used in DOM diffing algorithms
JavaScript
56
star
26

mdjson

πŸ“– Transform markdown to an object where headings are keys
JavaScript
55
star
27

context-attribute

Set the error context using doc comments
Rust
53
star
28

copy-template-dir

High throughput template dir writes
JavaScript
52
star
29

newspeak

πŸ’¬ natural language localization
JavaScript
51
star
30

assert-snapshot

Snapshot UI testing for tape tests
JavaScript
50
star
31

polite-element

Politely waits to render an element until the browser has spare time
JavaScript
45
star
32

choo-persist

Synchronize choo state with indexedDB
JavaScript
44
star
33

power-warn

Warn on low power level.
Rust
42
star
34

base-elements

A selection of configurable native DOM UI elements
JavaScript
41
star
35

nanostack

Small middleware stack library
JavaScript
40
star
36

millennial-js

πŸ’
CSS
39
star
37

on-intersect

Call back when an element intersects with another
JavaScript
35
star
38

github-templates

Generate .github templates
Rust
35
star
39

microcomponent

Smol event based component library
JavaScript
35
star
40

observe-resize

Trigger a callback when an element is resized
JavaScript
33
star
41

hypertorrent

Stream a torrent into a hyperdrive
JavaScript
31
star
42

npm-install-package

Install an npm package
JavaScript
30
star
43

async-iterator

An async version of iterator
Rust
30
star
44

rust-lib-template

Rust lib template repository
Rust
29
star
45

heckcheck

A heckin small test generator
Rust
29
star
46

maxstache

Minimalist mustache template replacement
JavaScript
28
star
47

normcore

No-config distributed streams using hypercore
JavaScript
28
star
48

winstall

Install all dependencies required by a project
JavaScript
27
star
49

chic

Pretty parser error reporting.
Rust
27
star
50

validate-formdata

Data structure for validating form data
JavaScript
25
star
51

playground-tide-mongodb

Example using tide + mongodb
Rust
24
star
52

playground-nanoframework

Building tiny frameworks yo
JavaScript
24
star
53

hyperlapse

Distributed process manager
JavaScript
23
star
54

promise-each

Call a function for each value in an array and return a Promise
JavaScript
23
star
55

omnom

Streaming parser extensions for BufRead
Rust
23
star
56

virtual-widget

Create a virtual-dom widget
JavaScript
22
star
57

tasky

fluent async task spawning experiments
Rust
22
star
58

debug-to-json

πŸ”§ Convert debug logs to JSON
JavaScript
22
star
59

http-sse

Create server-sent-events
JavaScript
21
star
60

initialize

Generate a fresh package
JavaScript
21
star
61

kv-log-macro

Log macro for logs kv-unstable backend
Rust
21
star
62

github-changelist

Generate a list of merged PRs since the last release
Rust
21
star
63

assert-html

Assert two HTML strings are equal
JavaScript
21
star
64

cargo-task-wasm

A sandboxed local task runner for Rust
Rust
21
star
65

workshop-distributed-patterns

Learn how to create robust multi-server applications in Node
HTML
20
star
66

crossgen

Cross compilation template generator
Rust
20
star
67

electron-crash-report-service

Aggregate crash reports for Electron apps
JavaScript
19
star
68

pid-lite

A small PID controller library
Rust
19
star
69

playground-virtual-app

playground with some virtual-* tech
JavaScript
19
star
70

virtual-raf

Create a RAF loop for virtual-dom
JavaScript
19
star
71

promise-map

Map over an array and return a Promise.
JavaScript
19
star
72

how

how(1) - learn how to do anything
Rust
18
star
73

secure-password

Safe password hashing.
Rust
18
star
74

futures-time

async time combinators
Rust
18
star
75

microanalytics

Capture analytics events in the browser
JavaScript
18
star
76

noop2

No operation as a moduleβ„’
Makefile
18
star
77

github-to-hypercore

Stream a github event feed into a hypercore
JavaScript
17
star
78

hyperreduce

Distributed reduce on top of hypercore
JavaScript
17
star
79

microframe

Smol requestAnimationFrame package
JavaScript
17
star
80

github_auth

Authenticate with GitHub from the command line.
Rust
17
star
81

virtual-streamgraph

Create a virtual-dom streamgraph
JavaScript
16
star
82

rust-cli

rust(1) cli prototype
Rust
16
star
83

extract-html-class

Extract all classes from html
JavaScript
16
star
84

templates

Template files used to generate things
Shell
16
star
85

from2-string

Create a stream from a string. Sugary wrapper around from2
JavaScript
16
star
86

fin

Simple finance visualizations
JavaScript
16
star
87

async-collection

Collection of async functions
JavaScript
15
star
88

document-ready

Document ready listener for browsers
Rust
15
star
89

buffer-graph

Resolve a dependency graph for buffer creation
JavaScript
15
star
90

choo-pull

Wrap handlers to use pull-stream in a choo plugin
JavaScript
15
star
91

json-stream-to-object

Parse a JSON stream into an object
JavaScript
15
star
92

promise-reduce

Reduce an array and return a Promise
JavaScript
14
star
93

formdata-to-object

Convert a formData object or form DOM node to a KV object
JavaScript
14
star
94

ergonomic-viewport

Get the current ergonomic viewport
JavaScript
14
star
95

choo-model

Experimental state management lib for choo
JavaScript
14
star
96

shared-component

Share a component instance inside a window context
JavaScript
13
star
97

multipart-read-stream

Read a multipart stream over HTTP
JavaScript
13
star
98

nanopubsub

Tiny message bus
JavaScript
13
star
99

server-render

HTML server rendering middleware
JavaScript
13
star
100

const-combinations

Experiment to get k-combinations working as a const fn
Rust
13
star