I am building a library with TS. This library uses the ssh2
library as a dependency.
I'm trying to create a function that can either accept an ssh2
configuration object or an already existing Client
instance to execute a command (this is a simplified case):
import { Client, ConnectConfig } from 'ssh2';
export function runCommand(params: Client | ConnectConfig) {
if(params instanceof Client) {
// do something
} else {
// create our own client
// do something
}
}
When I build this library and call the function like so:
const { readFileSync } = require("fs");
const { Client } = require("ssh2");
const { runCommand } = require("myLib");
const conn = new Client();
conn
.on("ready", () => {
console.log("Client :: ready");
runCommand(conn);
})
.connect({
host: "example.com",
port: 22,
username: "me",
privateKey: readFileSync(process.env.HOME + "/.ssh/id_ed25519"),
});
the instanceof
check in my function will return false
for some reason. What exactly is happening here? Is it a TS compilation issue or does Node.js see the Client
from my lib's dependency and my consumer code's dependency as two different classes?
The compiled code looks something like:
const ssh2_1 = require("ssh2");
//...
function runCommand(params) {
if (params instanceof ssh2_1.Client) {
// do something
}
else {
// create our own client
// do something
}
}
What may not be obvious from the behaviour of instanceof
operator, is that the class must be exactly in the parent chain, in the sense of JavaScript object reference.
Hence, if you import Client
class twice, they are different actual object references, and instanceof
may give a "false" negative.
This is very probably what happens here: ssh2
is bundled with your library, hence it imports its own copy of the dependency. Whereas your app imports ssh2
on its own, leading to a separate copy.
You have a few options:
externals
), let the app install a single version that both your library and app will import; typical case of specifying peerDependencies
instanceof
operator; instead, use some heuristic to determine whether the object has the shape you need; typically check for some properties and their typeClient
class in your case) from your library, and use that one in your app (const { Client, runCommand } = require("myLib");
), so that it is the exact same copy (since it is already bundled, why not re-using it instead of re-bundling it?)