Importing classes works at design time but not at runtime - node.js

I'm using Visual Studio 2013 (Update 3), Node.js Tools v1.0.20721.02 and Node.js v0.10.31
I'm trying to put each class into its own file.
At design time everything seems fine, intellisense is working and the code compiles without issues.
At runtime however, node tells me it cannot find the classes.
I've reproduced this again and again by creating a new Node console project in Visual Studio
SomeClass.ts
export class SomeClass
{
name: string;
constructor(name: string)
{
this.name = name;
}
}
app.ts
///<reference path="Scripts/typings/node/node.d.ts"/>
import some = require("SomeClass");
var instance = new some.SomeClass("Batman");
console.log(instance.name);
The generated javascript output looks like this:
SomeClass.js
var SomeClass = (function () {
function SomeClass(name) {
this.name = name;
}
return SomeClass;
})();
exports.SomeClass = SomeClass;
//# sourceMappingURL=SomeClass.js.map
app.js
///<reference path="Scripts/typings/node/node.d.ts"/>
var some = require("SomeClass");
var instance = new some.SomeClass("Batman");
console.log(instance.name);
//# sourceMappingURL=app.js.map
Runtime output
module.js:340
throw err;
Error: Cannot find module 'SomeClass'
at Function.Module._resolveFilename (module.js:338:15)
...
Visual Studio Solution structure
This is a standard solution created by the project template in Visual Studio,
shouldn't it just work out of the box?
I've seen quite a few related questions, including this one, which seem to solve the
the issues most people are having, but don't seem to solve my problem.

Try using require("./SomeClass") instead:
node.js resolves require differently when there is no path prepended (see here), so in this case you need to tell it to look in the current directory, not for a core module or inside an (inexistent) node_modules directory.
Further info on why it does not fail before runtime, given by the OP in the comments:
Apparently VS resolves everything whether you prepend './' or not and at runtime node requires './' to be prepended.

Run your program again adding the following line before your require:
console.log("cwd:%s", process.cwd());
And adjust the path you require so it starts at your current working directory (cwd).

Related

jest test with xmljs GLOBAL not defined

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;

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.

Node.js Gulp src/dest 4.0 behaviour vs Gulp 3.6

Quick Summary of my question:
Does Gulp 3.6.0 dest() handle glob-base the same way as 4.0.0?
function other() {
return src([
path.join("src/**/*"),
path.join("!src/**/*.{html,css,js,scss}")
])
.pipe(fileFilter)
.pipe(dest(dist));
}
Running Gulp 3.6.0 with the code above produced this result:
Note that the folders and files in question added to the dist folder by this code were:
-app
-assets
-config
favicon.ico
Now running the same code in 4.0.0 produces this:
I know that the glob-base is added on by default to the destination when it's piped through, however is this behaviour different to how gulp handled mirroring source to dest file directory structure in 3.6.0? The example would suggest otherwise.
If anyone could provide me with a solution for producing the same folder structure as supplied in my 3.6.0 result that would be great. I've tried gulp-flatten and gulp-rename but nothing is producing the desired result of nicely removing only the glob-base.
So I'm still not sure what the significance of upgrading to Gulp 4.0 actually was with relation to how glob-parent/glob-base is handled however I managed to get what I needed using the base option.
This option effectively nullified the additional src hard-coded path reference before /**/ in the path.
function other() {
var fileFilter = plugins.filter(function(file) {
return file.stat.isFile();
});
var appFilter = plugins.filter(function(file) {
return file.path.indexOf("\\src\\app\\") === -1;
});
return src(path.join(conf.paths.src, "/**/*"), { base: conf.paths.src })
.pipe(appFilter)
.pipe(fileFilter)
.pipe(dest(conf.paths.dist));
}

Intellij NodeJs 6.4.0 unexpected token export

I'm using Intellij Idea to create a NodeJs application in ES6.
My node.exe version is version 6.4.0
I created a simple class :
//wNodeClasses.js
'use strict';
export class wsUrl
{
constructor()
{}
}
I import the module in another file :
require('../../../Root/Libs/Waldata/wsNodeClasses');
When I start the application I always get the error :
d:\Dev\webDev\Root\Libs\Waldata\wsNodeClasses.js:11
export class wsUrl
^^^^^^
SyntaxError: Unexpected token export
at Object.exports.runInThisContext (vm.js:76:16)
I don't use any transpiler , I want to write "pure ES6 code" (I don't want to use Babel or any equivalent)
I understand that NodeJs 6.4.0 can interpret directly ES6 code
Here is my Node.Exe command line :
-max-old-space-size=8192 --expose_debug_as=v8debug
I am a newbie, I suppose I'm missing something obvious, I googled around and I didn't found an answer
I finally found the issue.
NodeJs 6.4.0 with chrome V8 doesn't support Es6 "export" keyword or syntax.
I found a work around using
//Module file
module.exports=
class wsUrl
{
}
and in the consumer :
var wsUrl = require('../../../Root/Libs/Waldata/wsNodeClasses');
...
var MyVar = wsUrl new("test");
I'm still searching to have multiple classes in a same Js file (module) and export them
Thanks for your answer.
I used a different technic (I assume it's just a matter of personal preference) :
//In the module module.exports.myClass1=class myClass1{}
module.exports.myClass2=class myClass2{}
//In the main file (consumer)
var myModule = require('../../../Root/Libs/Waldata/wsNodeClasses');
...
var test=new myModule .wsUrl("param");
This works for me with NodeJs 6.4.0 and intellij (or webstorm)
PS :I'm adding an answer, because I had problems formatting my comment (could not make a "line break"

Why can node not find my module?

I am using node v0.12.5 with nwjs and I have defined my own custom module inside of my project so that I can modularise my project (obviously).
I am trying to call my module from another module in my project but every time I attempt to require it I get the error could not find module 'uploader'.
My uploader module is currently very simple and looks like:
function ping_server(dns, cb) {
require('dns').lookup(dns, function(err) {
if (err && err.code == "ENOTFOUND") {
cb(false);
} else {
cb(true);
}
})
}
function upload_files()
{
}
module.exports.ping_server = ping_server;
module.exports.upload_files = upload_files;
With the idea that it will be used to recursively push files to a requested server if it can be pinged when the test device has internet connection.
I believe I have exported the methods correctly here using the module.exports syntax, I then try to include this module in my test.js file by using:
var uploader = require('uploader');
I also tried
var uploader = require('uploader.js');
But I believe node will automatically look for uploader.js if uploader is specified.
The file hierarchy for my app is as follows:
package.json
public
|-> lib
|-> test.js
|-> uploader.js
|-> css
|-> img
The only thing I am thinking, is that I heard node will try and source the node_modules folder which is to be included at the root directory of the application, could this be what is causing node not to find it? If not, why can node not see my file from test.js given they exist in the same directory?
UPDATE Sorry for the confusion, I have also tried using require('./uploader') and I am still getting the error: Uncaught Error: Cannot find module './uploader'.
UPDATE 2 I am normally completely against using images to convey code problems on SO, but I think this will significantly help the question:
It's clear here that test.js and uploader.js reside in the same location
When you don't pass a path (relative or absolute) to require(), it does a module lookup for the name passed in.
Change your require('uploader') to require('./uploader') or require(__dirname + '/uploader').
To load a local module (ie not one from node_modules) you need to prefix the path with ./. https://nodejs.org/api/modules.html#modules_modules
So in your case it would be var uploader = require('./uploader');
This problem stemmed from using Node Webkit instead of straight Node, as stated in their documentation all modules will be source from a node_modules directory at the root of the project.
Any internal non C++ libraries should be placed in the node_modules directory in order for Node webkit to find them.
Therefore to fix, I simply added a node_modules directory at the root of my project (outside of app and public) and placed the uploader.js file inside of there. Now when I call require('uploader') it works as expected.
If you're developing on a mac, check your file system case sensitivity. It could be that the required filename is capitalized wrong.

Resources