Skip to content

Commit

Permalink
Merge branch 'v4'
Browse files Browse the repository at this point in the history
  • Loading branch information
surma committed May 5, 2019
2 parents 708ba84 + 9517223 commit 846e70b
Show file tree
Hide file tree
Showing 58 changed files with 2,428 additions and 2,804 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@
*.swp
node_modules
*.bak
dist/comlink.ts
dist/README.md
dist/package.json
dist
.*_cache*
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ RUN apt-get update -qqy \
ENV NVM_DIR /usr/local/nvm
RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash \
&& source $NVM_DIR/nvm.sh \
&& nvm install v8
&& nvm install v11

ENV CHROME_BIN /opt/google/chrome/chrome
ENV INSIDE_DOCKER=1
Expand Down
124 changes: 59 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
# Comlink

Comlink’s goal is to make [WebWorkers][webworker] enjoyable. Comlink removes the mental barrier of thinking about `postMessage` and hides the fact that you are working with workers.
Comlink makes [WebWorkers][webworker] enjoyable. Comlink is a **tiny library (1.1kB)**, that removes the mental barrier of thinking about `postMessage` and hides the fact that you are working with workers.

> Note: Comlink’s goal is to be a building-block for higher-level abstraction libraries. For example, take a look at [Clooney].
At a more abstract level it is an RPC implementation for `postMessage` and [ES6 Proxies][es6 proxy].

```js
// main.js
const MyClass = Comlink.proxy(new Worker("worker.js"));
// `instance` is an instance of `MyClass` that lives in the worker!
const instance = await new MyClass();
await instance.logSomething(); // logs “myValue = 42”
```

```js
// worker.js
const myValue = 42;
class MyClass {
logSomething() {
console.log(`myValue = ${myValue}`);
}
}
Comlink.expose(MyClass, self);
$ npm install --save comlink@alpha
```

![Comlink in action](https://user-images.githubusercontent.com/234957/54164510-cdab2d80-4454-11e9-92d0-7356aa6c5746.png)

## Browsers support & bundle size

![Chrome 56+](https://img.shields.io/badge/Chrome-56+-green.svg?style=flat-square)
Expand All @@ -34,82 +21,84 @@ Comlink.expose(MyClass, self);

Browsers without [ES6 Proxy] support can use the [proxy-polyfill].

**Size**: ~3.9k, ~1.6k gzip’d
**Size**: ~2.5k, ~1.2k gzip’d, ~1.1k brotli’d

## Introduction

WebWorkers are a web API that allow you to run code in a separate thread. To communicate with another thread, WebWorkers offer the `postMessage` API. You can send messages in form of [transferable] JavaScript objects using `myWorker.postMessage(someObject)`, triggering a `message` event inside the worker.

Comlink turns this messaged-based API into a something more developer-friendly: Values from one thread can be used within the other thread (and vice versa) just like local values.

Comlink can be used with anything that offers `postMessage` like windows, iframes and ServiceWorkers.
On mobile phones, and especially on low-end mobile phones, it is important to keep the main thread as idle as possible so it can respond to user interactions quickly and provide a jank-free experience. **The UI thread ought to be for UI work only**. WebWorkers are a web API that allow you to run code in a separate thread. To communicate with another thread, WebWorkers offer the `postMessage` API. You can send JavaScript objects as messages using `myWorker.postMessage(someObject)`, triggering a `message` event inside the worker.

## Download
Comlink turns this messaged-based API into a something more developer-friendly by providing an RPC implementation: Values from one thread can be used within the other thread (and vice versa) just like local values.

You can download Comlink from the [dist folder][dist]. Alternatively, you can
install it via npm
## API

```
$ npm install --save comlinkjs
```
### `Comlink.wrap(endpoint)` and `Comlink.expose(value, endpoint?)`

or use a CDN like [delivrjs]:
Comlink’s goal is to make _exposed_ values from one thread available in the other. `expose` exposes `value` on `endpoint`, where `endpoint` is a [`postMessage`-like interface][endpoint].

```
https://cdn.jsdelivr.net/npm/[email protected]/umd/comlink.js
```
`wrap` wraps the _other_ end of the message channel and returns a proxy. The proxy will have all properties and functions of the exposed value, but access and invocations are inherintly asynchronous. This means that a function that returns a number will now return _a promise_ for a number. **As a rule of thumb: If you are using the proxy, put `await` in front of it.** Exceptions will be caught and re-thrown on the other side.

## Examples
### `Comlink.transfer(value, transferables)` and `Comlink.proxy(value)`

There’s a collection of examples in the [examples directory][examples].
By default, every function parameter, return value and object property value is copied, in the sense of [structured cloning]. Structured cloning can be thought of as deep copying, but has some limitations. See [this table][structured clone table] for details.

## API
If you want a value to be transferred rather than copied — provided the value is or contains a [`Transferable`][transferable] — you can wrap the value in a `transfer()` call and provide a list of transferable values:

The Comlink module exports 3 functions:
```js
const data = new Uint8Array([1, 2, 3, 4, 5]);
await myProxy.someFunction(Comlink.transfer(data, [data.buffer]));
```

### `Comlink.proxy(endpoint)`
Lastly, you can use `Comlink.proxy(value)`. When using this Comlink will neither copy nor transfer the value, but instead send a proxy. Both threads now work on the same value. This is useful for callbacks, for example, as functions are neither structured cloneable nor transferable.

> Returns the value that is exposed on the other side of `endpoint`.
```js
myProxy.onready = Comlink.proxy(data => {
/* ... */
});
```

`proxy` creates an ES6 proxy and sends all operations performed on that proxy through `endpoint`. `endpoint` can be a `Window`, a `Worker` or a `MessagePort`.\* The other endpoint of the channel should be passed to `Comlink.expose`.
### Transfer handlers and event listeners

If you invoke function, all parameters will be structurally cloned or transferred if they are [transferable]. If you want to pass a function as a parameters (e.g. callbacks), make sure to use `proxyValue` (see below). Same applies to the return value of a function.
It is common that you want to use Comlink to add an event listener, where the event source is on another thread:

\*) Technically it can be any object with `postMessage`, `addEventListener` and
`removeEventListener`.
```js
button.addEventListener("click", myProxy.onClick.bind(myProxy));
```

### `Comlink.expose(obj, endpoint)`
While this won’t throw immediately, `onClick` will never actually be called. This is because [`Event`][event] is neither structured cloneable nor transferable. As a workaround, Comlink offers transfer handlers.

> Exposes `obj` to `endpoint`. Use `Comlink.proxy` on the other end of `endpoint`.
Each function parameter and return value is given to _all_ registered transfer handlers. If one of the event handler signals that it can process the value by returning `true` from `canHandle()`, it is now responsible for serializing the value to sturctured cloneable data and for deserializing the value. A transfer handler has be set up on _both sides_ of the message channel. Here’s an example transfer handler for events:

`expose` is the counter-part to `proxy`. It listens for RPC messages on `endpoint` and applies the operations to `obj`. Return values of functions will be structurally cloned or transfered if they are [transferable].
```js
Comlink.transferHandlers.set("EVENT", {
canHandle: obj => obj instanceof Event,
serialize: ev => {
return [{
target: {
id: ev.target.id
classList: [...ev.target.classList]
}
}, []];
},
deserialize: obj => obj,
});
```

### `Comlink.proxyValue(value)`
Note that this particular transfer handler won’t create an actual `Event`, but just an object that has the `event.target.id` and `event.target.classList` property. Often, this enough. If not, the transfer handler can be easily augmented to provide all necessary data.

> Makes sure a parameter or return value is proxied, not copied.
### `Comlink.createEndpoint`

By default, all parameters to a function that are not [transferable] are copied (structural clone):
Every proxy created by Comlink has the `[createEndpoint]` method.
Calling it will return a new `MessagePort`, that has been hooked up to the same object as the proxy that `[createEndpoint]` has been called on.

```js
// main.js
const api = Comlink.proxy(new Worker("worker.js"));
const obj = { x: 0 };
await api.setXto4(obj);
console.log(obj.x); // logs 0
```

The worker receives a copy of `obj`, so any mutation of `obj` done by the worker won’t affect the original object. If the value should _not_ be copied but instead be proxied, use `Comlink.proxyValue`:

```diff
- await api.setXto4(obj);
+ await api.setXto4(Comlink.proxyValue(obj));
const port = myProxy[Comlink.createEndpoint]();
const newProxy = Comlink.wrap(port);
```

`console.log(obj.x)` will now log 4.
## Node

Keep in mind that functions cannot be copied. Unless they are used in combination with `Comlink.proxyValue`, they will get discarded during copy.
Comlink works with Node’s [`worker_threads`][worker_threads] module. Take a look at the example in the `docs` folder.

[clooney]: https://github.com/GoogleChromeLabs/clooney
[webworker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
[umd]: https://github.com/umdjs/umd
[transferable]: https://developer.mozilla.org/en-US/docs/Web/API/Transferable
Expand All @@ -119,6 +108,11 @@ Keep in mind that functions cannot be copied. Unless they are used in combinatio
[delivrjs]: https://cdn.jsdelivr.net/
[es6 proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
[proxy-polyfill]: https://github.com/GoogleChrome/proxy-polyfill
[endpoint]: src/protocol.js
[structured cloning]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
[structured clone table]: structured-clone-table.md
[event]: https://developer.mozilla.org/en-US/docs/Web/API/Event
[worker_threads]: https://nodejs.org/api/worker_threads.html

---

Expand Down
Loading

0 comments on commit 846e70b

Please sign in to comment.