• Stars
    star
    3,488
  • Rank 12,371 (Top 0.3 %)
  • Language
    JavaScript
  • License
    Apache License 2.0
  • Created over 2 years ago
  • Updated about 1 year ago

Reviews

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

Repository Details

A tool for finding memory leaks in web apps

fuite

fuite /fษฅit/ French for "leak"

fuite is a CLI tool for finding memory leaks in web apps.

Introductory blog post

Tutorial video

Usage

npx fuite https://example.com

This will check for leaks and print output to stdout.

By default, fuite will assume that the site is a client-rendered webapp, and it will search for internal links on the given page. Then for each link, it will:

  1. Click the link
  2. Press the browser back button
  3. Repeat to see if the scenario is leaking

For other scenarios, see scenarios.

How it works

fuite launches Chrome using Puppeteer, loads a web page, and runs a scenario against it. It runs the scenario some number of iterations (7 by default) and looks for objects that leaked 7 times (or 14 times, or 28 times). This might sound like a strange approach, but it's useful for cutting through the noise in memory analysis.

fuite looks for the following leaks:

  • Objects (captured with Chrome heap snapshots)
  • Event listeners
  • DOM nodes (attached to the DOM โ€“ detached nodes will show under "Objects")
  • Collections such as Arrays, Maps, Sets, and plain Objects

The default scenario clicks internal links because it's the most generic scenario that can be run against a wide variety of SPAs, and it will often catch leaks if client-side routing is used.

Options

Usage: fuite [options] <url>

Arguments:
  url                        URL to load in the browser and analyze

Options:
  -o, --output <file>        Write JSON output to a file
  -i, --iterations <number>  Number of iterations (default: 7)
  -s, --scenario <scenario>  Scenario file to run
  -S, --setup <setup>        Setup function to run
  -H, --heapsnapshot         Save heapsnapshot files
  -d, --debug                Run in debug mode
  -p, --progress             Show progress spinner (use --no-progress to disable)
  -b, --browser-arg <arg>    Arg(s) to pass when launching the browser
  -V, --version              output the version number
  -h, --help                 display help for command

URL

fuite <url>

The URL to load. This should be whatever landing page you want to start at. Note that you can use --setup for a custom setup function (e.g. to log in with a username/password).

Output

-o, --output <file>

fuite generates a lot of data, but not all of it is shown in the CLI output. To dig deeper, use the --output option to create a JSON file containing fuite's analysis. This contains additional information such as the line of code that an event listener was declared on.

Anything that you see in the CLI, you can also find in the output JSON file.

Iterations

-i, --iterations <number>

By default, fuite runs 7 iterations. But you can change this number.

Why 7? Well, it's a nice, small, prime number. If you repeat an action 7 times and some object is leaking exactly 7 times, it's pretty unlikely to be unrelated. That said, on a very complex page, there may be enough noise that 7 is too small to cut through the noise โ€“ so you might try 13, 17, or 19 instead. Or 1 if you like to live dangerously.

Scenario

--scenario <scenario>

The default scenario is to find all internal links on the page, click them, and press the back button. You can also define a scenario file that does whatever you want:

fuite --scenario ./myScenario.mjs https://example.com

Your myScenario.mjs can export several async functions, most of which are optional.

Here is a template:

// myScenario.mjs

/**
 * OPTIONAL: Setup code to run before each test
 * @param { import("puppeteer").Page } page
*/
export async function setup(page) {
}

/**
 * OPTIONAL: Code to run once on the page to determine which tests to run
 * @param { import("puppeteer").Page } page
 */
export async function createTests(page) {
}

/**
 * REQUIRED: Run a single iteration against a page โ€“ e.g., click a link and then go back
 * @param { import("puppeteer").Page } page
 * @param { any } data
 */
export async function iteration(page, data) {
}

/**
 * OPTIONAL: Teardown code to run after each test
 * @param { import("puppeteer").Page } page
 */
export async function teardown(page) {
}

/**
 * OPTIONAL: Code to wait asynchronously for the page to become idle
 * @param { import("puppeteer").Page } page
 */
export async function waitForIdle(page) {
}

You can delete any optional functions you don't need.

Note that your scenario file can also extend the default scenario.

setup function (optional)

The async setup function takes a Puppeteer Page as input and returns undefined. It runs before each iteration, or before createTests. This is a good place to log in, if your webapp requires a login.

If this function is not defined, then no setup code will be run.

Note that there is also a --setup flag. If defined, it will override the setup function defined in a scenario.

createTests function (optional)

The async createTests function takes a Puppeteer Page as input and returns an array of test data objects representing the tests to run, and the data to pass for each one. This is useful if you want to dynamically determine what tests to run against a page (for instance, which links to click).

If createTests is not defined, then the default tests are [{}] (a single test with empty data).

The basic shape for a test data object is like so:

{
  "description": "Some human-readable description",
  "data": {
    "some data": "which is passed to the test"
  }
}

For instance, your createTests might return:

[
  {
    "description": "My test 1",
    "data": { "foo": "foo" }
  },
  {
    "description": "My test 2",
    "data": { "foo": "bar" }
  }
]

iteration function (required)

The async iteration function takes a Puppeteer Page and iteration data as input and returns undefined. It runs for each iteration of the memory leak test. The iteration data is a plain object and comes from the createTests function, so by default it is just an empty object: {}.

Inside of an iteration, you want to run the core test logic that you want to test for leaks. The idea is that, at the beginning of the iteration and at the end, the memory should be the same. So an iteration might do things like:

  • Click a link, then go back
  • Click to launch a modal dialog, then press the Esc key
  • Hover to show a tooltip, then hover away to dismiss the tooltip
  • Etc.

The iteration assumes that whatever page it starts at, it ends up at that same page. If you test a multi-page app in this way, then it's extremely unlikely you'll detect any leaks, since multi-page apps don't leak memory in the same way that SPAs do when navigating between routes.

teardown function (optional)

The async teardown function takes a Puppeteer Page as input and returns undefined. It runs after each iteration, or after createTests.

If this function is not defined, then no teardown code will be run.

waitForIdle function (optional)

The async waitForIdle function takes a Puppeteer Page and should resolve when the page is considered "idle."

Here is an example idle check:

export async function waitForIdle(page) {
  await new Promise(resolve => setTimeout(resolve, 2000)) // wait 2 seconds
  await page.waitForSelector('#my-element') // wait for element
}

If this function is not defined, then the default idle check is used. The default is based on heuristics, using the network idle and main thread idle.

Setup

--setup <setup>

The --setup option can define a custom setup function, which runs immediately after the page is loaded, but before any other scenario code.

For instance, you can use --setup to log in with a username/password. To do so, first create a file called mySetup.mjs:

export async function setup (page) {
  await page.type('#username', 'myusername');
  await page.type('#password', 'mypassword');
  await page.click('#submit');
}

Then pass it in:

npx fuite https://example.com --setup ./mySetup.mjs

The setup function defined here is the same one that you can define in a custom scenario using --scenario (i.e. it takes a Puppeteer Page as input).

If both --scenario and --setup are defined, then --setup will override the setup function in the scenario.

Heap snapshot

  -H, --heapsnapshot         Save heapsnapshot files

By default, fuite doesn't save any heap snapshot files that it captures (to avoid filling up your disk with large files). If you use the --heapsnapshot flag, though, then the files will be saved in the /tmp directory, and the CLI will output their location. That way, you can inspect them and load them into the Chrome DevTools memory tool yourself.

Debug

  -d, --debug                Run in debug mode

Debug mode lets you drill in to a complex scenario and debug it yourself using the Chrome DevTools. The best way to run it is:

NODE_OPTIONS=--inspect-brk fuite --debug <url>

(Note that NODE_OPTIONS will not work if you run npx fuite. So you have to install fuite locally or globally, e.g. using npm i -g fuite.)

Then navigate to chrome:inspect in Chrome, click "Open dedicated DevTools for Node," and now you are debugging fuite itself.

This will launch Chrome in non-headless mode, and it will also automatically pause before running iterations and afterwards. That way, you can open up the Chrome DevTools and analyze the scenario yourself, take your own heap snapshots, etc.

Progress

-p, --progress             Show progress spinner (use --no-progress to disable)

Enable or disable the progress spinner while the test runs. It's true by default, so you should use --no-progress to disable.

Browser args

-b, --browser-arg <arg>   Arg(s) to pass when launching the browser

This allows you to pass args (aka flags) into Puppeteer when launching the browser. You can define multiple args, and they are passed ver batim to Puppeteer's launch args option.

For example:

fuite <url> -b --use-fake-device-for-media-stream -b --enable-experimental-web-platform-features

JavaScript API

fuite can also be used via a JavaScript API, which works similarly to the CLI:

import { findLeaks } from 'fuite';

const results = findLeaks('https://example.com', {
  scenario: scenarioObject,
  iterations: 7,
  heapsnapshot: false,
  debug: false,
  progress: true,
  browserArgs: ['--use-fake-device-for-media-stream']
});
for await (const result of results) {
  console.log(result);
}

Note that findLeaks returns an async iterable.

This returns the same output you would get using --output <filename> in the CLI โ€“ a plain object describing the leak. The format of the object is not fully specified yet, but a basic shape can be found in the TypeScript types.

Options

The options for findLeaks are basically the same as for the CLI. The only differences between the JavaScript API and the CLI are:

Cancel the test

You can pass in an AbortSignal as the signal option to cancel the test on-demand:

const controller = new AbortController();
const { signal } = controller;
findLeaks('https://example.com', { signal });

// Later
controller.abort();

Scenario object

For the JavaScript API, you can pass in a custom scenario as a plain object. First, define it:

const myScenario = {
  async setup(page) { /* ... */ },
  async createTests(page) { /* ... */ },
  async iteration(page, data) { /* ... */ },
  async teardown(page) { /* ... */ }
};

Then pass it in:

import { findLeaks } from 'fuite';

for await (const result of findLeaks('https://example.com', {
  scenario: myScenario
})) {
  console.log(result);
}

If scenario is undefined, then the default scenario will be used.

Extending the default scenario

If you're writing your own custom scenario, you can also extend the default scenario. For instance, if you want the default scenario, but to be able to log in with a username and password first:

import { defaultScenario, findLeaks } from 'fuite';

const myScenario = {
  ...defaultScenario,
  async setup(page) {
    await page.type('#username', 'myusername')
    await page.type('#password', 'mypassword')
    await page.click('#submit')
  }
};

for await (const result of findLeaks('https://example.com', {
  scenario: myScenario
})) {
  console.log(result);
}

Note that the above works if you're using the JavaScript API. For the CLI, you probably want to use the --setup flag.

Limitations

fuite focuses on the main frame of a page. If you have memory leaks in cross-origin iframes or web workers, then the tool will not find those.

Similarly, fuite measures the JavaScript heap size of the page, corresponding to what you see in the Chrome DevTool's Memory tab. It ignores the size of native browser objects.

fuite works best when your source code is unminified. Otherwise, the class names will show as the minified versions, which can be hard to debug.

fuite may use a lot of memory itself to analyze large heap snapshot files. If you find that Node.js is running out of memory, you can run something like:

NODE_OPTIONS=--max-old-space-size=8000 fuite <url>

The above command will provide 8GB of memory to fuite. (Note that NODE_OPTIONS will not work if you run npx fuite; you have to run fuite directly, e.g. by running npm i -g fuite first.)

FAQs

The results seem wrong or inconsistent.

Try running with --iterations 13 or --iterations 17. The default of 7 iterations is decent, but it might report some false positives.

It says I'm leaking 1kB. Do I really need to fix this?

Not every memory leak is a serious problem. If you're only leaking a few kBs on every interaction, then the user will probably never notice, and you'll certainly never hit an Out Of Memory error in the browser. Your ceiling for "acceptable leaks" will differ, though, depending on your use case. E.g., if you're building for embedded devices, then you probably want to keep your memory usage much lower.

It says my page's memory grew, but it also said it didn't detect any leaks. Why?

Web pages can grow memory for lots of reasons. For instance, the browser's JavaScript engine may JIT certain functions, taking up additional memory. Or the browser may decide to use certain internal data structures to prioritize CPU over memory usage.

The web developer generally doesn't have control over such things, so fuite tries to distinguish between browser-internal memory and JavaScript objects that the page owns. fuite will only say "leak detected" if it can actually give some actionable advice to the web developer.

How do I debug leaking event listeners?

Use the --output command to output a JSON file, which will contain a list of event listeners and the line of code they were declared on. Otherwise, you can use the Chrome DevTools to analyze event listeners:

  • Open the DevTools
  • Open the Elements panel
  • Open Event Listeners
  • Alternatively, run getEventListeners(node) in the DevTools console

How do I debug leaking collections?

fuite will analyze your leaking collections and print out a stacktrace of which code caused the increase โ€“ for instance, pushing to an Array, or seting a Map. So this is the first place to look.

If you have sourcemaps, it will show the original source. Otherwise, it'll show the raw stacktrace.

Sometimes more than one thing is increasing the size, and not every increase is at fault (e.g. it deletes right after). In those cases, you should use --output and look at the JSON output to see the full list of stacktraces.

In some other cases, fuite is not able to track increases in collections. (E.g. the object disallows modifications, or the code uses Array.prototype.push.call() instead of .push()ing directly.)

In those cases, you may have to do a manual analysis. Below is how you can do that.

First, run fuite in debug mode:

NODE_OPTIONS=--inspect-brk fuite https://example.com --debug

Then open chrome:inspect in Chrome and click "Open dedicated DevTools for Node." Then, when the breakpoint is hit, open the DevTools in Chromium (the one running your website) and click the "Play" button to let the scenario keep running.

Eventually fuite will give you a breakpoint in the Chrome DevTools itself, where you have access to the leaking collection (Array, Map, etc.) and can inspect it.

It will also give you debugger breakpoints on when the collection is increasing (e.g. push, set, etc.). For plain objects, it tries to override the prototype and spy on setters to accomplish this.

Note that not every leaking collection is a serious memory leak: for instance, your router may keep some metadata about past routes in an ever-growing stack. Or your analytics library may store some timings in an array that continually grows. These are generally not a concern unless the objects are huge, or contain closures that reference lots of memory.

Why not support multiple browsers?

Currently fuite requires Chromium-specific tools such as heap snapshots, getEventListeners, queryObjects, and other things that are only available with Chromium and the Chrome DevTools Protocol (CDP). Potentially, such things could be accessible in a cross-browser way, but today it just isn't possible.

That said, if something is leaking in Chrome, it's likely leaking in Safari and Firefox too.

More Repositories

1

optimize-js

Optimize a JS file for faster parsing (UNMAINTAINED)
JavaScript
3,754
star
2

pokedex.org

Offline-capable Pokรฉdex web site (unmaintained)
JavaScript
2,269
star
3

marky

High-resolution JavaScript timer based on performance.mark/measure (491 bytes min+gz)
JavaScript
1,097
star
4

pinafore

Alternative web client for Mastodon (UNMAINTAINED)
JavaScript
1,021
star
5

emoji-picker-element

A lightweight emoji picker for the modern web
JavaScript
956
star
6

slow-deps

๐ŸŒ Measure which dependencies in a project are slowest to npm install (UNMAINTAINED)
JavaScript
513
star
7

blob-util

Cross-browser utils for working with binary Blobs
TypeScript
489
star
8

Catlog

Logcat-reading app for Android (UNMAINTAINED)
Java
471
star
9

promise-worker

Promise-based messaging for Web Workers and Service Workers
JavaScript
465
star
10

pouchdb-find

Easy-to-use query language for PouchDB. โš ๏ธ NOTICE โš ๏ธ: moved to the PouchDB repo
JavaScript
433
star
11

rollupify

Browserify transform to apply Rollup (UNMAINTAINED)
JavaScript
386
star
12

cjs-to-es6

CLI to convert CommonJS to ES6 modules (UNMAINTAINED)
JavaScript
262
star
13

pseudo-worker

A tiny and mostly spec-compliant WebWorker polyfill
JavaScript
243
star
14

cordova-plugin-sqlite-2

Native SQLite database API for Cordova/PhoneGap/Ionic, modeled after WebSQL (UNMAINTAINED)
JavaScript
169
star
15

CustomFastScrollViewDemo

Demo of an Android app using a FastScrollView with non-alphabetic overlays.
Java
130
star
16

pouchdb-electron

PouchDB for Electron (formerly Atom Shell)
126
star
17

hello-javascript

Demo of a JavaScript module that supports many publishing options
JavaScript
121
star
18

jison-debugger

UI for debugging Jison grammars (UNMAINTAINED)
JavaScript
117
star
19

pretty-s3-index-html

A prettier index.html for listing public files and folders in Amazon S3 buckets. (UNMAINTAINED)
HTML
112
star
20

node-websql

The WebSQL Database API, implemented for Node.js
JavaScript
87
star
21

SuperSaiyanScrollView

Super-fast, super-lightweight sectioned lists for Android
Java
81
star
22

state-of-binary-data-in-the-browser

The state of binary data in the browser (2015)
79
star
23

KeepScore

Score keeping app for Android (unmaintained)
Java
68
star
24

rollup-comparison

compare rollup to browserify/webpack
JavaScript
67
star
25

vdom-as-json

Convert virtual-dom objects to and from JSON (UNMAINTAINED)
JavaScript
64
star
26

database-comparison

Compare DOM-blocking in browser databases
JavaScript
58
star
27

vdom-serialized-patch

Serialize virtual-dom patches as a minimal JSON object (UNMAINTAINED)
JavaScript
56
star
28

pouchdb-phonegap-cordova

PouchDB for PhoneGap/Cordova
55
star
29

chord-magic

Musical chord parser, transposer, and disambiguator in JavaScript
JavaScript
54
star
30

html5workertest

Show which APIs are supported in Web Workers and Service Workers (UNMAINTAINED)
JavaScript
53
star
31

resources-for-mastodon-newbies

Links and tips for Mastodon newbies
53
star
32

fruitdown

Browser-based LevelDOWN adapter that works over Apple IndexedDB (UNMAINTAINED)
JavaScript
50
star
33

vuvuzela

Simple and non-recursive JSON parse/stringify library
JavaScript
45
star
34

AppTracker

Android app that logs app usage statistics in a background Service and displays app information (most frequently used, most recently used, etc.) in a widget or in the main activity.
Java
42
star
35

cost-of-small-modules

Benchmark showing the cost of various bundlers (repo locked ๐Ÿ”’)
Shell
38
star
36

pouchdb-async-storage

PouchDB adapter for AsyncStorageDOWN for use in React Native (INCOMPLETE)
JavaScript
37
star
37

ChordReaderRoot

Android app for reading and transposing downloaded guitar chord charts. (unmaintained)
Java
29
star
38

PouchDroid

PouchDB + Android = deliciously synchronous (DEPRECATED! DO NOT USE!)
JavaScript
29
star
39

async-functions-in-pouchdb

Demo of ES7 async functions in PouchDB
JavaScript
28
star
40

pouchdb-ionic

PouchDB for Ionic Framework
27
star
41

arrow-key-navigation

Add left/right focus navigation to a web app or KaiOS app
JavaScript
23
star
42

base64-encode-string

Simple module that converts a string to base64 (for educational purposes)
JavaScript
23
star
43

pouchdb-with-service-workers

Repo for brainstorming on PouchDB replication with service workers
22
star
44

tiny-queue

Simple JavaScript FIFO queue implementation to avoid having to do shift()
JavaScript
21
star
45

css-talk-2022

Talk given in October 2022 for perf.now about CSS runtime performance
JavaScript
21
star
46

pouchdb-nw

PouchDB for NW.js (aka Node-Webkit)
20
star
47

OfflineBrowser

Android app to view HTML pages offline.
Java
19
star
48

test-optimize-js

Web page to test optimize-js against any JavaScript file
JavaScript
19
star
49

serviceworker-update-demo

Effort to write a ServiceWorker demo app that properly manages updates
JavaScript
16
star
50

JapaneseNameConverterRoot

Android app to convert English names into Japanese characters.
Java
16
star
51

SimpleTalker

Simple Android app to speak some text passed in as a parameter. Intended to be used from the command line.
Java
16
star
52

pouchdb-ionic-hello-world

PouchDB Ionic "hello world" app (using Ionic v1)
JavaScript
15
star
53

pouchdb-cordova-hello-world-with-sqlite-plugin

"Hello world" Cordova app with PouchDB, using the SQLite Plugin
CSS
15
star
54

mingz

Check the browserified+min+gz size of any npm module (bash script)
14
star
55

braziljs-2016

Nolan's BrazilJS talk: "We can work it out: from Web Workers to Service Workers"
JavaScript
14
star
56

PopupDemo

Demo of a Quick Action-like popup in Android
Java
14
star
57

open-stylable

Web component where styles leak in, but they don't leak out
JavaScript
13
star
58

substring-trie

Minimalistic trie implementation for prefix searches
JavaScript
13
star
59

cordova-prepopulated-database-demo

Demo of prepopulated databases in Cordova SQLite Plugin 2
JavaScript
12
star
60

pouchdb-nw-hello-world

Demo of using PouchDB in NW.js (aka Node-WebKit)
JavaScript
12
star
61

package-json-versionify

Browserify transform to strip everything from package.json except for the "version" field.
JavaScript
12
star
62

measure-style-and-layout

Demo of measuring style and layout in a webapp
HTML
10
star
63

pouchdb-ionic-2-typescript-demo

Demo of PouchDB with Ionic 2 and TypeScript
JavaScript
10
star
64

browserify-count-modules

Count the total number of modules in a Browserify bundle.
JavaScript
10
star
65

web-starter-kit-rollupify

web starter kit, but with browserify+watchify+rollupify
HTML
10
star
66

webworker-postmessage-perf-test

Web Worker postMessage() benchmark
HTML
9
star
67

pouchdb-chrome-app

PouchDB for Chrome packaged apps
9
star
68

lodash-bundle-size-test

Test repo for experimenting with babel-plugin-lodash and lodash-webpack-plugin
JavaScript
8
star
69

emoji-mart-outside-react

Demo of how to use emoji-mart outside of React
JavaScript
8
star
70

throw-max-listeners-error

Throw an error if the max number of EventEmitter listeners is exceeded
JavaScript
7
star
71

pouchdb-getting-started-todo

Complete PouchDB "Getting Started" Todo app
CSS
7
star
72

memory-leaks-2020

Slides for a talk given at QueensJS in August 2020
HTML
7
star
73

database-comparison-worker-pouch

Fork of https://github.com/nolanlawson/database-comparison to demo worker-pouch
JavaScript
7
star
74

pouchdb-adapter-cordova-sqlite-demo

Demo of using pouchdb-adapter-cordova-sqlite in an Ionic 1 project (note: PouchDB v6, Ionic v1)
JavaScript
7
star
75

sugar-pouch

A sweeter, simpler interface for PouchDB (work in progress)
6
star
76

pouchdb-chrome-app-hello-world

"Hello world" Chrome app with PouchDB
JavaScript
6
star
77

shadow-selector-benchmark

Benchmark of shadow DOM with various CSS selectors
JavaScript
6
star
78

debug-websql

Simple script to console.log every SQL query received by Web SQL
JavaScript
6
star
79

sqlite-plugin-fork

Fork of the SQLite plugin from 2014, back when it was compatible with PouchDB
JavaScript
6
star
80

pouchdb-cordova-hello-world

"Hello world" Cordova app with PouchDB
CSS
6
star
81

fabric-calculator

Web app to calculate how much fabric you need for a sewing project
Svelte
6
star
82

RelatednessCalculator

Java library for parsing English relative names ("mom," "dad," "grandma," etc.) and calculating relatedness coefficients.
Java
6
star
83

pouchdb-perf-report-3.10

PouchDB Performance Report for v3.1.0, November 2014
5
star
84

pouchdb-prebuilt-demo

Demo of a prebult PouchDB database, used in a Cordova app.
JavaScript
5
star
85

async-functions-with-regenerator

A "hello world" for async/await with Babel and Regenerator
JavaScript
5
star
86

CatLogDonate

Java
5
star
87

webperf-2016-03

Nolan's 30-minute talk on Web Workers for the NYC Web Performance meetup in March 2016.
JavaScript
5
star
88

ultimate-crossword-app

The Ultimate Crossword - an online crossword puzzle (UNMAINTAINED)
JavaScript
5
star
89

react-wheel-jank-demo

Demo of a React app that causes janky scrolling by attaching a wheel event to the entire document
JavaScript
5
star
90

couch-community-compat-table

The state of sync pairs in the Couch community
4
star
91

offlinefirst-2016-03

Nolan's presentation for Offline First meetup, Boston March 2016
JavaScript
4
star
92

pouchdb-ionic-2-hello-world-with-sqlite

PouchDB "Hello world" app using Ionic v2, with native SQLite
TypeScript
4
star
93

async-rect

DEPRECATED - do not use
JavaScript
4
star
94

RelatednessCalculatorInterface

Grails frontend interface for the RelatednessCalculator
JavaScript
4
star
95

jnameconverter-server

JapaneseNameConverter simple RESTful web server
Java
4
star
96

cascadia-2016

Nolan's talk on Web Workers and Service Workers for Cascadia Fest 2016
JavaScript
4
star
97

starwars-data

generate data for the starwars-offline app
Python
4
star
98

pwa-deploy

Deploy a Progressive Web App to a connected Android device or emulator (EXPERIMENTAL - DON'T USE THIS)
Java
4
star
99

async-functions-with-kneden

A "hello world" for async/await with Babel and Kneden
JavaScript
3
star
100

independent-rendering-test

Independent Rendering test demo
JavaScript
3
star