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
instanceofoperator, is that the class must be exactly in the parent chain, in the sense of JavaScript object reference.Hence, if you import
Clientclass twice, they are different actual object references, andinstanceofmay give a "false" negative.This is very probably what happens here:
ssh2is bundled with your library, hence it imports its own copy of the dependency. Whereas your app importsssh2on 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 specifyingpeerDependenciesinstanceofoperator; instead, use some heuristic to determine whether the object has the shape you need; typically check for some properties and their typeClientclass 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?)