Exported File Scope Variables of ES6 Javascript - javascript

Following up on What is file scope in javascript, from which I know that there might or might not be a so-called File Scope Variables, however, I do remember reading such term somewhere but couldn't find it any more, except the Q&A of What is file scope in javascript. Anyway,
I want to know what's exact behavior definition for such exported file scope variables.
Because I was trying to toggle my bot dynamically but wasn't able to, eliminating factors one by one, and it finally came down on me it is because of such "exported file scope variables", and me not understanding their behavior. Take a look at the following extremely simplified bot application:
VarTestFileA.js
function nextBot() {
BotC = !BotC
return BotA[BotC]
}
function logBot() {
console.log("I:", bot)
}
const BotA = {true: {"token": 2}, false: {"token":3}}
let BotC = true
var bot = BotA[BotC]
module.exports = {
bot,
nextBot,
logBot,
}
VarTestFileB.js
const bt = require('./VarTestFileA')
console.log(bt.bot)
bt.bot = bt.nextBot()
bt.logBot()
console.log("O:", bt.bot)
bt.bot = bt.nextBot()
bt.logBot()
console.log("O:", bt.bot)
bt.bot = bt.nextBot()
bt.logBot()
console.log("O:", bt.bot)
You may know (even without running it) that no matter how I did, the bt.bot cannot be toggled. Here is the output:
$ node VarTestFileB.js
{ token: 2 }
I: { token: 2 }
O: { token: 3 }
I: { token: 2 }
O: { token: 2 }
I: { token: 2 }
O: { token: 3 }
Also, I tried to toggle bot from within VarTestFileA.js, it works within it, but console.log("O:", bt.bot.token) never shows the updated value. All and all,
It all comes down to exact behavior definition of such exported file scope variables, because if putting them in the same file, it runs perfectly fine. Hence the question.

The behavior is quite straight forward, and quite interesting at the same time. The statement const bt = require('./VarTestFileA') creates an object copy of the exported object, and where we used variables (e.g bot) - values are passed instead of reference, but where we passed a Function then a reference is passed because functions are objects in JS.
From VarTestFileA.js we exported { bot, nextBot, logBot } so dt in essence is actually equal to:
dt = {
bot : bot, //copy of old bot = BotA[BotC] which equals {"token": 2}
nextBot: nextBot, //reference to nextBot() which has access to bot in file A
nextBot: logBot , //reference to logBot () which has access to bot in file A
}
Now coming to VarTestFileB.js where we print and try to understand the behavior, let's look at how each statement behaves:
First Statement:
console.log(bt.bot) will print {"token": 2} because bot was passed by value and not reference.
Second Statement:
bt.bot = bt.nextBot() this actually changes value of dt.bot and not the true bot in file A. So bt.bot will have the toggled value, but the actual bot declared in file A is still the same because it hasn't been changed. That now takes us to the third statement:
Third Statement:
bt.logBot(), this prints the initial value of bot in file A. Because it hasn't been changed.
So the real answer here is, based on this behavior we can declare that if we hold references of the variables in File A, then we can change them from another file. But can I really provide evidence for this? Well! yes, let's see how below
To test this, let's create another method in File A;
function getBot(){
return bot;
}
And then modify the nextBot as follows:
function nextBot() {
BotC = !BotC
bot = BotA[BotC]
}
and then export references to these methods;
module.exports = {
getBot,
nextBot,
logBot,
}
So we can now perform the same experiments and try to print out the old test from the question:
const bt = require('./VarTestFileA')
console.log(bt.getBot())
bt.nextBot()
bt.logBot()
console.log("O:", bt.getBot())
bt.nextBot()
bt.logBot()
console.log("O:", bt.getBot())
bt.nextBot()
bt.logBot()
console.log("O:", bt.getBot())
And the output is as follows:
{ token: 2 }
I: { token: 3 }
O: { token: 3 }
I: { token: 2 }
O: { token: 2 }
I: { token: 3 }
O: { token: 3 }
The results are interesting because now the bot from File A seems to be toggling as expected.
In Conclusion, the issue is passing properties either by reference or value. bot from your initial code is passed as by value, it's not a complex item or it's not declared as type Object but holds a simple object as value. So there's no way to pass bot by reference. I stand to be corrected but I don't think variables that holds a primitive value like a Boolean in JavaScript or simple objects as values can be passed by reference. But functions are complex types, meaning they are Objects, hence they are passed by reference.
I hope you find this in order.

Related

Discord bot Command That changes Value of a different command

I'm looking for a way to change a text string or a value in one command by typing the new value in a different command. For example I have Discord js v12 and I'm using module commands with each command being in its own .js file.
module.exports = {
name: 'calc',
cooldown: 1000,
run: async(client, message, args) => {
if (!message.member.hasPermission("ADMINISTRATOR")) return await message.delete();
await message.delete();
var multiply = args[0] * (100 - percalc) / 100;
var calculation = multiply.toFixed(2);
if(!args[0]) return await message.channel.send('Specify a Value');
await message.channel.send(changableValue);
await message.channel.send(calculation < 5 ? 5 : calculation);
}
and I have the consts in the config file like
const percalc = 50;
const changableValue = 'Text example';
Right now the command _calc {number} puts out a calculation in percentage based on the percalc const and a text that comes with it in the changableValue part.
I'd like to make a command let's say _calcset {Value} that will save the provided value and will send it in place of the changableValue const.
First of all, note that the only reason the keyword const exists is because it stands for constant and constant variables can never be changed once initialized. So make sure you change the variable declaration keyword to just var or let.
Method 1 - If you don't need data to persist
Now, if you only want the variable to be changed per session, and you're fine with it going back to what you defined it as when you shut down the bot, you can just update the variable using functions exported from the js file. But to get the dynamic variable you'll also need to use a getter function that you export as well. Example:
config.js
var changeableValue = "foo";
function getValue() {
return changeableValue;
}
function updateValue(newValue) {
changeableValue = newValue;
}
module.exports = {
getValue,
updateValue
}
command.js
const { getValue, updateValue } = require("config.js");
console.log(getValue()); // logs "foo"
updateValue("bar");
console.log(getValue()); // logs "bar"
Unfortunately, as I mentioned, the changeableValue var will be reset back to "foo" every time you shut off the bot. If that's okay with you, then the above works fine.
Method 2 - If you need data to persist through sessions
If you want to persist the changeableValue variable through sessions, then it gets a little more complicated. Your two most likely options are either to write the value to a JSON file using the fs module (so it will be saved to your disk), or save the value in some other database, like MongoDB. I would recommend using another database provider because there are more problems you can run into when writing to your own disk, for example, if you make two write requests at the same time (like if two users use the command at the same time), you can corrupt the file when the requests try to write at the same time. However setting up an external database is outside of the scope of this question, so here's how you would set up writing to a JSON file:
config.json
{
"changeableValue": "foo"
}
command.js
const fs = require("fs");
var { changeableValue } = require("config.json");
console.log(changeableValue) // logs "foo"
var updatedValueJSON = JSON.stringify({ changeableValue: "bar" }); // necessary because the JSON object must be converted to a string
fs.writeFile("config.json", updatedValueJSON, "utf8", () => {
// this is the callback function called when the file write completes
let { changeableValue } = require("config.json");
console.log(changeableValue); // logs "bar", and now if you restart the bot and import changeableValue, it will still be bar
});

Disabling console access for specific Javascript files

In my current project with lots of dependencies I need a way to disable console access for specific libraries so that those files can't use any of the console functionality.
I could of course disable console functionality by simply finding and replacing it in the library bundle, but as this project has a lot of dependencies that would make updating libraries a huge hassle.
I'm aware that I can disable console functionality by overwriting it with an empty function block:
console.log = function(){};
But that disables the console functionality for the entire project. So im looking for an implementation, or a line of code with which I can disable console functionality for a specific file or code block.
Write a white-listing "middleware" for console.log
// Preserve the old console.log
const log = console.log;
// Used a dictionary because it's faster than lists for lookups
const whiteListedFunctions = {"hello": true};
// Whitelisting "middleware". We used the function's name "funcName"
// as a criteria, but it's adaptable
const isWhitelisted = callerData => callerData.funcName in whiteListedFunctions;
// Replacing the default "console.log"
console.log = arg => {
const stack = new Error().stack.split("at")[2].trim().split(' ');
const fileParts = stack[1].substr(1, stack[1].length - 2).split(':');
const callerData = {
funcName: stack[0],
file: fileParts.slice(0, fileParts.length - 2).join(':'),
lineColNumber: fileParts.slice(fileParts.length - 2).join(':')
};
if (isWhitelisted(callerData)) { // Filtering happens here
log(arg);
}
};
// Define the calling functions
function hello() { console.log("hello"); }
function world() { console.log("world"); }
hello(); // => Prints hello
world(); // => Doesn't print anything
Method explanation
You can do this by creating a whitelist (or blacklist) that will contain your filtering criteria. For example it may contain the name of the functions that call console.log or maybe the file name, or even the line and column numbers.
After that you create your whitelisting "middleware". This will take the caller function data and decide if it can log stuff or not. This will be done based on the previously defined whitelist. You can choose your preferred criteria in this "middleware".
Then you actually replace console.log by overriding with your new logger. This logger will take as an argument the message to log (maybe multiple arguments?). In this function you also need to find the data relating to the caller function (which wanted to call console.log).
Once you have the caller data, you can then use your whitelisting middleware to decide if it can log stuff
Getting information about the caller function
This part is a little "hacky" (but it got the job done in this case). We basically create an Error and check its stack attribute like this new Error().stack. Which will give us this trace
Error
at console.log.arg [as log] (https://stacksnippets.net/js:25:7)
at hello (https://stacksnippets.net/js:41:11)
at https://stacksnippets.net/js:48:1
After processing (split, map, etc...) the trace we get the caller function data. For example here we have
The caller function's name: hello
The file name: https://stacksnippets.net/js
The line and column number: 41:11 (watch out for minifiers)
This bit was inspired by VLAZ's answer in How to disable console.log messages based on criteria from specific javascript source (method, file) or message contents, so make sure to check it out. Really good and thorough post.
Note
To make sense of the trace we can do new Error().stack.split("at")[INDEX].trim().split(' ') where INDEX is the position of the function call you want to target in the stack trace. So if you want to get a different "level" that the one used in this example, try changing INDEX
Just redefine the console to log over a condition, your condition of course will be a check over which library is accessing the function:
// Your condition, could be anything
let condition = true;
/* Redefine the console object changing only the log function with your new version and keeping all the other functionalities intact
*/
let console = (old => ({
...old,
log: text => { if (condition) old.log(text) }
}))(window.console)
// Redefine the old console
window.console = console;
console.log('hello!')
Hope it helped :)
Yes, you can disable console logs from files based on their path! Here's a solution:
// in ./loud-lib.js
module.exports = {
logsSomething: () => console.log('hello from loud-lib')
}
// in ./silent-lib.js
module.exports = {
logsSomething: () => console.log('hello from silent-lib')
}
// in ./index.js
const loud = require('./loud-lib');
const silent = require('./silent-lib');
// save console.log
const log = console.log;
// redefinition of console.log
console.log = (...params) => {
// define regexp for path of libraries that log too much
const loudLibs = [/loud-lib/];
// check if the paths logged in the stacktract match with at least one regexp
const tooLoud = !!loudLibs.find(reg => reg.test(new Error().stack));
// log only if the log is coming from a library that doesn't logs too much
if (!tooLoud) log(...params);
};
loud.logsSomething();
silent.logsSomething();
$ node ./index.js
hello from silent-lib
This is based on the fact that new Error() produces a stack trace that identifies from which file is the error coming from (recursively).
Based on this observation, you can define an array of regular expression that match the name of libraries you don't want to hear logs from. You can get really specific and creative with the re-definition of console.log, but I kept it simple.
However, be aware of this (especially when using Webpack): if you bundle all your JS assets into one single bundle.js, the stacktrace will always point to bundle.js, thus logging everything. You'll have to go further from my code, for example by using stack-source-map, but I don't have sufficient details on your project to deliver a solution. I hope the ideas above are sufficient for you.

Set global variable to be accessed from other files - javascript

I found the solution by using global.consoleLog = v => this.log(v));. Now the variable consoleLog is available anywhere.
ORIGINAL QUESTION
Currently I am participating in the battlecode challenge. My question is about Javascript though and accessing a global variable.
I have a minimal working example with two files.
// === robot.js ===
import { BCAbstractRobot } from 'battlecode';
import Test from './test.js';
class MyRobot extends BCAbstractRobot {
constructor() {
super();
this.log("asdf"); // the log function does work
// Test.setConsoleLog(this.log); // way 1
// console.log = this.log; // way 2
}
turn() {
Test.testFunction("hello");
return this.move(0, 1);
}
}
// === test.js ===
let consoleLog = undefined;
function setConsoleLog(c) {
consoleLog = c;
}
function testFunction(s) {
// consoleLog(s); // way 1
// console.log(s); // way 2
}
export default { testFunction, consoleLog, setConsoleLog };
Since battlecode gives you the log function, you cannot use console.log for security reasons. However I want to be able to log from any file. The log function is a property of the MyRobot class, but I want to be able to access the function from other files and functions without passing parameters every time.
I tried a couple ways. First I tried creating an export from a util file (test.js) which would start out as undefined and then be set by setConsoleLog. However, when I attempt to use consoleLog, it is still undefined.
I also tried overwriting the console.log reference, but this only works within one file and the reference is still to console.log in other files.
What would be the recommended way to go about creating a global reference or import to this.log such that any file could import it and access it?
The bot is run within a node vm, but with a bundler as far as I know.

How to make a globally accessible variable?

How can I make a globally accessible variable in nightwatch.js? I'm using a variable to store a customized url (dependent on which store is loaded in our online product), but I need it to be accessible across several javascript functions. It appears the value of it resets after each function ends, despite it being declared outside of the function at the head of the file.
It's been some time since you asked your question and support for what you requested might not have been (natively) available before. Now it is.
In the developer guide two methods are provided for creating global variables accessible from any given test, depending on your needs. See here for good reading.
Method 1:
For truly global globals, that is, for all tests and all environments. Define an object, or pass a file, at the "globals_path" section of your nightwatch.json file, i.e.
"globals_path": "./lib/globals.js",
You will need to export the variables, however, so brushing up on Node is a good idea. Here is a basic globals.js file example:
var userNames = {
basicAuth: 'chicken',
clientEmail: 'SaddenedSnail#domain.com',
adminEmail: 'admin#domain.com',
};
module.exports = {
userNames: userNames
}
This object/file will be used for all of your tests, no matter the environment, unless you specify a different file/object as seen in method 2 below.
To access the variables from your test suite, use the mandatory browser/client variable passed to every function (test), i.e:
'Account Log In': function accLogin(client) {
var user = client.globals.userNames.clientEmail;
client
.url(yourUrl)
.waitForElementVisible('yourUserNameField', 1000)
.setValue('yourUserNameField', user)
.end();
}
Method 2:
For environment based globals, which change depending on the environment you specify. Define an object, or pass a file, at the "globals" section of your nightwatch.json file, nested under your required environment. I.e.
"test_settings" : {
"default" : {
"launch_url" : "http://localhost",
"selenium_port" : 4444,
"selenium_host" : "localhost",
"globals": {
"myGlobal" : "some_required_global"
}
}
}
Please note that at the time of writing, there seems to be a bug in nightwatch and thus passing a file using Method 2 does not work (at least in my environment). More info about said bug can be found here.
To expand on Tricote's answer, Nightwatch has built-in support for this. See the documentation.
You can either specify it in the nightwatch.json file as "globals": {"myvar": "whatever"} or in a globals.js file that you reference within nightwatch.json with "globals": "path/to/globals.js". In the latter case, globals.js could have:
module.exports = {
myvar: 'whatever'
};
In either case, you can access the variable within your tests as Tricote mentioned:
module.exports = {
"test": function(browser) {
console.log(browser.globals.myvar); // "whatever"
}
};
I'll probably get down-voted for this, but another option that I have been using successfully to store and retrieve objects and data is to do a file write as key value pairs to an existing file.
This allows me to, at the end of a test run, see any data that was randomly created. I create this file in my first test script using all of the data I will use to create the various accounts for the test. In this way, if I see a whole lot of failures, I can take a look at the file and see what data was used, then say, log in as that user and go to that location manually.
In custom commands I have a file that exports the following function:
saveToFile : function(path, filename, data) {
this.yfs = fs;
buffer = new Buffer(data);
console.log("Note: About to update the configuration with test data" )
fs.open(path, 'w', function(err, fd) {
if (err) {
throw 'error opening file: ' + err;
}
fs.write(fd, buffer, 0, buffer.length, null, function(err) {
if (err) throw 'error writing file: ' + err;
return fs.close(fd, function() {
console.log('File write: ' + path + ' has been updated.' );
})
});
})
},
In this file, 'data' is key value pairs like "username" : "Randy8989#mailinator.com". As a result I can use that data in later scripts, if so desired.
This being true, I'll be exploring GrayedFox's answer immediately.
Not sure it's the best way, but here is how I do it : you can define a variable in the browser.globals and access it in your different tests
For instance :
module.exports = {
before: function(browser) {
console.log("Setting up...");
// initialize global variable state
browser.globals.state = {};
},
"first test": function(browser) {
var settings = browser.globals,
state = browser.globals.state;
state.my_shared_var = "something";
browser.
// ...
// use a shared variable
.setValue('input#id', state.my_shared_var)
// ...
// ...
// save something from the page in a variable
.getText("#result", function(result) {
state.my_shared_result = result.value;
})
// ...
},
"second test": function(browser) {
var settings = browser.globals,
state = browser.globals.state;
browser.
// ...
// use the variables
.url("http://example.com/" + state.my_shared_result + "/show")
.assert.containsText('body', state.my_shared_var)
// ...
}
}
An alternative of globals.json if you need read data with complex procedure, is just to create a function in the same test file.
In the following example, I needed simple values and data from csv.
So I created getData() function and I can invoke directly from inside:
let csvToJson = require('convert-csv-to-json');
function getData(){
let users = csvToJson.getJsonFromCsv("/../users.csv");
return {
"users:": users,
"wordToSearch":"JRichardsz"
}
}
module.exports = {
"login": function(browser) {
//data is loading here
var data = getData();
browser
.url('https://www.google.com')
.waitForElementVisible('input[name="q"]', 4000)
.setValue('input[name="q"]', data.wordToSearch)
.keys(browser.Keys.ENTER)
.waitForElementVisible('#result-stats', 4000)
.end();
}
};
generaly it is a bad practice, but you can assign it as field of window class.
window.someGlobalVar = 'http://example.org/'
and window object is accessible globally

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