npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@jscadui/postmessage

v0.2.0

Published

postMessage utility for worker and main window

Downloads

4

Readme

postMessage utility

Allows for simpler usage of postMessage by defining a RPC protocol that can send notifications or call methods. Calling methods is handled with Promises because the postMessage is async by definition.

If you use this utility both in your main thread and in the worker you will get the most benefits.

Consider these steps depending on the complexity

  1. postMessage: if you have only few messages pushing some data, you do not even need this
  2. RPC: if you have messages, and some of them are response to a request(functionally) you should try formalizing a protocol and this RPC here can be a good starting point
  3. Proxy+methods: as soon as you are considering RPC, I would recommend going the step further and formalize the communication with documented method names and parameter types (TS or JSDoc) and use the Proxy object to have nice code hints in your IDE.

RPC protocol

The RPC protocol is inspired by JSONRPC, as I personally found it useful in multiple projects.

Request: Structure the top level object like this:

  • method - name of the method called
  • params - array of parameters
  • id - id of the request. Optional, not used for one-way notifications. Used to match response with the original request.

Response: Structure the top level object like this:

  • response - value returned by the method
  • error - {code,message,stack} if method fails
  • id - id of the request to match this response to

Proxy object and method definitions

With modern JavaScript we can go few steps further and make communication with worker be as simple as calling methods(all of them async ofc.).

If you create a class that handles incomming rpc calls, NOTICE THAT unless all of your methods are actually async, you can not just use the declaration of your class for the side that is takling to your class via postMessage. You must create an interface that has all the methods declared async.

Here is partial sample of jsdoc definitions for jscad worker

/**
@typedef InitOptions
@prop {String} baseURI - to resolve inital relative path
@prop {Array<Alias>} alias - 
@prop {Array<Object>} bundles - bundle alias {name:path} 

@typedef RunScriptOptions
@prop {string} script - script source
@prop {string} url - script url/name
@prop {string} base - base url 

@typedef ScriptResponse
@prop {Array<any>} entities  
@prop {number} mainTime  - script run time
@prop {number} convertTime  - tim converting script output to gl data

@typedef JscadWorker
@prop {String} name
@prop {(options:InitOptions)=>Promise<void>} init
@prop {(options:RunScriptOptions)=>Promise<ScriptResponse>} runScript
*/

For me, TypeScript is nicer to write declarations, but I still prefer JSDoc as it requires less tooling and also works in IDE.

You can do it either way you prefer, so here is also typescript version of the sample:

export interface InitOptions {
  /** to resolve inital relative path */
  baseURI:String,
  alias: Array<Alias>,
  /** bundle alias {name:path} */
  bundles: Array<Record<String,String>>, 
}

export interface RunScriptOptions {
  /** script source */
  script: string,
  /** script url/name */
  url: string,
  /** base url */
  base: string,
}

export interface ScriptResponse {
  entities: Array<any>,
  /** script run time */
  mainTime: number,
}

export interface JscadWorker {
  async init(options:InitOptions):void,
  async init(options:RunScriptOptions):ScriptResponse,
}

Manuall calling init worker method documented above, requires sending worker.postMessage({method:'init', params:[{bundles}]}) just to trigger the method. Then you would also need to handle response, and wrap it all into a promise

If you do not want to be fancy with typed code you can just use initMessaging and call worker methods using sendCmd.

const {sendCmd } = initMessaging(worker, handlers, { onJobCount: trackJobs })
await sendCmd('init',[{ bundles }])
const result = await sendCmd('runScript', [{ script}])
// IDE does not know type of the result

You can then import and use the definition

/** @typedef {import('@jscadui/worker').JscadWorker} JscadWorker*/

/** @type {JscadWorker} */
const workerApi = messageProxy(worker, handlers, { onJobCount: trackJobs })
await workerApi.init({ bundles })
const result = await workerApi.runScript({ script})
// result is now known to be ScriptResponse and you get autocomplete

transferable

It is simple to send transferable objects when sending a message to the worker by adding a parameter to postMessage. It is however more tricky to support transferable for return values without complicating simple use cases that do not need transferable.

If you have a method that can be called and it needs to return transferable then you must use object as a return value. When returning such object in call to withTransferable before returning the value. It will not be in the data at the receiving end, but will be taken out and passed to postMessage as the transferable parameter.

Exmaple from jscad worker

  return withTransferable({ entities, mainTime }, transferable)