Majid Hajian
Majid Hajian is a passionate software developer with years of developing and architecting complex web and mobile applications. He is passionate about web platform especially flutter, IoT, PWAs, and performance.
Majid Hajian
Around 90% of all internet users have mobile access
mhadaily
mhadaily
mhadaily
mhadaily
mhadaily
WE NEED TO CARE ABOUT ALL OF OUR USERS
mhadaily
Web Workers
Web Assembly
mhadaily
Worklets
import HelloWorld from "@/components/HelloWorld.component";
export default {
name: "whoAmI",
data: {
me: {
name: "Majid Hajian",
location: "Oslo, Norway",
description: ```
Passionate Software engineer,
Community Leader, Author and international Speaker
```,
main: "Web, Javascripter, Flutter/Dart, IoT",
homepage: "https://www.majidhajian.com",
socials: {
twitter: "https://www.twitter.com/mhadaily",
github: "https://www.github.com/mhadaily"
},
books: {
"Progressive Web App with Angular": {
version: "1.0.0",
publishedBy: "Apress",
foundOn: "www.pwawithangular.com",
}
},
author: {
packtpub: "PWA development, 7 hours video course",
Udemy: "PWA development, 7 hours video course"
}
founder: "Softiware As (www.Softiware.com)"
devDependencies: {
tea: "green", mac: "10.14+",
},
engines: {
FlutterVikings: "Orginizer", MobileEraConference: "Orginizer",
ngVikingsConference: "Orginizer",
MobileMeetupOslo:"Co-Orginizer",AngularOslo: "Co-Orginizer",
FlutterDartOslo: "Co-Orginizer",Framsia: "Co-Orginizer",
}};}
};
Find me on the internet by
cc: medium.com/@francesco_rizzi
mhadaily
mhadaily
1 / 60 = 0.0166666
mhadaily
16 milliseconds
iPhone X
Moto G4
Nokia 2.1
Same tasks!
16
mhadaily
mhadaily
We as developer
must handle unexpected to bring better user experience to our users in another word
we should let main thread breathe by giving it enough space and air!
mhadaily
An object that manages the execution of tasks serially or concurrently on your app's main thread or on a background thread.
let label = UILabel();
DispatchQueue.global(qos: .background).async {
// do your job here
DispatchQueue.main.async {
// update ui here
}
}
So executing a task on a background queue and updating the UI on the main queue after the task finished is a pretty easy one using Dispatch queues.
mhadaily
mhadaily
runs on single thread
Note: To do parallelism with Javascript check out my full talk on it
mhadaily
headless
main browser
mhadaily
const worker = new Worker("./worker.js");
let counter = 0;
setInterval(() => {
counter = counter + 1;
worker.postMessage({ action: "power2", payload: counter });
}, 3000);
self.onmessage = message => {
const { data } = message;
switch (data.action) {
case "power2":
const number = data.payload; const power2 = number * number;
self.postMessage({ command: "power2", payload: power2 });
break;
}};
Worker Thread
function power2(){}
const w = new Worker();
mhadaily
mhadaily
w.power2(5);
Main Thread
mhadaily
// Main
import * as Comlink from "https://unpkg.com/comlink@alpha/dist/esm/comlink.mjs";
// import * as Comlink from "../../../dist/esm/comlink.mjs";
async function init() {
const worker = new Worker("./worker.js");
const service = Comlink.wrap(worker);
const power2 = await service.power2(2);
console.log(power2);
const increment = await service.increment(2);
console.log(increment);
}
init();
// Worker
importScripts("https://unpkg.com/comlink@alpha/dist/umd/comlink.js");
// importScripts("../../../dist/umd/comlink.js");
const service = {
power2: value => value * value,
increment: (value) => value * 1
};
Comlink.expose(service);
// all imports
class UsersWrapper extends Component {
worker;
workerService;
constructor(props) {
super(props);
this.state = {
users: [],
};
}
componentDidMount() {
this.worker = new Worker('./worker.js');
this.workerService = Comlink.wrap(worker);
// Assumption is we have a list of 3000 users!
fetchUsers().then(users => {
this.setState({ users });
})
}
async sortAscending() {
const sortedUsers = await this.workerService.sort(this.state.users);
this.setState({ users: sortedUsers });
return;
}
componentDidMount(){
this.worker.terminate();
}
render() {
return this.state.users.slice(0,20).map((user, index) => (<User key={user.id}>);
}
}
mhadaily
View
Actions
Reducer
Store
Might be even blocking
mhadaily
View
Actions
Reducer
Store
Web Worker
Main Thread
mhadaily
const initialState = { count: 0 };
const delayFunction = () => {
console.log('Start to delay...');
const seconds = 3;
const start = new Date().getTime();
const delay = seconds * 1000;
while (true) {
if (new Date().getTime() - start > delay) {
break;
}
}
console.log('Finished delaying');
};
export default (state = initialState, action) => {
switch (action.type) {
case 'increment':
delayFunction();
return { ...state, count: state.count + 1 };
case 'decrement':
delayFunction();
return { ...state, count: state.count - 1 };
default:
return state;
}
};
import { expose } from 'comlink';
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
expose(store);
const withOutWebWorker = async () => {
const store = createStore(reducer);
ReactDOM.render(<App store={store} />, document.getElementById('app'));
};
const withWebWorker = async () => {
const worker = new Worker('./store.worker.js');
const remoteStore = wrap(worker);
const store = await remoteStoreWrapper(remoteStore);
ReactDOM.render(<App store={store} />, document.getElementById('app2'));
};
Does it even faster?
UI Thread
mhadaily
UI Thread
Worker Thread
RELIABLE
Sometimes it better keep the user waiting a bit longer instead of making unresponsive UI or block ui
mhadaily
mhadaily
mhadaily
AssemblyScript compiles a strict subset of TypeScript to WebAssembly
npm init
npm install assemblyscript/assemblyscript
npx asinit .
/*
./assembly
Directory holding the AssemblyScript sources being compiled to WebAssembly.
./assembly/tsconfig.json
TypeScript configuration inheriting recommended AssemblyScript settings.
./assembly/index.ts
Exemplary entry file being compiled to WebAssembly to get you started.
./build
Build artifact directory where compiled WebAssembly files are stored.
./build/.gitignore
Git configuration that excludes compiled binaries from source control.
./index.js
Main file loading the WebAssembly module and exporting its exports.
./package.json
Package info containing the necessary commands to compile to WebAssembly.
*/
mhadaily
// no optimization, no memoization
export function fibonacci(num: i32): i32 {
if (num <= 1) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
}
/** Creates a new array and returns it to JavaScript. */
export function createArray(length: i32): Int32Array {
return new Int32Array(length)
}
/** Randomizes the specified array's values. */
export function randomizeArray(arr: Int32Array): void {
for (let i = 0, k = arr.length; i < k; ++i) {
let value = i32((Math.random() * 2.0 - 1.0) * i32.MAX_VALUE)
unchecked(arr[i] = value)
}
}
/** Computes the sum of an array's values and returns the sum to JavaScript. */
export function sumArray(arr: Int32Array): i32 {
let total = 0
for (let i = 0, k = arr.length; i < k; ++i) {
total += unchecked(arr[i])
}
return total
}
// We'll need the unique Int32Array id when allocating one in JavaScript
export const Int32Array_ID = idof<Int32Array>()
// no optimization, no memoization
export function fibonacci(num: i32): i32 {
if (num <= 1) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
}
/** Creates a new array and returns it to JavaScript. */
export function createArray(length: i32): Int32Array {
return new Int32Array(length)
}
/** Randomizes the specified array's values. */
export function randomizeArray(arr: Int32Array): void {
for (let i = 0, k = arr.length; i < k; ++i) {
let value = i32((Math.random() * 2.0 - 1.0) * i32.MAX_VALUE)
unchecked(arr[i] = value)
}
}
/** Computes the sum of an array's values and returns the sum to JavaScript. */
export function sumArray(arr: Int32Array): i32 {
let total = 0
for (let i = 0, k = arr.length; i < k; ++i) {
total += unchecked(arr[i])
}
return total
}
// We'll need the unique Int32Array id when allocating one in JavaScript
export const Int32Array_ID = idof<Int32Array>()
var int8: i8 = <i8>0; // 8-bit signed integer [-128 to 127]
var uint8: u8 = <u8>0; // 8-bit unsigned integer [0 to 255]
var int16: i16 = <i16>0; // 16-bit signed integer [-32,768 to 32,767]
var uint16: u16 = <u16>0; // 16-bit unsigned integer [0 to 65,535]
var int32: i32 = <i32>0; // 32-bit signed integer [-2,147,483,648 to 2,147,483,647]
var uint32: u32 = <u32>0; // 32-bit unsigned integer [0 to 4,294,967,295]
var int64: i64 = <i64>; // 64-bit signed integer [-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807]
var uint64: i64 = <u64>0; // 64-bit unsigned integer [0 to 18,446,744,073,709,551,615]
var float32: f32 = <f32>0.0; // 32-bit float [32 bit float range]
var float64: f64 = <f64>0.0; // 64-bit float [64 bit float range]
var pointer: usize = <usize>0; // a 32/64-bit pointer to a location in memory
npm run asbuild
/**
Created: ./build/optimized.wasm
Created: ./build/optimized.wasm.map
Created: ./build/optimized.wat
Created: ./build/untouched.wasm
Created: ./build/untouched.wasm.map
Created: ./build/untouched.wat
* /
// untouched.wasm is good for debugging
// optimized.wasm can be used in production
// wat is the text representation of the genrated .wasm
mhadaily
// all other imports
import { instantiateStreaming, ASUtil } from "assemblyscript/lib/loader";
// no optimization, no memoization
function fibonacci(num) {
if (num <= 1) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
}
class FibWrapper extends Component {
imports = {};
wasmService;
async componentDidMount() {
this.wasmService = await instantiateStreaming(fetch('/optimized.wasm'), this.imports);
}
async factorialwasm() {
const result = await this.wasmService.fibonacci(9999);
this.setState({ number: result });
return;
}
factorialJS() {
const result = fibonacci(9999);
this.setState({ number: result });
return;
}
render() {
return <Fib />;
}
}
mhadaily
MEASURE
mhadaily
A lightweight version of WebWorkers
Access to low-level parts of rendering pipleline
Run Javascript and WebAssembly
graphic rendering or audio processing
High performance
mhadaily
Paint
Animation
Layout
Audio
mhadaily
(also known as “CSS Custom Paint” or “Houdini’s paint worklet”)
mhadaily
// checkerboard.js
class CheckerboardPainter {
paint(ctx, geom, properties) {
// Use `ctx` as if it was a normal canvas
const colors = ['black', 'white'];
const size = 32;
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
const color = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.fillStyle = color;
ctx.rect(x * size, y * size, size, size);
ctx.fill();
}
}
}
}
// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);
mhadaily
<!-- index.html -->
<!doctype html>
<style>
textarea {
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>
<script>
if (typeof CSS === 'undefined' || !('paintWorklet' in CSS)) {
return;
} else {
CSS.paintWorklet.addModule('checkerboard.js');
}
</script>
mhadaily
mhadaily
mhadaily
source code
https://github.com/mhadaily/off-main-thread-examples
Let's embrace the power of workers
let's make our web app more reliable & usable
Majid Hajian
mhadaily
Slides and link to source code
bit.ly/let-the-main-thread-breathe
majid[at]softiware[dot]com
By Majid Hajian
The main thread, on the web, has a lot of responsibilities. At the same time, web apps are getting more sophisticated every day. Therefore, the main thread gets too busy that will disappoint our user by showing janky frames! The off-main-thread architecture ensures apps run smoothly on every device for everyone. In this talk, we will go through the possibilities in browsers such as WebWorker, Worklet, and WebAssembly by introducing practical tools that allow us to boost our user experiences.
Majid Hajian is a passionate software developer with years of developing and architecting complex web and mobile applications. He is passionate about web platform especially flutter, IoT, PWAs, and performance.