JavaScript run eval() with imported modules - or alternative? - javascript

I am currently trying to build a "web IDE", similiar to e.g JSFiddle.
I want to execute user input (JavaScript) locally in the browser - which works fine.
For that I simply used
let userInput = `console.log("the user typed this")`
eval(userInput) // correctly executed the script from above
However, I want to access statically hosted modules as well - like myModule.js
export { test }
function test() {
console.log("I am a module")
}
and let the user use that module like that
let userInput = `
import { test } from "./myModule.js"
test()
`
eval(userInput) // throws an error
which throws an error that cannot use import outside a module
now to my question(s):
Can I somehow use import() dynamically inside the eval() statement?
Is there perhaps an alternative that allows me to execute user input inside the browser with imported modules?
Note: I am using Vue (nuxt) for an easy UI
Thanks!

Related

Using 'export' keyword solely for importing into Unit Tests

I'm using Meteor and am writing unit tests for a Collection. I've got Helper methods for the collection in addition to just regular JS functions.
I.e.
Collection.helpers({
helperFn: function () {
return 'foo';
}
});
//And in the same file
function bar() {
return "bar";
}
Then in my tests file I have something like
import { Collection } from '../collections'
//Use Factory or Stub to create test Document
//This then works just fine and I can assert, etc..
testDoc.helperFn
My question is with wanting to test just the regular 'bar' JS function. This isn't a big deal using ES6 classes because then I can just export the whole class and call any function with an instance of it. But with Meteor I'm finding the only way I can access the function is by using the 'export' keyword.
So in my Collection file
export function bar ({ return bar; });
And now in my test file I'd do something like
import { bar } from '../collection'
I'd rather not add an export statement for every time I test a new function. Is there any way around this or is it not a big deal?
I do think that the export/import is the way to go, but to answer the first part of your question: yes, you can fall back to the original scoping of meteor and put these functions in the global scope of meteor as follows:
do not put your files in the imports/ folder, but into another folder in your project, e.g., server/.
defined the functions as:
bar = function() { /* function body */ }
These variables are interpreted by meteor as being global to the project, and hence do not need to be imported before use.
That said, there was a reason meteor introduced the imports/ folder and corresponding export/import paradigm in version 1.3. It avoids polluting the global scope and makes it much easier to see where things are defined.

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

Test driven node (without testing frameworks)

I have my code:
var User = function() {
...
}
and the test code using IIFE:
(function() { // test new user
var user = new User();
if (typeof user === 'undefined') {
console.log("Error: user undefined");
}
...
}());
both in the same js file. Works great! But, as the program grows, this is becoming too refractory for me to manage, as I have a piece of test code for every piece of business logic.
I've been taught to keep all my js in the same file, (minified is good) in production, but is there a best-practical way to keep my test code in a different file during development?
I was thinking I could use a shell script to append the test code to the production code when I want to run the tests, but I'd prefer a cross-platform solution.
I don't want or need a framework solution, I want to keep it light -- does node have anything built-in for this sort of thing?
Node has two expressions for this case. First:
module.exports = name_of_module;
Which is to export module for example function, object or something similar. And the second:
var module_name = require('Path/to/module');
to import it from other file. If you want to export IIFE you must assign it to global variable and module.export name of variable.

ES6 Modules - Global Variables

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.

Categories

Resources