ES6 Modules - Global Variables - javascript

I was trying to create a quick pub-sub system based out of localStorage. The process made me realize that my understanding of how ES6 modules work is incomplete.
const subscribers = {};
export default {
subscribe (key, callback) {
if (!Array.isArray(subscribers[key])) {
subscribers[key] = [callback];
} else {
subscribers[key] = [...subscribers[key], callback];
}
},
publish (key, value) {
window.localStorage[key] = value;
subscribers[key].forEach(cb => cb(value));
}
};
I imported this module whenever I wanted to subscribe/publish to a key in localStorage. The problem is that the subscribers object gets reinitialized everytime the module is imported.
Is there a way to do retain the subscribers object without polluting window? I assumed that the import statement will only execute the file once only for the first time it is imported.
Thanks.

This is an oversight at my end.
I made a typo when importing this module (capitalization), I specified the wrong filename.
There is another module with an equal name when case is ignored. This
can lead to unexpected behavior when compiling on a filesystem with
other case-semantic. Rename module if multiple modules are expected or
use equal casing if one module is expected.
This caused the module to reinitialize.
Please correct me if I am wrong, but I believe a module will be only executed once for the entire application, when imported for the first time.
Thanks.

Related

Hijacking node require even when spawning another script

I am trying to replace a specific package using
import Module from 'module';
const {require: oldRequire} = Module.prototype;
Module.prototype.require = function twilioRequire(file) {
if (file === 'the-package-of-interest) {
// return something else
}
return oldRequire.apply(this, arguments);
};
const p = require('the-package-of-interest');
// this gives me the replacement
This would work fine, but if this was placed inside a script that spawns another script, this does not work in the other script, i.e
// main.js
import Module from 'module';
import spawn from 'cross-spawn';
const {require: oldRequire} = Module.prototype;
Module.prototype.require = function twilioRequire(file) {
if (file === 'the-package-of-interest) {
// return something else
}
return oldRequire.apply(this, arguments);
};
spawn.sync('node', ['/path/to/another/script.js'], { stdio: "inherit" });
// another/script.js
require('the-package-of-interest');
// gives the original package, not the replacement
I don't suppose there is a way to spawn another script, but keep the hijacked require scope the same?
Even though mocking libraries are usually used in tests, this might be a good situation to stub another library or file you wrote with another one based on a configuration.
There are a lot of mocking libraries for node, and there's a good article which covers some of them.
The library it recommends is pretty good but you can use whatever you want to do this
Create a mock definitions file that replaces that import with the second file
First - define your mocks. You can do it in any place, this is just a setup.
Easiest way is to do this in the same file, but you can create a mock defitions file if you like
import rewiremock from 'rewiremock';
...
// by mocking the module library, you actually replace it everywhere
rewiremock('module')
.with(otherModule);
your other module need to have the same export for this to work and act with the same interface.
Replace the library based on a variable
To use, you need some sort of condition that will select if to use the original library or the second one
// This is only needed if you put the mock definitions in another file
// Just put the rest of the code under the previous snippter
// if everything is in the same file
require('<your_rewiremock_definitions_file>');
if (replaceImport) {
rewiremock.enable();
}
// run the code here
if (replaceImport) {
rewiremock.disabled();
}
Your module should be replaced everywhere by the second module.
You can use any other mocking library to do this. there's a short section at the start of the readme with different options if you want a different style or another library.
rewiremock works by replacing the node.js cache so it should change the dependency everywhere.

Change syntax for protractor step definitions from promises to async syntax

I have a protractor-cucumber framework whose step definitions are somewhat structured as per this: https://github.com/cucumber/cucumber-js/blob/master/docs/support_files/step_definitions.md
I use a return and chain the promises together. Recently, I came across a different syntax called the async function. But, when I try to convert my step definitions to async, all the help files in the framework where I use say module.exports and require() display the following warning:
[ts] File is a CommonJS module; it may be converted to an ES6 module.
When I run test cases since I can't access these helper files due to the error my tests cases fail. Like, my page object files, I am not able to access them from my tests. I think they don't get exported like they used to.
Could someone please advice me as to how I can change my test cases to async syntax without breaking them? How do I resolve the above issue without disrupting my tests in a major way.
Adding code
Here is a step from my step definition before the change
let { Given, Then, When } = require('cucumber');
Given(/^I am on the "([^"]*)" page$/, function (home) {
home = this.url.FDI_HOME;
return browser.get(home);
});
Here is a step definition, after I change it to an async function
let { Given, Then, When } = require('cucumber');
Given(/^I am on the "([^"]*)" page$/, async function (home) {
home = this.url.HOME
await browser.get(home);
});
And I will change my other steps in similar fashion. Problem arises when I try to run the above step it fails saying that it is not able to access this.url.HOME. I have another file to supply URLs called the urls.js looks something like this
let targetStore = browser.params.store || 'bestbuy';
let FDI_HOST = browser.params.fdi;
module.exports = {
HOME Page: 'https://homepage.com',
Shop_Page: 'https://shop.com',
storeLink: `http://www.${targetStore}.com`,
};
I see three dots under the word "module.exports" in VS code and when I hover over it, it displays an error saying: [ts] File is a CommonJS module; it may be converted to an ES6 module.
I have tried to find a resolution to this but not been able to successfully make it. if I use the syntax as "async()=>{}" the test cases fails but when I use "async function(){}" then a few of the steps pass but not the other.
These are suggestions/hints. They visually indicate that vscode can perform an action to possibly refactor/improve your code, but they are not treated as errors.
You can disable them by adding "javascript.suggestionActions.enabled": false to your user/workspace settings.
Source: https://github.com/Microsoft/vscode/issues/47299

Webpack extracts function, from module, that depends on outer scope

I notice this bug sometimes when there is very little code inside a function of a module:
// module A
import data from "data.json";
export function getSomeData() {
return data;
}
// module B
impoort { getSomeData } from "moduleA";
alert(getSomeData());
Then the error is something along the lines of
TypeError: data_json__WEBPACK_IMPORTED_MODULE_1__ is undefined
I notice at the top of module B there are some binding exports (whatever that is)
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getSomeData", function() { return getSomeData; });
And this is making me think that whenever possible, in order to optimize things, webpack is bundling it so that only this function gets executed but it misses that it's dependent on data from the module itself. How can I prevent this from happening (other than writing better code, duh)?
I'm posting this for any lost souls in the future.
I found the reason it was that I was including module B in module A as well and this appears to be a very smart way webpack handles recursive includes (where node would return an empty object). Sadly for people without the proper knowledge like myself this could be pretty frustrating and time consuming to debug.
Hope it helps someone.

Augment types for imported module in TypeScript

I'm converting an existing codebase from js/react/jsx setup to TypeScript. Naturally, I'd like to do it file by file, and have a question about approaches for making TS compiler work with the existing js codebase.
I convert file index.js but want to leave foo.js in JavaScript for now:
// index.ts
import { fooFunction } from 'foo';
foo({ val: 1 });
// foo.js
export const fooFunction = ({ val, optionalProp }) => {
//...
}
The problem in this example that TypeScript automatically infers argument to foo and complains that Property 'optionalProp' is missing in type { val:string, optionalProp:any }
Needing to "stub" the type of fooFunction sent me looking and I found a few ways i may be able to do it:
1) Use require instead of import
// index.ts
var fooFunction: any = require('foo').fooFunction;
2) Merge declarations
?
3) Add a d.ts file with custom declarations for foo - haven't attempted but seems inconvenient
Ideally I don't have to do (1) because I want to keep using import syntax and I don't have to do (3) because that will require me to add declaration files only to remove them later when I'm ready to convert foo.
(2) sounds awesome, but I can't figure out a working solution.
// index.ts
declare module 'foo' {
function fooFunction(options: any): any
}
Doesn't work and throws Cannot redeclare block-scoped variable 'fooFunction'
How do I do that? Are there docs that have examples of what I'm trying to do and/or have more explanation about declaration merging and how to work with namespace/interface/value?
Are there better approaches for incrementally transitioning to TypeScript?
This may not work, just wanted to preface this with that - I don't know much at all about React - but I know for sure that you can use ? on a variable to make it "optional". I've used this before in functions
myFooFunction(requiredParam: typeA, optionalParam?: typeB) { ... }
Another thing you should consider is the fact that Typescript can add some type checking at compile time, so IMO you should leverage that as much as possible.
Edit: to work with the optional parameter, you could just check that it exists.
myFooFunction(requiredParam: typeA, optionalParam?: typeB) {
if (optionalParam) { // if this results to true, the param was given
//do something
}
}

Can I export/require a module in Node.js to be used without a var holding its objects?

I am creating my own error library to have a custom catalog of specific and well documented errors to return on my API. I am doing something like this:
module.exports = CError;
function CError () {
}
// CUSTOM ERROR TYPES
CError.EmptyParamError = createErrorType(...);
CError.InvalidFormatError = createErrorType(...);
A sample of how I use my custom error types right now:
CError = require('cerror');
if(!passwd)
callback(new CError.EmptyParamError(passwd, ...));
I will use this errors through my entire project and I wish to have a cleaner code like this: (without the CError reference)
if(!passwd)
callback(new EmptyParamError(passwd, ...);
Is there a way to export the module or to require it that allows me to do this?
I googled without finding any answer, I also checked all this interface design patterns for Node.js modules but no one applies.
You can set it as a global, though as always when using globals, beware of the side-effects.
EmptyParamError = createErrorType(...);
That's it. Just leave off the var keyword, and don't set it as a property.
If it's only one or two types, you can skip the CError variable like this:
var EmptyParamError = require('cerror').EmptyParamError;
if(!passwd)
callback(new EmptyParamError(passwd, ...));
If you have multiple types in a single file, there will be multiple require('cerror') statements, but I believe there's no significant performance hit there because (if I understand correctly) Node will cache it the first time.

Categories

Resources