Get file name from javascript ES6 class reference in nodejs - javascript

Is there a way to get the file name from a reference to a class? Please note this example is over simplified to illustrate what I'm trying to do (don't start suggesting logging libraries please!)
//Logger1.js
class Logger1 {
}
//MainProcess.js
class MainProcess {
startChildProcess(Logger) {
//This extension doesn't work, but looking to find something similar that does:
Logger.fileName = () => {
return __filename
}
let loggerFileName = Logger.fileName() //Returns "Main.js" not "Logger1.js", so no good...
childProcess.fork(processFileName, [loggerFileName] )
}
}
//In a child process spawned by main:
loggerPath = process.argv[2]
let Logger = require(loggerPath)[path.basename(loggerPath).replace(".js","")]
let logger = new Logger()
I can obviously add a property with a string value, or a method to return __filename the Logger1 class, but I'd rather avoid it. Is there a way to do this from inside the MainProcess class, keeping Logger1 and any other external code clean?
The reason I don't pass the instance of the logger, is the main process then creates child processes, and these child processes instantiate their own loggers. And there is no way to pass object references down to child processes as far as I'm aware.

Related

How to evaluate classes from files applied globally using Eval - node.js

Parent script including childscript
const fs = require('fs');
function include(f) {
eval.apply(global,[fs.readFileSync(f).toString()])
}
include(__dirname+'/fileToRead.js');
hi();
let childVar = new child();
------------------------- childScript ------------------------------
function hi() {
console.log("hi");
}
class baseObj {
constructor(){
console.log("hello")
}
}
class child extends baseObj{
constructor(){
super();
}
}
----------------------------result-----------------------------------
// the hi function executes
hi
// the instance does not
let childVar = new child();
^
ReferenceError: child is not defined
---------------------Conclusive question---------------------
how do would I get the eval method to globalize the child class as well as the baseObj class using my include method provided.
---------------------------------disclaimer------------------
Disclaimer : I know modules exist and I think they are great , but when you are doing something so large server side its a huge pain to keep track of exports and imports.
Especially since cyclic references exist. Not to mention every single time you create a file you need to add whatever functionality to it with require statements and find the path to those files and then export whatever functionality you are creating in that file to go to other files .
I wish to avoid modules using this method for the most part and make it similar to dealing with client side import statements.
Thanks for the help and have a nice day guys ! :)
Your problem boils down to this:
eval('function foo() {}')
console.log(foo) // ok
eval('class bar {}')
console.log(bar) // nope
This is because
Bindings introduced by let, const, or class declarations are always instantiated in a new LexicalEnvironment. (https://tc39.es/ecma262/#sec-performeval, NOTE)
If you really want to replace the stock require, for whatever reason, try using the VM API (this is what require uses under the hood).
If you really-really need this, this is an option, as class (unlike var) does not create global bindings:
var baseObj = class {
constructor() {
console.log("hello")
}
}
var child = class extends baseObj {
constructor() {
super();
}
}

How to set environment variables like test environment for playwright-cucumber js

I've been using Playwright with Cucumber for e2e-automation of a react web application. This repo has been my starting point and it's been working out pretty good.
However, I'm looking for pointers on how to run these tests on different test environments - like development or QA, so that the target urls and other params vary as per the environment passed. For eg -
if (env == dev){
baseurl = dev_url
}
else{
baseurl = qa_url
}
The Cucumber documentation mentions the World parameter - an this issue looks like a similar issue, however I'm skeptical of passing a different JSON for this task.
Can this be achieved only at a Cucumber level or is there a Playwright or Node way of doing this?
As you are already using cucumber, define your world file like this:
First you can segregate your properties files into: properties-dev.json and properties-qa.json. Below code reads properties file based on env we are passing in defaultOptions object and stores entire properties file data into 'this'. Use 'this' in your hooks file and call this.keyNameForUrl to get url specific to environment.
Note: 'this' can be accessible only in world and hooks files (refer // https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/world.md). If you need this data in other files, create a separate class and declare all public static varibles in it. In Hooks BeforeAll function, reassign values from 'this' to the static variables created in the class.
import { setWorldConstructor } from 'cucumber';
const fs = require('fs');
const defaultOptions = {
env: 'qa'
};
function processOptions(options) {
let envFile = 'properties-' + options.env + '.json';
let environment = JSON.parse(fs.readFileSync(envFile));
return Object.assign(options, environment);
}
function World(input) {
this.World = input;
});
Object.assign(this, processOptions(Object.assign(defaultOptions, options)), options);
}
setWorldConstructor(World);
// https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/world.md

Call functions of a class instance from other file in Node

I have a bot.js class in which I made the function getUserInventory. I required bot.js in my main file app.js and created a new instance.
bot.js
class SteamBot {
constructor(logOnOptions){
[...]
this.logOn(logOnOptions);
}
logOn(logOnOptions){
[...]
}
getUserInventory(sid, gameid, contextid, onlyTradeable, callback){
[...]
}
}
module.exports = SteamBot;
app.js
const SteamBot = require('./bots/bot.js');
const bot = new SteamBot({
'logOnOptions'
});
Now I can call the function getUserInventory in my app.js file by just typing bot.getUserInventory(...).
But what if I want to call the function in another file? Do I have to just type the same code that I wrote in app.js? Or would that cause problems?
I am a beginner.
When you are calling new SteamBot(); you are creating a new instance of a SteamBot, it depends how you intend to use your SteamBot, do you need multiple steambots or just one ? If you only need one instance in your app,I would advise you to do something like that :
file /steambot.class.js
// this file contains your class
class SteamBot {
...
}
module.exports = SteamBot;
file /steambot.js
const SteamBotClass = require('./steambot.class');
let SteamBot;
module.exports = function(...args) {
SteamBot = SteamBot || new SteamBotClass(...args);
return SteamBot;
};
/app.js
const SteamBot = require('./steambot')({
'logOnOptions'
});
And if you want to use in a third file the same instance:
const SteamBot = require('./steambot')();
with this structure, in your app, you would be able to create a new class if you need it one day, with just the .class.js file, but if you need to keep one instance of SteamBot all along the app, just require steambot.js.
it gives you a singleton pattern based on file, a bit different from the answer from Grégory which I also like.
Hope this helps :)
What you are doing in app.js is instantiate a new object of the class SteamBot.
Then you are calling the method getUserInventory of that object.
Can you call the method of this object from another file?
Yes if you pass to that file the object you just created. If not you gotta instantiate a new object, which can be a mistake due to the fact that i'll be totally different from the first object you did created (an other implementation of the class that's going to have it's own attributes).
To pass through the object instantiation you have two things you can do :
Use static methods. A static method do not require an instantiation.
example:
SteamBot.getUserInventory(...)
class SteamBot {
static getUserInventory(...) {
...
}
}
Use singleton pattern, that will allow the creation of only one instance of a class.
example:
let instance = null;
class SteamBot {
constructor() {
if (instance) return instance;
instance = this;
return instance;
}
static getInstance() {
return instance || new SteamBot();
}
}
It doesn't mater how you name your bot variable. If you name it bot you will call bot.getUserInventory(...), if anotherBot then anotherBot.getUserInventory(...) if foo then foo.getUserInventory(...) and so on

Pass TypeScript object to Window in Electron app

If I have a simple typescript class that just prints to the screen, like below, how can I access it on the front end in a simpler way?
speak.ts
export class Speak {
public write() {
console.log("Hello");
}
}
I know you are able to use
index.html
<script>
var x = require('./speak');
x.Speak.prototype.write(); // Prints "Hello"
</script>
The require statement has to assign to a variable for me to access this class. I'm not able to access it using require('./speak'); on its own, trying to bring it into global scope.
Having to preface every command with "x.Speak.prototype" is a bit verbose, and could easily become much longer when multiple classes and interfaces are introduced.
I feel like I'm not doing this the right way. How can I bring data/functions over from TypeScript classes to operate on the front end?
UPDATE
When I try something like below in my index.html file
<script>
var speak = new Speak();
speak.write("some other stuff");
</script>
I get an error: Uncaught ReferenceError: Speak is not defined
There are two things involved.
ES6 -> CommonJS interop
class syntax
For the first point, you are declaring an ES6 module while consuming it in commonJs syntax.
that's why you need the extra X to hold on to the module object in CJS:
var X = require('./speak');
var speak = new X.Speak();
// or accessing the `Speak` class directly:
var Speak = require('./speak').Speak;
var speak = new Speak();
If you consume the same code in ES6, it would be:
import { Speak } from './speak'
const s = new Speak();
// or
import * as X from './speak'
const s = new X.Speak();
Of course, ESM (ES6 Module system) is not available in every browser, so you need to transpile your TypeScript code down to ES5 and use some loader mechanism to load the module (like requireJS).
For the second point, you are writing a class. so you typically would create an instance of Speak and use it (following code assume you consume the code in a module, to avoid confusion with the first point):
var speak = new Speak();
speak.write();
If you don't need an instance, you can use a static method or just function:
export class Speak {
static write() { ... }
}
// usage:
Speak.write();
// function
export function write() { ... }
// usage:
write();

What is the better practice for sharing variables across Node.js modules

So I'm writing a multiplayer game with Socket.io and most of the socket calls are handled in the main file (app.js) including storing usernames and the sockets they're connected to.
But I'd like to create a separate file (game.js) that handles all the game code including socket emissions to certain rooms. However to do that I'll need access to my array with the users/sockets stored in it (which is in app.js)
So I'm wondering what the best way to share the variable would be? Should I pass the the array reference through every function that I need it in?
Or should I write a function that is called once and creates a global variable (or the scope that I need it in) with a reference to the array?
Also If I should ever need to share the same dependency across multiple files should I call require in each one of them?
About Modules and the use Global/Shared State
An interesting aspect of modules is the way they are evaluated. The module is evaluated the first time it is required and then it is cached. This means that after it has been evaluated no matter how many times we require it again, we will always get the same exported object back.
This means that, although Node provides a global object, it is probably better to use modules to store shared stated instead of putting it directly into the global object. For instance, the following module exposes the configuration of a Mongo database.
//module config.js
dbConfig = {
url:'mongodb://foo',
user: 'anakin',
password: '*******'
}
module. exports = dbConfig;
We can easily share this module with as many other modules as we want, and every one of them will get the same instance of the configuration object since the module is evaluated only once, and the exported object is cached thereon.
//foo.js
var dbConfig1 = require('./config');
var dbConfig2 = require('./config');
var assert = require('assert');
assert(dbConfig1==dbConfi2);
So, for your specific problem, the shared state that you want to share can reside in a singleton object exposed by whatever module you have. Just make sure your singleton object is the one being exposed in your module and you will always get a reference back to it every time you require it.
If by 'variable' you mean reference to the socket - you may want to consider passing a callback or module to game.js which handles the emission - but that game.js calls when necessary.
Like Edwin Dalorzo mentioned, having a separate file for all your variables seems the best.
I've had a similar problem for a few hours now because I didn't know that variables were persistent. The scenario that I had was:
I have two files cli.ts and main-lib.ts. cli.ts reads user input, and depending on the input, runs functions in main-lib.ts. While main-lib.ts is busy validating the input, cli.ts uses some global variables that main-lib.ts generates when a test passes. The only limitation is that I can't mix the main-lib.ts code with the cli.ts, I can only share the function callValidateFunction.
The issue that I had originally thought of was: if I were to make a global-vars.ts file, the variables' data will still be different for each call of require (i.e., calling setVar(...) will only change the variable value that was imported.
However, thanks to Edwin's answer, I managed to implement a bridge:
// cli.ts
import { setVar, getVar } from "./var-bridge";
import { callValidateFunction } from "./main-lib";
function run(args: string[]): void {
// ...
if (arg == "--email") {
// Set the test status.
setVar("testStatus", "pending");
// Validate the input email address.
callValidateFunction("validateEmail", nextArg());
// Get the testStatus.
const testStatus: string = getVar("testStatus");
}
// ...
}
// main-lib.ts
import { setVar, getVar } from "./var-bridge";
const funcs: {name: string, func: (arg: string) => boolean} = {};
funcs.validateEmail = function(arg: string): boolean {
let passed: boolean = false;
// ...
return passed;
};
function callValidateFunction(functionName: string, arg: string): void {
// ...
const passed = funcs[functionName](arg);
if (passed) setVar("testStatus", "passed");
}
// ...
// var-bridge.ts
const variables: {name: string, value: any} = {
"testStatus": "",
// ...
};
function setVar(varName: string, varValue: any): void {
variables[varName] = varValue;
}
function getVar(varName: string): any {
return variables[varName];
}
export { setVar, getVar };

Categories

Resources