Description
Providing a pleasant experience when using RSocket-js in browser environments is a goal of the work we are doing in #158, and as we approach a preview release, I would like to take a closer look at what changes might be needed, and what options we have.
As demonstrated in #212, we know that the Buffer
module, which is native to node, but unavailable in Browser environments, is something that will need to be supported in some way (as suspected) as it is required to produce the Binary data involved with sending and receiving payloads.
There are a few possible directions to go with this, in no particular order:
A) require consumers to polyfill the Buffer
module with their build tool of choice
Most frontend build tools (Webpack, etc.) support a mechanism to polyfill Node APIs with alternatives that support browser environments. For example, the webpack resolve.fallback
configuration can be used to provide an alternative version of Buffer
that has browser compact.
Learn more here.
const webpackConfig = {
...
resolve: {
...
fallback: {
buffer: require.resolve('buffer/'),
},
},
};
Pros:
- no additional changes are required to rsocket-js packages to support browser environments
- consumers may provide whichever polyfill they see fit
Cons:
- rsocket-js packages are not browser compatible without special build tool configuration
- an increased learning curve for consumers
B) update APIs to accept browser compatible Buffer
modules as a dependency
A dependency injection type pattern could be adopted by the public APIs to accept a browser compatible Buffer
implementation. This pattern would likely involve introducing a similar configuration as the wsCreator
on the WebsocketClientTransport
API. See below.
Pros:
- APIs support browser environments without special build tool configuration
- consumers may provide whichever polyfill they see fit
Cons:
- potentially significant changes needed to expose necessary configuration and dependency injection throughout various layers of the current package implementations
- some packages that currently expose primarily functional APIs could require significant alteration to support dependency injection/configuration.
@rsocket/composite-metadata
being one such package.
Example:
Changing the encodeRoutes
API could require adding an additional argument that would provide a browser-compliant Buffer
implementation.
export function encodeRoutes(...routes: string[], bufferImpl: Buffer): Buffer {
if (routes.length < 1) {
throw new Error("routes should be non empty array");
}
return bufferImpl.concat(routes.map((route) => encodeRoute(route, bufferImpl)));
}
Alternatively, @rsocket/composite-metadata
could be altered to provide a more class-based approach which could provide benefits to dependency injection patterns, but at the potential cost of losing module tree-shaking at build time, due to the loss of individually exported functions. Loss of module tree-shaking may not be a concern though due to the relatively minimal size of the composite-metadata APIs.
C) produce browser compatible build artifacts with pre-provided Buffer
polyfills
Currently, the project builds for commonjs
environments, which supports Node as well as build tooling such as Webpack quite well. We could additionally introduce build targets/configurations that would provide a browser-compatible Buffer
module within scope.
rsocket-js/tsconfig.build.json
Line 3 in 285e3b4
Pros:
- APIs support browser environments without special build tool configuration
Cons:
- consumers cannot control which
Buffer
polyfill is used - potentially difficult to control the size of the artifacts produced, which is a consideration when building for browsers
- more complex build configuration and tooling required to maintain the project
- consumers required to import specific artifacts for usage in browser environments (ex:
require('@rsocket/core/browser')
) - likely challenges with inter-package dependencies and build tooling
I would argue that this approach may be the least appealing approach from the package's maintenance perspective.