Can I read and execute dynamic script file using webpack? - javascript

I wondering if it possible to load script dynamic by webpack.
I create a nodejs application with webpack 5 configuration and typescript. (ts-loader).
And I want the application will be able to run module-script file that I'll give by reading the config file I created.
Something like that:
app.ts:
const config = JSON.parse(fs.readFileSync('./app.config.json', 'utf-8'));
import(config.fn).then(m => { console.log({ m }); m.foo(); })
app.config.json:
{ fn: 'path/to/fn.js' }
path/to/fn.js:
exports = function foo() { console.log('in foo'); }
The problem is when I type the filename hard-coded in import it's works, but when i pass using variable it not work.
I wondering if webpack can do it? because I getting error:
uncaught (in promise) Error: Cannot find module './foo.js'
The code:
console.clear();
const foo = './foo.js';
// not works :(
import(foo).then((m) => {
console.log({ m });
});
// not works :(
// require(foo).then((m) => {
// console.log({ m });
// });
//works:
// import('./foo').then((m) => {
// console.log({ m });
// });
stackblitz

Related

custom global function not defined when testing in Jest; works fine when not testing

I have a custom, globally-scoped function in my Express app, foo. When running my Jest test scripts, this function is caught as undefined. Thus, any tests using them fail.
index.d.ts:
declare global{
function foo(): string;
}
export {};
src/Utils/index.ts:
global.foo = function foo(){
return "bar";
};
src/Modules/Example.module.ts:
export const test = () => {
// This will return bar, as expected, when developing.
// A reference error will only be thrown when running npm test.
return foo();
};
src/Modules/Example.test.ts:
import { test } from "./Example.module";
describe("modules/example", () => {
describe("test", () => {
it("returns bar", () => {
let bar = test();
expect(bar).toBe("bar");
});
});
});
Despite this not being an issue while developing, this test results in the error:
ReferenceError: foo is not defined.
export const test = () => {
return foo();
^
...
};
You can specify src/Utils/index.ts as a setup file, which Jest will load and execute before running tests. You can add it to your Jest configuration file (or create one if you don't have one):
Assuming a CJS-format Jest configuration, jest.config.js:
module.exports = {
// Your other configuration options
"setupFiles": ["<rootDir>/src/Utils/index.ts"]
};
It will look slightly different if you are using a JSON or TypeScript Jest configuration file.
However I don't recommend using global variables (even if you use them a lot). With a proper code editor setup, it is easy to import a function from another file.

Accessing non-existent property of module.exports inside circular dependency NodeJS

Im having some issues when using module.exports inside NodeJS, and I've followed multiple guides, and im almost certain Im doing it right.
I have to scripts, main.js and event.js. Im trying to share a function from main.js to event.js, but its not working. Here is the code:
Main.js
function Scan(){
if(fs.readdirSync('./events/').length === 0){
console.log(colors.yellow('Events Folder Empty, Skipping Scan'))
} else {
var events = fs.readdirSync('./events/').filter(file => file.endsWith('.json'))
for(const file of events){
let rawdata = fs.readFileSync('./events/' + file);
let cJSON = JSON.parse(rawdata);
}
events.sort()
tevent = events[0]
StartAlerter()
}
}
module.exports = { Scan };
Event.js
const main = require('../main')
main.Scan;
This returns the error:
(node:19292) Warning: Accessing non-existent property 'Scan' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
What am I doing wrong?
I discovered that the arrangement had no effect in the error.
I simply changed the way I exported the function from the Main.js
from:
module.exports = { Scan };
to:
exports.Scan = Scan
And in Event.js, I was able to access the file like this
const main = require("./Main.js");
let result = main.Scan();
This solved my problem, I hope it helps another developer 😎
Problem solved, heres what I did differently:
module.exports = { Scan };
Is declared before the Scan function is defined, like so:
module.exports = { Scan };
function Scan(){
//Code
}
Then in event.js, I wrote
const main = require('../main')
As it is now a module, and can be used with the require() function.
Then to execute the funciton in event.js, I write
main.Scan()
To execute it.
better try :
module.exports = Scan;
I am gonna answer it using a simple example, like in this case below:
File A has 3 functions to process database activity: function
addDB, updateDB, and delData;
File B has 2 functions to process User activity on smartphone:
function addHistory, and editHistory;
Function updateDB in file A is calling function editHis in file B, and function editHistory is calling function updateDB in file A. This is what we called circular-dependency. And we need to prevent it by only giving output of state from editHistory and the rest will be processed inside file A.
//ORIGINAL FUNCTIONS which caused CIRCULAR DEPENDENCY
function updateDB() {
//process update function here
//call function in fileB
const history = require("fileB.js");
await history.editHistory(data).then((output) => {
if(output["message"] === "success"){
response = {
state: 1,
message: "success",
};
}
});
return response;
}
//THIS is the WRONG ONE
function editHistory() {
//process function to edit History here
//call function in fileA
const file = require("fileA.js");
await file.updateDB(data).then((output) => { //You should not call it here
if(output["message"] === "success") {
output = {
state: 1,
message: "success",
};
}
});
return output;
}
//==================================================//
//THE FIX
function updateDB() {
//process function here
const history = require("fileB.js");
await history.editHistory(data).then((output) => {
if(output["message"] === "success"){
await updateDB(data).then((output) => {
response = {
state: 1,
message: "success",
};
});
} else {
log("Error");
}
});
return response;
}
function editHistory() {
//process function to edit History here
// No more calling to function inside the file A
output = {
state: 1,
message: "success",
};
return output;
}

Import a module from string variable

I need to import a JavaScript module from an in memory variable.
I know that this can be done using SystemJS and Webpack.
But nowhere can I find a good working example nor documentation for the same. The documentations mostly talks of dynamic import of .js files.
Basically I need to import the module like below
let moduleData = "export function doSomething(string) { return string + '-done'; };"
//Need code to import that moduleData into memory
If anyone can point to documentation that will be great
There are limitations in the import syntax that make it difficult to do if not impossible without using external libraries.
The closest I could get is by using the Dynamic Import syntax. Two examples follow: the first one is the original I wrote while the second one is an edit from a user that wanted to modernize it.
The original one:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<script>
var moduleData = "export function hello() { alert('hello'); };";
var b64moduleData = "data:text/javascript;base64," + btoa(moduleData);
</script>
<script type="module">
async function doimport() {
const module = await import(b64moduleData);
module.hello();
}
doimport();
</script>
</body>
</html>
The modernized one:
function doimport (str) {
if (globalThis.URL.createObjectURL) {
const blob = new Blob([str], { type: 'text/javascript' })
const url = URL.createObjectURL(blob)
const module = import(url)
URL.revokeObjectURL(url) // GC objectURLs
return module
}
const url = "data:text/javascript;base64," + btoa(moduleData)
return import(url)
}
var moduleData = "export function hello() { console.log('hello') }"
var blob = new Blob(["export function hello() { console.log('world') }"])
doimport(moduleData).then(mod => mod.hello())
// Works with ArrayBuffers, TypedArrays, DataViews, Blob/Files
// and strings too, that If URL.createObjectURL is supported.
doimport(blob).then(mod => mod.hello())
You will however notice that this has some limitations on the way the import code is constructed, which may not precisely match what you need.
The simplest solution is probably to send the code of the module on the server for it to generate a temporary script to be then imported using a more conventional syntax.
let moduleData = await import("data:text/javascript,export function doSomething(str) { return str + '-done'; }");
and to test it
moduleData.doSomething('test');
Use nodejs flag --experimental-modules to use import ... from ...;
node --experimental-modules index.mjs
index.mjs:
import fs from 'fs';
let fileContents = "export function doSomething(string) { return string + '-done'; };"
let tempFileName = './.__temporaryFile.mjs';
fs.writeFileSync(tempFileName, fileContents);
console.log('Temporary mjs file was created!');
import(tempFileName)
.then((loadedModule) => loadedModule.doSomething('It Works!'))
.then(console.log)
Further reading here
How It Works:
I first create the file with fs.writeFileSync
then I use import method's promise to load module and
pipe doSomething method call with "It Works!"
and then log the result of doSomething.
Credits: https://stackoverflow.com/a/45854500/3554534, https://v8.dev/features/dynamic-import, #Louis
NodeJS:
// Base64 encode is needed to handle line breaks without using ;
const { Module } = await import(`data:text/javascript;base64,${Buffer.from(`export class Module { demo() { console.log('Hello World!') } }`).toString(`base64`)}`)
let instance = new Module()
instance.demo() // Hello World!
You can create component and Module on fly. But not from string. Here is an example:
name = "Dynamic Module on Fly";
const tmpCmp = Component({
template:
'<span (click)="doSomething()" >generated on the fly: {{name}}</span>'
})(
class {
doSomething(string) {
console.log("log from function call");
return string + "-done";
}
}
);
const tmpModule = NgModule({ declarations: [tmpCmp] })(class {});
this._compiler.compileModuleAndAllComponentsAsync(tmpModule).then(factories => {
const f = factories.componentFactories[0];
const cmpRef = this.container.createComponent(f);
cmpRef.instance.name = "dynamic";
});
Here is the stackblitz
Based on Feroz Ahmed answer, here is a specific example to dynamically load a Vue 3 component from the server via socket.io.
Server-side (Node + socket.io)
socket.on('give-me-component', (data: any, callback: any) => {
const file = fs.readFileSync('path/to/js/file', 'utf-8');
const buffer = Buffer.from(file).toString('base64');
callback(`data:text/javascript;base64,${buf}`);
});
Client-side (Vue 3 + socket.io-client)
socket.emit('give-me-component', null, async (data: any) => {
// Supposes that 'component' is, for example, 'ref({})' or 'shallowRef({})'
component.value = defineAsyncComponent(async () => {
const imp = await import(data);
// ... get the named export you're interested in, or default.
return imp['TheNamedExport_or_default'];
});
});
...
<template>
<component :is="component" />
</template>
For some weird reason, the suggested way of dynamically import()-ing a "data" URL throws an error: TypeError: Invalid URL when done from an npm package code.
The same "data" URL import()s without any errors from the application code though.
Weird.
const module = await import(`data:text/javascript;base64,${Buffer.from(functionCode).toString(`base64`)}`)
<script type="module">
import { myfun } from './myModule.js';
myfun();
</script>
/* myModule.js */
export function myfun() {
console.log('this is my module function');
}

Jasmine Data Provider is not working (jasmine_data_provider_1.using is not a function)

I am trying to achieve Data Driven testing in my project by using jasmine data providers.
I have a data.ts file like below
export const hardshipTestData = {
scenarios: {
scenario1: {
isHome: 'Yes'
},
scenario2: {
isHome: 'No'
}
}
};
I am using above data in spec file
import { using } from 'jasmine-data-provider';
import { hardshipTestData } from '../../data/testdata';
using(hardshipTestData.scenarios, function (data, description) {
it('testing data providers', () => {
console.log(data.isHome);
});
});
My issue here is when I am trying to write data. intelligence is not even giving the option isHome. When I enforce it and run the test I am getting the following error
TestSuite encountered a declaration exception
configuration-parser.js:48
- TypeError: jasmine_data_provider_1.using is not a function
any help is appreciated
You need to change import type. Try to replace:
import { using } from 'jasmine-data-provider';
with:
const using = require('jasmine-data-provider');
Also, keep in mind that firstly should be describe block:
describe('example test', () => {
using(hardshipTestData.scenarios, (data) => {
it('should calc with operator -', () => {
console.log(data.isHome);
});
});
});
Adding to Oleksii answer, his answer is for typescript.
but If you want to use in plain javascript use below:
Add below in your code:
var using = require('jasmine-data-provider');
Example:
var jasminedatasetobj = require("./jasmineDataDrivenData");
var using = require('jasmine-data-provider');
using(jasminedatasetobj.datadrive, function (data, description) {
it('Open NonAngular js website Alerts', async() => {
await browser.get("https://qaclickacademy.github.io/protocommerce/");
element(by.name("name")).sendKeys(data.name);
});
});
You might need to give full path of Jasmine data provider for plain javascripts to avoid module not found error.
var jsondataobj = require('../../../../config/Jsoninput.json');//define the data source location
var using = require('C:/Users/sam/AppData/Roaming/npm/node_modules/jasmine-data-provider');
describe("Test Jasmine Data provider",function(){
you need to declare the variable for "jasmine-data-provider" , because import can use to import the properties/classes.
instead of using variable you can give any name to the varible (I tried use "post" instead of "using" and it is still working as expected)
your code should be like
import { hardshipTestData } from "../Test";
const using = require("jasmine-data-provider");
describe("Login TestCases", () => {
using(hardshipTestData.scenarios, (alldata: any, alldesc: any) => {
it("login with different credentials", async () => {
console.log(data.isHome);
})
})
})
this will resolve you problem.

Using proxyquire in a browserify factor bundle

Stuck with this one.
I am using laravel elxir with tsify to generate my js. I run the typescript through factor-bundle to split common js modules into a seperate files. I don't think though that will be a problem in this case because everything is in a spec.js
spec.ts
/// <reference path="../../../typings/index.d.ts" />
import "jasmine-jquery";
// #start widgets
import "./widgets/common/widget-factory/test";
factory-widget/index.ts
export class WidgetFactory {
.... this contains a require call to browser.service which i need to mock
}
factory-widget/test.ts
...
import {WidgetFactory} from "./index";
const proxyRequire = require("proxyquire");
it("should output the factory items", ()=> {
proxyRequire('./widgets/browser.service/index',{
"#global": true,
});
}
browser-service.ts
...
export class BrowserService implements IBrowserService{
//details
}
Getting an error Uncaught TypeError: require.resolve is not a function on line 262.
Here is the code ( yeah it's over 20,000 lines ) how else are you supposed to debug this stuff . ¯_(ツ)_/¯
I've looked at Stubbing with proxyquire. I am not holding my breath getting an answer on this one.
Edit: 06-09-2016
Proxquire is needed to overide the require call in the boot method of the WidgetFactory class
In factory-widget/index.ts:
boot(output = true):any {
let required = {};
if (this._sorted.length) {
this._sorted.forEach((key)=> {
if (output) {
console.log(`${this._path}${key}/index`);
// this is where is need to overide the call to require.
required[key] = require(`${this._path}${key}/index`);
}
});
this._sorted.forEach((key)=> {
let dependencies = {},
module = this._factory[key];
if (module.hasOwnProperty(this.dependencyKey)) {
module[this.dependencyKey].map((key)=> {
dependencies[_.camelCase(key)] = this.isService(module) ? new required[key] : key;
});
}
if (this.isTag(module)) {
if (output) {
document.addEventListener("DOMContentLoaded", ()=> {
riot.mount(key, dependencies);
});
}
//console.log(key,dependencies);
}
else {
}
})
}
}
I've added a proxyquireify example to the tsify GitHub repo. It's based on the simple example in the proxyquireify README.md.
The significant parts are the re-definition of require to call proxyquire in foo-spec.ts:
const proxyquire = require('proxyquireify')(require);
require = function (name) {
const stubs = {
'./bar': {
kinder: function () { return 'schokolade'; },
wunder: function () { return 'wirklich wunderbar'; }
}
};
return proxyquire(name, stubs);
} as NodeRequire;
and the configuration of the proxyquire plugin in build.js:
browserify()
.plugin(tsify)
.plugin(proxyquire.plugin)
.require(require.resolve('./src/foo-spec.ts'), { entry: true })
.bundle()
.pipe(process.stdout);
If you build the bundle.js and run it under Node.js, you should see that the message written to the console includes strings returned by the functions in the stubbed ./bar module.

Categories

Resources