Skip to content
Compare
Choose a tag to compare
@WebReflection WebReflection released this 25 Jul 11:42
· 50 commits to main since this release
5f91c2b

Breaking Changes

  • there is no CommonJS anymore ... this whole stack is modern Web standards based, no require possible also because top-level await is mandatory
  • both uhtml and server exports are on hold ... as I think uhtml was unnecessary and I need to better refactor the server-side story too
  • provided exports are now explicit:
    • you refer to /main suffix for things needed to bootstrap on the main thread
    • you refer to /worker suffix for things needed to bootstrap on the worker thread
  • exports provide slightly different namespaces when coincident() is invoked:
    • it was pointless to have isWindowProxy on the main thread as hard-coded () => false
    • it was pointless to have a window at all on the main thread as it was referring to the window itself
    • it was ugly and error prone to magic-guess if the self or global context was a Worker or not
    • it was ugly and less powerful to pass along already initialized Worker instances ... the Worker class now does bootstrap correctly itself while still extending the native Worker class provided by the environment
    • the Worker class also ensure that worker wasn't already bootstrapped and somehow "ruined" or polluted elsewhere before being passed to coincident(worker) utility
  • the Worker used on the main thread MUST be the one provided by coincident after invoking it. This guarantee a unique and secure crypto CHANNEL for any operation between that constructor and its instances, once created.
  • the worker bootstrap is now asynchronous and it needs to be awaited. This is a no brainer though, because ...
  • the worker is always type module now. Don't bother overriding the type with passed options, it will always be a standard module with top level await enabled by default too.
  • if no window is used, even if the import points at the coincident/window/worker, the worker can always expose utilities that can then be consumed by the main thread.

Background

As explained in my previous MR, I was not happy at all about the initial super-hacky way things landed, even if proven to be very robust, and yet very hard to maintain, debug, or improve ... I really needed to do some refactoring!

Despite the intrinsic complexity required and handled by this module though, things are now way cleaner or easier to reason about than before, also easier to document, extend, track or debug in the future ... it's a piece of cake now and I can easily eat it too 😋

Some breaking change was necessary, although the underlying functionality is still in place except it's much better than before:

  • cleaner and faster than ever
  • better tested and never ambiguous
  • SharedArrayBuffer works regardless of mini-coi or server headers (sabayon polyfills all the things, enabling optional ServiceWorker integration that mini-coi couldn't before)
  • bootstrap dance is more predictable
  • everything is based on ESM due mandatory top level await needed to make this module happen

Unfortunately though, for the time being extra exports such as uhtml or server are not in here yet, but arguably nobody cared about the uhtml one and very few understood or used the server one ... my intent is to likely ditch uhtml export but still ship the server/main and server/worker exports after also refactoring that initial PoC that worked wonders but it wasn't nearly as elegant as the current solution.

That's it, that's the TL;DR version of this major update; keep reading if interested in knowing more.

How to test v2

npm install --save coincident@next

This is currently an RC2 but I am confident things will land sooner than later as official v2.

I need to test dozen things before landing this officially but this module is at the core of both Polyscript and PyScript projects, hence things might move fast enough.

How to use v2

This is the raw coincident export:

// main thread
import coincident from 'coincident/main';
const { Worker } = coincident();
const w = new Worker('./worker.js');
w.proxy.test = () => {
  console.log('test');
};

// worker thread
import coincident from 'coincident/worker';
const { proxy } = await coincident();
proxy.test();

This is the coincident/window export:

// main thread
import coincident from 'coincident/window/main';
const { Worker } = coincident();
const w = new Worker('./worker.js');
w.proxy.test = () => {
  console.log('test');
};

// worker thread
import coincident from 'coincident/window/worker';
const { proxy, window } = await coincident();
proxy.test();
console.log(window.location.href);

The main exports has Worker, proxy, polyfill and transfer in it, where Worker is the one you should use to bootstrap a coincident Worker, proxy is where you use or expose utilities, polyfill is a boolean value that returns true if the environment has been polyfilled and transfer is the utility to transfer buffers across worlds.

The worker exports has proxy, polyfill and transfer, where proxy is where you use or expose utilities, polyfill is a boolean value that returns true if the environment has been polyfilled and transfer is the utility to transfer buffers across worlds.

When it comes to window exported reference, and isWindowProxy counter part, it means you are using the coincident/window export and you can reach any main window property or class and check for isWindowProxy too behind the scene, yet only on workers as it has no meaning in the real window related world.


Refactoring Details

  • the window proxy is now elegantly provided by js-proxy which is 100% code covered, tested against memory leaks, wrote to be as fast as possible by design
  • the "maybe available" SharedArrayBuffer issue has been fully solved via sabayon which guarantee functionality with or without special headers enabled and it allows people to provide their Service Worker as long as they add sabayon features
  • Reflect related traps a part, and secured CHANNELS created at runtime via crypto.getRandomUUID(), also increasing both security and portability of the project, all possible value types are now represented as [int32, value] key/value pairs convention, reducing the amount of memory needed to pass complex structures around and simplifying also both stringify and parse JSON like operations