Konva - Making components for Konva in TypeScript

Konva - Making components for Konva in TypeScript

So I'm writing various library components that are targeted at Konva and I want the generated JS to be usable in plain JS without being opinionated about where Konva's JS is located. What?

There I was, nearing the end of my lib development. Inside the single lib class I had an import for the locally installed konva.

import * as Konva from "/konva"

This works because the natural home for anything you 'npm install' is the /node_modules/<lib name> folder. Which is on the standard search path for the TS compilers.

And all was well.

Until I decided that I better create a dist version of the lib code, and a demo setup so folks could quickly get it working and see what it could do. Can you see the problem yet?

The problem

...is that the transpiler-output JS code includes the import command for Konva. And that will throw an error if Konva is not available in that location. Hmmmm - most folks using vanilla JS are going to get Konva from a CDN, not download it.

Ok - could I just do that for them? No - they might have a favorite that differs from the one I choose. And CDN's come and go. And they might want to host the Konva JS locally to avoid the dependency on the CDN.

So -just jumping in the helicopter for the view from above...at dev time I need the import for Konva, but at run time I want to use the global reference to Konva that will be present courtesy of the dev who is using my lib having already brought it in via a script tag or some other way that might be created in the future. Hmmmm.

If I remove the TS import then I get all kind of syntax errors because I lose the definition of the Konva types such as Stage, Layer, Rect and Shape.

Brick wall, head, bang!

The solution?

In the end I went for a robust solution of defining TS globals. It turns out that it is a well trodden path to have to refer to some global aka Window variable or constant or object class. And TypeScript has an approach via its globals thing.

declare global {
  export var country: string;
  export namespace Konva {
      export const Stage: typeof import('konva/lib/Stage').Stage;
      export type Stage = import('konva/lib/Stage').Stage;
      export const stages: typeof import('konva/lib/Stage').stages;

      export const Layer: typeof import('konva/lib/Layer').Layer;
      export type Layer = import('konva/lib/Layer').Layer;
      export type LayerConfig = import('konva/lib/Layer').LayerConfig;

      export const Shape: typeof import('konva/lib/Shape').Shape;
      export type Shape = import('konva/lib/Shape').Shape;

      export const Image: typeof import('konva/lib/shapes/Image').Image;
      export type Image = import('konva/lib/shapes/Image').Image;

      export const Rect: typeof import('konva/lib/shapes/Rect').Rect;
      export type Rect = import('konva/lib/shapes/Rect').Rect;
      export type RectConfig = import('konva/lib/shapes/Rect').RectConfig;
  }
}

With this in place at the top of my file I can refer to Konva without importing it.

const stage = new Konva.Stage({
  container: '',
});

And when transpiled, there is no residual import statement for Konva.

Summary

I needed to refer to Konva type definitions in my TS lib code, but needed to avoid the use of import because that meant dictating to the lib user where Konva must be located. The use of the TS globals object got me off the hook.

Thanks for reading.

VW June 2023

Photo by Greg Rosenke on Unsplash