Git Product home page Git Product logo

mogwai's Introduction


mogwai

minimal, obvious, graphical web application interface

Crates.io

mogwai is a view library for creating GUI applications. It is written in Rust and runs in your browser and has enough functionality server-side to do rendering. It is an alternative to React, Backbone, Ember, Elm, Purescript, etc.

goals

  • provide a declarative approach to creating and managing view nodes
  • encapsulate component state and compose components easily
  • explicate view mutation
  • be small and fast (aka keep it snappy)

concepts

The main concepts behind mogwai are

  • sinks and streams instead of callbacks

    View events like click, mouseover, etc are sent through streams instead of invoking a callback. Streams can be mapped, filtered and folded.

  • widget views are dumb

    A view is just a struct that mutates the UI tree after receiving a message from a stream. Views are constructed and nested using plain Rust functions or an RSX macro.

  • widget logic is one or more async tasks that loop over event messages

    Widgets use asynchronous task loops that receive events from the view and send updates to the view in response.

example

Here is an example of a button that counts the number of times it has been clicked:

use mogwai_dom::prelude::*;

#[derive(Default)]
struct Button {
    clicks: usize,
    click: Output<()>,
    text: Input<String>,
}

impl Button {
    /// Convert into a `ViewBuilder`
    fn builder(mut self) -> ViewBuilder {
        rsx! (
            button(on:click=self.click.sink().contra_map(|_: JsDomEvent| ())) {
                // Using braces we can embed rust values in our UI tree.
                // Here we're creating a text node that starts with the
                // string "Clicked 0 times" which updates every time a
                // message is received on the stream.
                {("Clicked 0 times", self.text.stream().unwrap())}
            }
        ).with_task(async move {
            while let Some(()) = self.click.get().await {
                self.clicks += 1;
                self.text.set(if self.clicks == 1 {
                    "Clicked 1 time".to_string()
                } else {
                    format!("Clicked {} times", self.clicks)
                }).await.unwrap();
            }
        })
    }
}

let btn = Button::default();
// Get a sink to manually send events.
let mut click_sink = btn.click.sink();
// Build the view to render in the browser
let view = Dom::try_from(btn.builder()).unwrap();
// Attach it to the browser's DOM tree
view.run().unwrap();

// Spawn asyncronous updates
wasm_bindgen_futures::spawn_local(async move {
    // Queue some messages for the view as if the button had been clicked:
    click_sink.send(()).await.unwrap();
    click_sink.send(()).await.unwrap();
    // view's html is now "<button>Clicked 2 times</button>"
});

introduction

If you're interested in learning more - please read the introduction and documentation.

why

  • No vdom diffing keeps updates snappy and the implementation minimal
  • Async logic by default
  • Explicit mutation
  • ViewBuilder allows running on multiple platforms (web, ios, android, desktop, etc)

mogwai uses streams, sinks, a declarative view builder and async logic to define components and how they change over time.

View mutation is explicit and happens as a result of views receiving messages, so there is no performance overhead from vdom diffing.

If you prefer a functional style of programming with lots of maps and folds - or if you're looking to go vroom! then maybe mogwai is right for you :)

Please do keep in mind that mogwai is still in alpha and the API is actively changing - PRs, issues and questions are welcomed. As of the 0.6 release we expect that the API will be relatively backwards compatible.

made for rustaceans, by a rustacean

mogwai is a Rust first library. There is no requirement that you have npm or node. Getting your project up and running without writing any javascript is easy enough.

benchmarketing

mogwai is snappy! Here are some very handwavey and sketchy todomvc metrics:

mogwai performance benchmarking

ok - where do i start?

First you'll need new(ish) version of the rust toolchain. For that you can visit https://rustup.rs/ and follow the installation instructions.

Then you'll need wasm-pack.

For starting a new mogwai project we'll use the wonderful cargo-generate, which can be installed using cargo install cargo-generate.

Then run

cargo generate --git https://github.com/schell/mogwai-template.git

and give the command line a project name. Then cd into your sparkling new project and

wasm-pack build --target web

Then, if you don't already have it, cargo install basic-http-server or use your favorite alternative to serve your app:

basic-http-server -a 127.0.0.1:8888

Happy hacking! ☕ ☕ ☕

cookbook

📗 Cooking with Mogwai is a series of example solutions to various UI problems. It aims to be a good reference doc but not a step-by-step tutorial.

group channel ☎️

Hang out and talk about mogwai in the support channel:

more examples please

Examples can be found in the examples folder.

To build the examples use:

wasm-pack build --target web examples/{the example}

Additional external examples include:

sponsorship

Please consider sponsoring the development of this library!

mogwai's People

Contributors

bryanjswift avatar erikdesjardins avatar fbucek avatar plaets avatar schell avatar stoically avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mogwai's Issues

[Example] Shared state between different component events

Looking for examples on how we would share state between different events:

Borrowing from your README.md Example.

I'd like to see an example where we render 2 buttons and they share the clicks state.

Heres a React Example: of what I mean
https://codesandbox.io/s/isolated-vs-shared-q2ii5?file=/src/App.tsx

Mogwai code:

extern crate log;
extern crate console_log;
extern crate console_error_panic_hook;
extern crate mogwai;
extern crate serde;
extern crate serde_json;

use log::Level;
use mogwai::prelude::*;
use std::panic;
use wasm_bindgen::prelude::*;


// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;



pub struct Button {
  pub clicks: i32
}

#[derive(Clone)]
pub enum ButtonIn {
  Click
}

#[derive(Clone)]
pub enum ButtonOut {
  Clicks(i32)
}

impl Component for Button {
  type ModelMsg = ButtonIn;
  type ViewMsg = ButtonOut;
  type DomNode = HtmlElement;

  fn update(
    &mut self,
    msg: &ButtonIn,
    tx_view: &Transmitter<ButtonOut>,
    _subscriber: &Subscriber<ButtonIn>
  ) {
    match msg {
      ButtonIn::Click => {
        self.clicks += 1;
        tx_view.send(&ButtonOut::Clicks(self.clicks))
      }
    }
  }

  fn view(
    &self,
    tx: Transmitter<ButtonIn>,
    rx: Receiver<ButtonOut>
  ) -> Gizmo<HtmlElement> {
    button()
      .rx_text("Clicked 0 times", rx.branch_map(|msg| {
        match msg {
          ButtonOut::Clicks(n) => format!("Clicked {} times", n)
        }
      }))
      .tx_on("click", tx.contra_map(|_| ButtonIn::Click))
  }
}

#[wasm_bindgen]
pub fn main() -> Result<(), JsValue> {
  panic::set_hook(Box::new(console_error_panic_hook::hook));
  console_log::init_with_level(Level::Trace)
    .unwrap();
    div().with(
      Button{ clicks: 0 }.into_component()
    ).with(
      Button{ clicks: 0 }.into_component()
    )
    .run()
}

Post 0.5 updates

  • export less of futures
  • fix the examples in the introduction, as some are inconsistent
  • fix the logo on the crates page (should be absolute)

[IDEA] TUI and other View types

mogwai would be very well suited for updating a terminal user interface as it would make it simple to only update the part of the screen that needs it. Currently the Component trait is geared towards HTML only, so would it be an interesting idea to split it up into a Component and something like a WebView trait, that way some other view traits could be made, like perhaps a CursiveView for one such TUI (or something lower level would even fit better) for something else, and perhaps others could be made for other purposes in the future as well. I could imagine Gizmo getting a type argument to determine what view it's pulling from, and as such how to wire things up.

`futures` crate is unused

Excepted for Transmitter::send_async when target architecture is not "wasm32". In order to improve compatibility with other runtimes this should not be relied on. At least it is not used in the main mogwai crate, some of the examples depend on it (or at least include it in their dependencies) though.

Consider giving GizmoBuilder a tyvar.

For example:
GizmoBuilder<HtmlElement>
GizmoBuilder<HtmlInputElement>
GizmoBuilder<HtmlSVGElement>
The problem to solve is how to nest GizmoBuilder(s).

// TODO: Consider giving GizmoBuilder a tyvar.
// For example:
// * `GizmoBuilder<HtmlElement>`
// * `GizmoBuilder<HtmlInputElement>`
// * `GizmoBuilder<HtmlSVGElement>`
// The problem to solve is how to nest GizmoBuilder(s).
/// Construction and wiring for DOM elements.

  • Found on master

Fragment Syntax or ViewBuilder<Vec<HtmlElement>>

I'm aware not all JSX idioms don't always map to TEA projects, but I was wondering if it might be nice to be able to do either of these:

JSX Fragment Syntax

fn component() -> ViewBuilder<HtmlElement> {
    builder! {
        <>
            <div></div>
            <div></div>
        </>
    }
}

fn parent_component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="wrap">
            {component()}
        </div>
    }
}

ViewBuilder<Vec<HtmlElement>>

fn component() -> ViewBuilder<Vec<HtmlElement>> {
    builder! {
        <div></div>
        <div></div>
    }
}

fn parent_component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="wrap">
            {component()}
        </div>
    }
}

Right now I have to do it this way, which is perfectly fine, I suppose:

fn component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="mydiv">
            <div></div>
            <div></div>
        </div>
    }
}

fn parent_component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="wrap">{component()}</div>
    }
}

Just a nice-to-have.

Mogwai with updates from global state changes

I suspect that managing state independently of interface is a key problem. I think this is the problem Redux is architected to solve. I come down this path from Fulcro (https://github.com/fulcrologic/fulcro; Clojure) which very elegantly decouples components from state by having each do a query against a normalised database.

Is there any appetite to create the layer on top of Mogwai which enables updates to a global state, which then propagate to one or more components?

static STATE: Mutex<HashMap<PersonID, (Rc<Person>, Transmitter<PersonChange>)> = Mutex::new(HashMap::new());

[Example] - Build more than a single html page.

Any example on how we can build more than 1 page. Doesn't have to be some crazy spa routing solution but rather just build some static pages from mogwai.

For Example:
/index.html
/about.html
/work.html
/work/post-1.html

Creating a View/ViewBuilder from a Vec

Hi

Is it possible to create a list of elements?

impl From<Vec<HtmlElement>> for ViewBuilder<HtmlElement> {
    fn from(vec: Vec<HtmlElement>) -> Self {
        builder! {
            // How do you construct a ViewBuilder or View from a Vec of elements?
        }
    }
}

I've found no example, and none of the code I've looked at is using a Vec of items. I'd want to pre-create a list of items so that they are displayed at once, instead of sending them one by one.

Thank you for a great framework!

SSR hydration

Support "hydrating" a DomWrapper from an html document.

LockedTransmitter

In component update functions you don't want the library user to send
because it causes a race condition (caught and thrown by a mutex). Make it so
a Transmitter can do all the wiring stuff and can send, but a
LockedTransmitter

pub struct Transmitter<A> {
next_k: Arc<Mutex<usize>>,
branches: Arc<Mutex<HashMap<usize, Box<dyn FnMut(&A)>>>>,
}

Recipe for updating DOM structure based on Receiver

While investigating how I would implement something with multiple pages (refs #32) I was trying to set up an enum of routes and have different a different structure/view depending on the route. Looking at the ViewBuilder it looks like Effect and Receiver can only be used to create a ViewBuilder if the type argument is a string value (so a text node) and I could not find an example where something other than text was being updated based on txrx functions. For reference, below is (an iteration) of what I was trying (based on 0081a29); I'm fairly sure this is incorrect conceptually because it isn't seeing an updated value in the Receiver so I'm not trying to fix the code below but rather figure out how it should have been written to work with Receiver correctly.

use crate::routes;
use log::trace;
use mogwai::prelude::*;

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Route {
    Home,
    NotFound,
}

#[derive(Debug)]
pub struct App {
    current_route: Route,
}

impl App {
    pub fn new() -> Self {
        App {
            current_route: Route::Home,
        }
    }
}

impl Component for App {
    type ModelMsg = Route;
    type ViewMsg = ();
    type DomNode = HtmlElement;

    fn update(&mut self, msg: &Route, tx_view: &Transmitter<()>, _sub: &Subscriber<Route>) {
        trace!("{:?}", msg);
        self.current_route = msg.clone();
        tx_view.send(&());
    }

    fn view(&self, tx: &Transmitter<Route>, _rx: &Receiver<()>) -> ViewBuilder<HtmlElement> {
        trace!("{:?}", &self);
        let main = match self.current_route {
            // pub fn home() -> ViewBuilder<HtmlElement>
            Route::Home => routes::home(),
            // pub fn not_found() -> ViewBuilder<HtmlElement>
            Route::NotFound => routes::not_found(),
        };
        builder! {
            <div class="root">
                <nav>
                    <button on:click=tx.contra_map(|_| Route::Home)>
                        "Home"
                    </button>
                    <button on:click=tx.contra_map(|_| Route::NotFound)>
                        "Not Found"
                    </button>
                </nav>
                <main>
                    {main}
                </main>
            </div>
        }
    }
}

Cookbook

Use mdbook to publish a mogwai cookbook.

Async update function for Component

It seems it would be nice to have Component::update be an async function. Investigate this.

Instead of

fn update(&mut self, msg: &InMsg, tx_view: &Transmitter<OutMsg>, sub: &Subscriber<InMsg>) {

I think it would be

async fn update(&mut self, msg: InMsg, tx_view: Transmitter<OutMsg>, sub: Subscriber<InMsg>) {

Question about target

Nice work done here! I have a question, is there a reason that --target no-modules is used in examples? Since es6 modules support somewhere as good as webassembly support amongst all browsers, it is fine to use --target web.
Instead of

<script src="pkg/mogwai_todo.js"></script>
<script type="module">
    window
        .wasm_bindgen('pkg/mogwai_todo_bg.wasm')
         .then( m => m.main() )
         .catch(console.error);
</script>

there will be

<script type="module">
import { init } from './pkg/mogwai_todo.js';
window.addEventListener('load', async () => {
    await init();
});
</script>

HTML definition to string

On targets other than wasm32, building a DomWrapper should create a server side node - aka a node that does not actually contain a web_sys::Element. It should be able to be serialized to string.

Add to cookbook how to listen to multiple events for the same element

How do I listen for two or more events on the same html element. For example, listening to focusin and focusout events on the same element

#[wasm_bindgen]
pub fn main(parent_id: Option<String>) -> Result<(), JsValue> {
    panic::set_hook(Box::new(console_error_panic_hook::hook));
    console_log::init_with_level(Level::Trace).unwrap();

    let (block_patch_tx, block_patch_rx) = txrx::<Patch<ViewBuilder<HtmlElement>>>();
    let (focused_tx, focused_rx) = (block_patch_tx.clone(), block_patch_rx.clone());

    let view = View::from(builder! {
        <section class="frow direction-column">
            <div id="editor" class="frow direction-column width-100" data-block-editor="browser-wasm">
                <div contenteditable="true" class="frow direction-column width-100 row-center" data-block="heading1"

                     //how do I listen to multiple events here
on:focusin=block_tx.clone().contra_map(move |_: &Event| {
                        block_patch_tx.send(&Patch::<ViewBuilder<HtmlElement>>::Insert{index: 0, value: textops()})
                    })
                    on:focusout=block_tx.clone().contra_map(move |_: &Event| {
                        block_patch_tx.send(&Patch::<ViewBuilder<HtmlElement>>::Remove{index: 0})
                    })
                patch:children=block_patch_rx>
                    <div>"This is heading 1"</div>
                </div>
        </section>
    });

    if let Some(id) = parent_id {
        let parent = utils::document().get_element_by_id(&id).unwrap();

        view.run_in_container(&parent)
    } else {
        view.run()
    }
}

fn textops() -> ViewBuilder<HtmlElement>{

} 

Does mogwai support code-splitting?

Hi,

A few days ago I posted the question above in a different issue, but I guess the notification was lost somehow, so I post it again as its own issue (which anyway makes more sense).

In the above-linked comment I wrote the following:

I'm thinking about creating a Rust/WASM-based static website generator similar to Gastby.js (which basically is a combination of a static website generator and a client-side JavaScript application framework).

One requirement for this is code-splitting (if a website has, say, 10.000 pages, it wouldn't be very performant if all 10k pages are loaded on the first visit... ;)).

(The great thing with such an approach is that you can include small (or big) apps and dynamic widgets, but otherwise you still have a static website, with all its benefits).

At this point, I have decided to build the static website generator now, so I'm currently in the phase where I evaluation the technology that I will use – and mogwai looks like a very interesting crate so far.

Downside of loading struct with view information

Hey @schell ,

Sorry I'm so long getting this feedback to you, but I was uneasy that your proposed solution for nesting had the reference to the view in the Component class.

Would it be possible for the solution to leave the impl Component "clean" in that it has only problem-domain fields. This would mean that Gizmo etc. data structures would need to take up the burden, but has the massive benefit that the domain model is untouched except for impl Component.

Cheers,
Alister.

Where do I start Section produces error

Rust noob here so excuse me if I made a dumb mistake:

Where Do I start doc

Producing this error when running build step:

C:\Users\matth\@dev\mogwai\demo>wasm-pack build --target no-modules
[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
   Compiling demo v0.0.0 (C:\Users\matth\@dev\mogwai\demo)
error[E0599]: no method named `build` found for type `mogwai::gizmo::Gizmo<mogwai::prelude::HtmlElement>` in the current scope
  --> src\lib.rs:29:6
   |
29 |     .build()?

HtmlTextAreaElement and mogwai 0.5.0

Hi, is it possible to send content to a <textarea> element somehow?

The <textarea> element requires a call to HtmlTextAreaElement::value() attribute but I haven't found a way to do this from the view or logic code.

I can't find a way to cast an element inside a builder! {} ViewBuilder to HtmlTextAreaElement to call the value() method, and I can't seem to send messages to it in any way that calls the value() method.

Note this is not the <textarea value= /> attribute, which you can send messages to fine, but it's a method in javascript on the element.

Some help with how to do this would be highly appreciated!

Thank you!

[Feature] - Support for render-to-string and partial-hydration

Preface:
I think Mogwai has a huge potential and thank you for making it!

The cargo generate .wasm bundle is the smallest I've seen uncompressed 48kb to 18kb gz and prob 25% smaller Brotli! Not too shabby, and therefore, you should promote these number in the github README.md:
image

And while I haven't personally battle tested, it would seem your benchmarks are superior to all of the alternatives:

image

I think one more thing to promote is the benefit that wasm is pre-parsed/compiled which makes the cost of JavaScript far smaller:
image

Background

I more frequently make (over-engineer) large(100k-1m pages) marketing sites. When building these sites I have always some key foci: Developer experience, speed & performance, SEO/a11y , and rich user experience.

I'm personally looking for a no-compromise framework; one that affords the opportunity to build static sites or a robust application.

Feature Request

There are 3 features I'd like to see from Mogwai

  1. render-to-string
  2. Partial/selective hydration:
  3. code-splitting (I don't think it is possible in wasm, yet)

Render-to-String

In something like preact it would look like this:

const html = require('htm');
const renderToString = require('preact-render-to-string');

const App = (props) => html`
  <div class="app">
    <h1>This is an app</h1>
    <p>Current server time: ${new Date + ''}</p>
  </div>
`;
const doc = renderToString(App);

Output

  <div class="app">
    <h1>This is an app</h1>
    <p>Current server time: Sun Jun 21 2020 10:55:20 GMT-0700 (Pacific Daylight Time) </p>
  </div>

The power in this is we could write Mogwai and generate a pure js static website.

That said, 100% JavaScript free website can be boring. We could make make a runtime that can rehydrate the prerendered html from the render-to-string fn above:

Link

A Rehydration Problem: One App for the Price of Two
Rehydration issues can often be worse than delayed interactivity due to JS. In order for the client-side JavaScript to be able to accurately “pick up” where the server left off without having to re-request all of the data the server used to render its HTML, current SSR solutions generally serialize the response from a UI’s data dependencies into the document as script tags. The resulting HTML document contains a high level of duplication:

image

This full hydration step is pretty status quo today. It gets a bit better with routing and this is 100% ok and personally I'm 100% ok with this approach.

But if you're curious, theirs a new approach called selective or partial hydration. The key here is to select the components that may need hydration and essentially wrap them. I've found a lot of benefit doing this hydration with web components:

import {RunningHeader} from '../running-header';

export function autoHydrate(Component, name) {
 <hydrate-root name=${name} />
    <${Component} ...${props} />
    <script
        type="text/hydration"
        dangerouslySetInnerHTML=${{
      __html: JSON.stringify({props})
    }}
    />

const HydratedRunningHeader = autoHydrate(RunningHeader, {name: 'hello});
import {HydratedRunningHeader} from '../components/running_header';
import {h, hydrate} from 'preact';


class HydrateRoot extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback() {
    const childNodes = [];
    let $end = this;
    let data = {};
    
    while (($end = $end.nextSibling)) {
      if (
          $end.nodeName === "SCRIPT" &&
          $end.getAttribute("type") === "text/hydration"
      ) {
        try {
          data = JSON.parse($end.textContent) || {};
        } catch (e) {}
        break;
      }
      childNodes.push($end);
    }

    this.root = {
      childNodes,
      appendChild: c => this.parentNode.insertBefore(c, $end)
    };
    hydrate(h(HydratedRunningHeader, data.props), this.root);
  }
}
export {HydrateRoot};

If we merge these two ideas we could take the original idea and pass in props:

const html = require('htm');
const renderToString = require('preact-render-to-string');

const App = (props) => html`
  <div class="app">
    <h1>This is an ${props.name}</h1>
    <p>Current server time: ${new Date + ''}</p>
  </div>
`;
const HydratedApp = autoHydrate(App , {name: 'HELLO WORLD'});
const doc = renderToString(HydratedApp);

Output:

  <hydrate-root name='App' />
  <div class="app">
    <h1>This is an HELLO WORLD</h1>
    <p>Current server time: Sun Jun 21 2020 10:55:20 GMT-0700 (Pacific Daylight Time) </p>
  </div>
 <script type="text/hydration">
   {props: {name: 'HELLO WORLD'}}
 </script>

When the hydrated-root bootstraps it will read the dom until it hits the script tag, it will JSON.parse the props and hydrate them back into the component.

In this example, with this approach, you can selectively or paritally bundle only the interactive components of an application/page and therefore, make the absolute smallest JavaScript bundle possible.

Ok this was a huge post but would love to hear back from you... in the mean time i'mma give mogwai a shot lol.

suggestion: GizmoBuilder::conditional_boolean_attribute

GizmoBuilder::boolean_attribute sets the attribute to "true" unconditionally.
In my current project, I often want to set a boolean attribute to a value that is not hardcoded, but also not changed based on events. It's dynamically computed when creating the builder. It would be much easier if there was a method conditional_boolean_attribute that took a boolean parameter and only set the attribute if this parameter was true.
Currently, I'm doing something like:

let builder = create_my_fancy_builder();
let builder = if condition { builder.boolean_attribute("my_attr") } else { builder };
builder

It would be great if I could replace this with

create_my_fancy_builder().conditional_boolean_attribute("my_attr", condition)

I think the second variant would be both easier to write and to read.

HTML macro

Create a dom! html/xml macro for defining DomWrapper

is this still maintained?

Hi there - this looks really interesting, but the lack of any commits since May concerns me slightly - is this still actively used and maintained? Is this simply "complete"?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.