JavaScript on the Desktop

Fast and Slow

Felix Rieseberg

Senior Staff Engineer, Slack

www.felix.fun


CGI


Battlefield 1


Nvidia GeForce Experience


Adobe
Creative Suite


All the Electron Apps

Slack
Visual Studio Code
Skype
Xbox
WhatsApp
Figma
WordPress
Visual Studio Installer
Dropbox
Hyper
Cypress

GitHub Desktop
Atom
Discord
Postman
Docker
Microsoft Teams
OpenFin
ChartIQ
Zeplin
Zeit Now
WebTorrent


 

Performance

💾

Memory

Speed

🔋

Energy

Measure & Monitor

Modules

const isReachable = require('is-reachable')

async function isFrontmaniaReachable() {
  const reachable = await isReachable('frontmania.com')

  console.log(reachable)
}

Modules

94000

Lines of JSON

const isReachable = require('is-reachable')

async function isDotReachable() {
  const fmReachable = await isReachable('frontmania.com')

  console.log(dotReachable)
}

Modules

// Don't require the module until you need it
// require() caches results

async function isDotReachable() {
  const isReachable = require('is-reachable')
  const fmReachable = await isReachable('frontmania.com')

  console.log(fmReachable)
}
// Do you even need a module?

function isReachable() {
  return new Promise((resolve, reject) => {
    const http = require('http')

    http.get('http://frontmania.com', () => {
      resolve()
    }).on('error', (e) => {
      reject(e)
    })
  })
}
#!/usr/bin/env node

'use strict';

[...]

try {
  require(__dirname + '/../lib/v8-compile-cache.js');
} catch (err) {

}

[...]

V8 Code Caching

require('v8-compile-cache')

async function isFrontmaniaReachable() {
  // The "compiled" blobs are now cached
  // Have V8 parse the module only once
  const isReachable = require('is-reachable')
  const dotReachable = await isReachable('frontmania.com')

  console.log(dotReachable)
}

V8 Code Caching

babel-core
 

yarn


yarn (bundled)

218ms
 

153ms


228ms

185ms
 

113ms


105ms

https://github.com/zertosh/v8-compile-cache/tree/master/bench

Rendering

window.requestAnimationFrame(() => {
  // Perform work that'll result in visual changes
  
  animate();
})

Optimize visual changes

Benchmarks

// Option 1

const divs = document.querySelectorAll('.test-class')

// Option 2

const divs = document.getElementsByClassName('test-class')

// 🤔 Which one is faster? And by how much? Let's find out:
// https://jsperf.com/dotjs-perf-example

Not all code is equal

document.querySelectorAll('.test-class')

 

375,363 ops/sec

 

 

± 5.27%, 99% slower

document.getElementsByClassName('test-class')

 

29,318,183 ops/sec

 

± 0.55%

Demo

#include <nan.h>

NAN_METHOD(IsPrime) {
    if (!info[0]->IsNumber()) {
        Nan::ThrowTypeError("Argument must be a number!");
        return;
    }
    
    int number = (int) info[0]->NumberValue();
    
    if (number < 2) {
        info.GetReturnValue().Set(Nan::False());
        return;
    }
    
    for (int i = 2; i < number; i++) {
        if (number % i == 0) {
            info.GetReturnValue().Set(Nan::False());
            return;
        }
    }
    
    info.GetReturnValue().Set(Nan::True());
}

NAN_MODULE_INIT(Initialize) {
    NAN_EXPORT(target, IsPrime);
}

NODE_MODULE(addon, Initialize);

Go Native

// Brought to you by Parcel:
// Import a wasm file like it's no big deal
const { add } = await import('./add.wasm')
const result = add(2, 3)

// 🙀 Same trick, but with Rust
const { add } = await import('./add.rs')
const result = add(2, 3)
// Rust, a true teamplayer

#[no_mangle]
pub fn add(a: i32, b: i32) -> i32 {
  return a + b
}

App Lifecycle

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    // Suspend all expensive operations
    // 🔪 setInterval()
    // 🔪 Animations
    // 🔪 Not-urgent network requests
  } else {
    // Continue!
  }
})

Background

window.requestIdleCallback(() => {
  // Perform non-important work that should not
  // impact the user's experience
  
  sendPerformanceTelemetry()
}, {
  timeout: 2000
})

De-prioritize work

const { Worker } = require('worker_threads')

export function resizeImage(image, size) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(__dirname + "/worker.js", {
      workerData: { image, size }
    })

    worker.on('message', resolve);
    worker.on('error', reject);
  })
}

Multithreading

Polyfills

Thank you!

Say Hi!
@felixrieseberg or www.felix.fun

FrontMania: JavaScript on the Desktop, Fast and Slow

By Felix Rieseberg

FrontMania: JavaScript on the Desktop, Fast and Slow

  • 770