Victor Nascimento
Victor Nascimento

Reputation: 781

Tree-shakeable strategy pattern in Typescript

I have been trying to build a library that requires its consumers to use a specific strategy per target. My current architecture follows:

[Application] -> contains -> [player] -> contains -> [renderer]

Currently, Renderer is an interface which needs to be replaced for different platforms:

I have the freedom to use any bundlers - currently using Webpack for the app and Rollup for the lib - and what I could achieve follows:

My lib exports a Player interface and a createPlayer function which returns a PlayerInterface. Then, on the application side I have a Webpack alias that resolves the correct platform library based on a build input.

e.g:

import { createPlayer } from "my-lib";


const player = createPlayer()

Then we build the application with

npm run build --platform=web

to which webpack will transform the my-lib import into my-lib/platforms/web, which also contains an exported createPlayer function which uses the correct renderer.

My question is the, from the application's point of view, how can we make sure that we import the correct renderer per platform on build time while allowing tree-shaking (so only including the correct sources)? I find that using the build system to do that is quite obscure, as it doesn't leave a clear trace of what's going on.

Is there a better way of doing this?

Best,

Upvotes: 1

Views: 326

Answers (1)

mattbasta
mattbasta

Reputation: 13709

You have a couple options. I'd recommend against having a compile-time switch, since that requires you to distribute multiple copies of your library (which isn't idiomatic). However, if you really wanted to do this, I'd suggest using webpack's ProvidePlugin or resolve.alias config field to dynamically link your code to the appropriate renderer at build time.

A more pragmatic approach, in my opinion, would be to have two entry points to your application, and allow the implementor to choose which entry point to use. This is similar to how react-dom switches between browser rendering and server rendering (i.e., react-dom versus react-dom/server):

// index.js

export * from './shared';
export {default as renderer} from './renderers/web';
// mobile/index.js

export * from '../shared';
export {default as renderer} from '../renderers/mobile';

Then, someone using your library could import {renderer, foo} from 'your-lib' or import {renderer, foo} from 'your-lib/mobile'.

Both of the above approaches work at build time.

While the latter approach normally forces you to choose which version of the library to use in your code, you can use webpack's resolve.alias configuration field to force imports to your-lib be redirected to your-lib/mobile.

Upvotes: 3

Related Questions