Account for multiple peer dependency versions in react-typescript library

444 Views Asked by At

I am converting my react component library react-esri-leaflet over to typescript. The library requires the user to install react-leaflet as a peerDependency, and it provides support for react-leaflet version 3 (RLV3), as well as react-leaflet version 2 (RLV2). For the default components, I can simply do

import { createLayerComponent } from 'react-leaflet'

But if the user wants to use the components that are compatible for RLV2, they can import those components separately (feel free to look at the library docs to see how). Most of the RLV2 components start with something like

import { withLeaflet, MapControl } from 'react-leaflet'

While converting over to typescript, this throws an error. While developing this library, my install of react-leaflet installs the newest version. withLeaflet and MapControl no longer exist in that version.

To solve that problem, I followed the advice in this answer to "Two versions of same npm package in Node application". This allows me to treat RLV2 as a separate package in my source code by aliasing it to 'react-leaflet-v2', and I can do

import { withLeaflet, MapControl } from 'react-leaflet-v2'

Great, now typescript is happy, and I can import the appropriate version of react-leaflet to the appropriate component and the typings are correct. However, when I compile back to js using tsc, the package name being used in my V2 components is still 'react-leaflet-v2'.

The problem is that the end-user of the library is going to be using either RLV2 or RLV3, and its going to simply be called 'react-leaflet' in their project. The compiled files of the library need to be able to find 'react-leaflet', not some alias, regardless of the version they're using.

How can I maintain type safety in development for both dependency versions, but allow my users to not worry about it, meaning just install the version of react-leaflet that they want to use, import the correct components from the library, and have the components find the 'react-leaflet' dependency in their project? Is it possible to tell tsc to rename 'react-leaflet-v2' to 'react-leaflet' when compiling? Is there another solution?

1

There are 1 best solutions below

0
On

So I figured out a way. Its not perfect, but basically:

Use Babel

Rather than transpile my ts files using tsc, I used babel, just for those files that have aliased node module imports. I found babel-plugin-transform-rename-import, which can rename imports at compile time. Adding it to a standard babel config, I have this:

{
  "presets": ["@babel/preset-env", "@babel/preset-typescript"],
  "plugins": [
    "@babel/plugin-proposal-object-rest-spread",
    "transform-react-jsx",
    "@babel/plugin-proposal-class-properties",
    [
      "transform-rename-import", // <--- solution here
      { "original": "react-leaflet-v2", "replacement": "react-leaflet" }
    ]
  ]
}

Which is a standard setup for react typescript files, with some extra bella and whistles. As you can see, it will rename any imports from "react-leaflet-v2" to "react-leaflet".

Considering my project was organized to have all these RLV2 files in their own folder, I wrote a script to use the regular tsc command to compile all the regular components, but use babel with that config to transform just the files in the v2 directory. I then used a tsc --emitDeclarationOnly true to write the .d.ts files for the v2 files.

Note this worked well because my library doesn't import any pure-typescript declarations from 'react-leaflet-v2', meaning no import { TypeOrInterfaceOrEnum } from 'react-leaflet-v2' - all imports were simply components or functions. If I had imported type information from react-leaflet-v2, there would still be import something from 'react-leaflet-v2' in my .d.ts files, and I would need to somehow transform those as well. Perhaps babel can do that too, but I don't have the need to explore it at this point.