jest test with xmljs GLOBAL not defined - node.js

currently I am writing an App using
NodeJS v13.12.0
Jest 25.4.0
xmljs 0.3.2
typescript 3.8.3
ts-jest 25.4.0
This App should mimic a CalDAV Server. For this reason, I rely on the module xmljs, which is (after my research) the only module giving me a direct path method for finding properties in the XML.
In the node Container, the App runs fine without any errors. But When I start a test with Jest, the test fails with the error
ReferenceError: GLOBAL is not defined
at node_modules/xmljs/core.js:46:2
at Object.<anonymous> (node_modules/xmljs/core.js:176:3)
at node_modules/xmljs/XmlParser.js:3:11
at Object.<anonymous> (node_modules/xmljs/XmlParser.js:204:3)
I now know, that this error originates from the xmljs module trying to set the GLOBAL variable, which in NodeJS resolved to global. But this does not happen in jest.
My code works like following:
import XmlParser = require("xmljs");
/*
* data is the body of a PROPFIND request
*/
new XmlParser({ strict: true }).parseString(data, (err, xmlNode) => {
// omit err
xmlNode.path(["propfind", "prop"], true);
const propertiesObj: XmlNode[] = childs[0].children;
const properties: string[] = [];
Object.keys(propertiesObj).forEach(n => {
properties.push(n);
});
logger.silly("Returning properties: %O", properties);
});
Can anyone
Show me a module to use instead without requiring huge modifications of my code
Which supports a pure js implementation without using node-gyp (since it may be used on windows server)
Show me how to make a workaround in jest to spoof this GLOBAL variable being set in xmljs
I appreciate your help

You can set the value of GLOBAL in the setup of your tests. It seems that the GLOBAL variable is the deprecated form of the global in node.
In your jest.config.js file you can add a setup file through the setupFiles option:
module.exports = {
[...] // Other configurations
setupFiles: ['<rootDir>/define-deprecated-global.js']
};
And in the file define-deprecated-global you can define the GLOBAL variable as:
global.GLOBAL = global;

Related

How to import a node module inside an angular web worker?

I try to import a node module inside an Angular 8 web worker, but get an compile error 'Cannot find module'. Anyone know how to solve this?
I created a new worker inside my electron project with ng generate web-worker app, like described in the above mentioned ng documentation.
All works fine until i add some import like path or fs-extra e.g.:
/// <reference lib="webworker" />
import * as path from 'path';
addEventListener('message', ({ data }) => {
console.log(path.resolve('/'))
const response = `worker response to ${data}`;
postMessage(response);
});
This import works fine in any other ts component but inside the web worker i get a compile error with this message e.g.
Error: app/app.worker.ts:3:23 - error TS2307: Cannot find module 'path'.
How can i fix this? Maybe i need some additional parameter in the generated tsconfig.worker.json?
To reproduce the error, run:
$ git clone https://github.com/hoefling/stackoverflow-57774039
$ cd stackoverflow-57774039
$ yarn build
Or check out the project's build log on Travis.
Note:
1) I only found this as a similar problem, but the answer handles only custom modules.
2) I tested the same import with a minimal electron seed which uses web workers and it worked, but this example uses plain java script without angular.
1. TypeScript error
As you've noticed the first error is a TypeScript error. Looking at the tsconfig.worker.json I've found that it sets types to an empty array:
{
"compilerOptions": {
"types": [],
// ...
}
// ...
}
Specifying types turns off the automatic inclusion of #types packages. Which is a problem in this case because path has its type definitions in #types/node.
So let's fix that by explicitly adding node to the types array:
{
"compilerOptions": {
"types": [
"node"
],
// ...
}
// ...
}
This fixes the TypeScript error, however trying to build again we're greeted with a very similar error. This time from Webpack directly.
2. Webpack error
ERROR in ./src/app/app.worker.ts (./node_modules/worker-plugin/dist/loader.js!./src/app/app.worker.ts)
Module build failed (from ./node_modules/worker-plugin/dist/loader.js):
ModuleNotFoundError: Module not found: Error: Can't resolve 'path' in './src/app'
To figure this one out we need to dig quite a lot deeper...
Why it works everywhere else
First it's important to understand why importing path works in all the other modules. Webpack has the concept of targets (web, node, etc). Webpack uses this target to decide which default options and plugins to use.
Ordinarily the target of a Angular application using #angular-devkit/build-angular:browser would be web. However in your case, the postinstall:electron script actually patches node_modules to change that:
postinstall.js (parts omitted for brevity)
const f_angular = 'node_modules/#angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
fs.readFile(f_angular, 'utf8', function (err, data) {
var result = data.replace(/target: "electron-renderer",/g, '');
var result = result.replace(/target: "web",/g, '');
var result = result.replace(/return \{/g, 'return {target: "electron-renderer",');
fs.writeFile(f_angular, result, 'utf8');
});
The target electron-renderer is treated by Webpack similarily to node. Especially interesting for us: It adds the NodeTargetPlugin by default.
What does that plugin do, you wonder? It adds all known built in Node.js modules as externals. When building the application, Webpack will not attempt to bundle externals. Instead they are resolved using require at runtime. This is what makes importing path work, even though it's not installed as a module known to Webpack.
Why it doesn't work for the worker
The worker is compiled separately using the WorkerPlugin. In their documentation they state:
By default, WorkerPlugin doesn't run any of your configured Webpack plugins when bundling worker code - this avoids running things like html-webpack-plugin twice. For cases where it's necessary to apply a plugin to Worker code, use the plugins option.
Looking at the usage of WorkerPlugin deep within #angular-devkit we see the following:
#angular-devkit/src/angular-cli-files/models/webpack-configs/worker.js (simplified)
new WorkerPlugin({
globalObject: false,
plugins: [
getTypescriptWorkerPlugin(wco, workerTsConfigPath)
],
})
As we can see it uses the plugins option, but only for a single plugin which is responsible for the TypeScript compilation. This way the default plugins, configured by Webpack, including NodeTargetPlugin get lost and are not used for the worker.
Solution
To fix this we have to modify the Webpack config. And to do that we'll use #angular-builders/custom-webpack. Go ahead and install that package.
Next, open angular.json and update projects > angular-electron > architect > build:
"build": {
"builder": "#angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js"
}
// existing options
}
}
Repeat the same for serve.
Now, create extra-webpack.config.js in the same directory as angular.json:
const WorkerPlugin = require('worker-plugin');
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
module.exports = (config, options) => {
let workerPlugin = config.plugins.find(p => p instanceof WorkerPlugin);
if (workerPlugin) {
workerPlugin.options.plugins.push(new NodeTargetPlugin());
}
return config;
};
The file exports a function which will be called by #angular-builders/custom-webpack with the existing Webpack config object. We can then search all plugins for an instance of the WorkerPlugin and patch its options adding the NodeTargetPlugin.

TypeError: Cannot read property 'getAppPath' of undefined

We're using Electron "electron": "^5.0.2"
The code that is having the error is in the main process. It calls our backend services. I was trying to add a constant for the API path the same way we were including constants elsewhere (note: the solution here might be to use an environment variable). The issue is that electron is giving a error when it is trying to access the appPath() method. This same code works elsewhere in the app.
TypeError: Cannot read property 'getAppPath' of undefined
const {app} = require('electron');
const path = require('path');
const constants = require(path.join(app.getAppPath(), 'src/constants'));
When the browser window is created we're setting nodeIntegration to true
window = new BrowserWindow({
webPreferences: {nodeIntegration: true}
});
Try:
const app = require('electron')
app.remote.app.getPath()
If that doesn't work you should try checking if main.js is:
included in package.json
NOT being required by your frontend JS or index.html
of course, make sure electron is installed as a dev dependency
if you are running into while trying to packaging the app you should run the code that uses app in a conditional, app isn't available while packaging but will be available in the prod

How to resolve fs.existsSync is not a function

In NodeJS I have:
const fs = require('fs');
if (!fs.existsSync("some_path")) {
...
}
But I get the error:
TypeError: fs.existsSync is not a function
After doing some searching, I read that Webpack brings its own require which clobbers node.js's require, so when you require a node.js core module that webpack can't resolve to one of your files or dependencies, it throws.
(My stack trace includes __webpack_require__)
But how can I fix it?
I was facing the same Error like TypeError: fs.existsSync is not a function
So, I figured out that one extra line was added automatically which was creating this issue in import.
after removing this line from import
import { TRUE } from "node-sass";
the issue has been resolved.
I had the same error that you have. Your vscode might have added a new module to your js file. Remove that module and your app should work just fine.
You can allow webpack to use the Node's require and include fs etc. by targeting node in the config:
module.exports = {
entry: './src/main.js',
target: 'node',
output: {
path: path.join(__dirname, 'build'),
filename: 'backend.js'
}
}
As described here: https://webpack.js.org/concepts/targets/ and https://webpack.js.org/configuration/target/
I was working on an electron application, I wanted to send a message from node and get in on the react side, but I was having that same issue when requiring ipcRenderer from electron, I tried
import { ipcRenderer } from 'electron';
and
const { ipceRenderer } = require('electron') This leads to an error due to webpack transforming node's require to its own webpack_require. See more info here
What worked for me was to use
const {ipcRenderer} = window.require('electron'); on the react side/renderer side from electron
In my case, I forgot that I'd only imported the promises API, const fs = require("fs").promises, which doesn't have exist or existsSync functions in Node 17.4.0.
To use exist or existsSync, make sure you've imported fs using the sync API (const fs = require("fs")).
Note: I'm adding this answer as a possible solution for future visitors to a canonical thread for the error, not OP who appears to have required fs correctly.
It is nothing to worry about, check your code for something like import { types } from "node-sass";, it would have mistakenly and automatically imported without you know. Remove that line, and everything should work perfectly.
Even if it is not type, it is something from node-sass in your node_modules file, and you can't edit that file.
So look for and remove import { types } from "node-sass"
In my case VSCode added a arbitrary import from electron. After removing it my application worked.
import { Menu } from 'electron';
In my case, i needed to send a message from the node to react. I tried importing ipcRenderer from 'electron'; and const ipceRenderer = require('electron') This results in an error owing to webpack changing the node's require to its own webpack require. See more info here

how to use node module with es6 import syntax in typescript

I have a typescript project which has uses one of our node modules which normally runs in our front-end. We are now looking to use this module in node on our server.
The module uses es6 import syntax import { props } from 'module/file'
When I include a ref in typescript using either of the following methods
import { props } from 'module/file';
var props = require('module/file');
I get the following error from typescript
unexpected token 'import'
(function (exports, require, module, __filename, __dirname) { import
It's a big job to re-write the module, and I've tried using babel with babel-plugin-dynamic-import-node, as well as SystemJS.
The problem with these systems is that they are all asynchronous, so I can't import the module in the standard fashion, so I would need to do a whole bunch of re-write when we get to the point that I can use import natively in node.js.
I can't be the first person to have this issue, but I can't seem to find a working solution.
--------------- update with set-up -------------
In response to #DanielKhoroshko's response. The original module I am trying to import is normally packaged by webpack in order to use on the front-end. I am now trying to use this same module both server-side and in the front-end (via webpack on the front-end) without re-writing the imports to use require and without running webpack to bundle the js to use on the server.
To be clear, the original module is written in JS, our service which is trying to use this module is written in typescript and transpiled. When the typescript tries to require the old module which uses import, it is at this point that we are running into the issue.
------------------ some progress ---------------------------
I've made some progress by creating a file in my imported module which uses babel in node.js to transpile the es6 code into commonJS modules.
I've done this via
var babel = require("babel-core")
var store = babel.transformFileSync(__dirname + '/store.js', {
plugins: ["transform-es2015-modules-commonjs"]
});
module.exports = {
store: store.code
}
I can now get the store in my new node.js project. However, the submodules within the store.js file are not included in the export.
So where in my module, it says
import activities from './reducers/activities';
I now get an error
Cannot find module './reducers/activities'
How can I get babel to do a deep traversal to include the sub-directories?
unexpected token 'import' means you are running es-modules code in environment that doesn't support import/export commands. If you are writing you code in TypeScript it's important to transpile it first before building for the browser or use ts-node to run it server-side.
If you are using webpack there are loaders ts-loader and awesome-typescript-loader
What is your setup?
To describe the module you would need to create an activities.d.ts file in the same folder where the js-version (I understood it is called activities.js and containers a reducer) resides with the following (approx.):
import { Reducer } from 'redux';
export const activities: Reducer<any>;
#Daniel Khoroshko was right in many ways, I ended up finding #std/esm which lets you import es6 modules and worked find for fetching the included imports as well.
var babel = require('babel-register')({
presets: ["env"]
});
require = require('#std/esm')(module);
var store = require('ayvri-viewer/src/store');
exports.default = {
store: store
}
I had to run babel to get a consistent build from es6 to node compatible es5

mock module which does not exist?

When i run my mocha tests in my meteor app by:
node_modules/.bin/mocha --compilers js:babel-core/register //..opts
i get a problem when my module under test wants to import:
import { Meteor } from 'meteor/meteor';
So i tried to mock it with mockery:
mockery.enable();
moduleUnderTest = '../moduleUnderTest';
mockery.registerAllowable(moduleUnderTest);
meteorMock = {};
mockery.registerMock('Meteor', meteorMock);
Unfortunately the module cannot be found
Error: Cannot find module 'meteor/meteor'
So the mocking of Meteor cannot be done.
Is there a way how i can fake the location meteor/meteor?
(Alternate Solution: If i can get access to the Meteor Environment in my mocha test)
If you look at the documentation, you'll see that .registerAllowable takes a string, not a module. You also need to give the exact module name that you are mocking, and provide a fake module with the values you want.
So:
var mockery = require("mockery");
mockery.enable();
mockery.registerAllowable("./moduleUnderTest");
// We want Meteor to have the value "foo". You probably want something
// different.
var meteorMock = { Meteor: "foo" };
// We mock 'meteor/meteor' because that's what's imported.
mockery.registerMock('meteor/meteor', meteorMock);
If you think about it, what you were doing cannot work. You were requiring the module before Mockery is configured for mocking 'Meteor', so Node loads your module, and then tries to load Meteor before the mock is available, and you get a failure.
Moreover, Meteor mocks modules so when you register a mock, you have to give a module name, not the name of a variable.

Resources