Node.js: How to transparently require and export submodules - javascript

I want to gather a lot of dependencies in one repository called, lets say module, so I only need to have one dependency for other repositories. Is that possible:
index.js in module repository to gather submodules
const submoduleA = require('submoduleA')
const submoduleB = require('submoduleB')
exports.submoduleA = submoduleA
exports.submoduleB = submoduleB
index.js in another module/library where I need all submodules, so I require module
const { submoduleA, submoduleB } = require('module')
Is that possible in any way? I know it's not a huge benefit, but would be nice to explain how all the submodules came about to be available.
What I want to be as transparent as possible is the module repository. Only requiring it to get all the other dependencies, but not getting in the way of normal syntax for those dependencies.

This example creates a fruit module that exports both the apple module and the banana module.
apple.js
function eatApple() {
return 'Yummy apple!';
}
module.exports = { eatApple };
banana.js
function eatBanana() {
return 'Yummy banana!';
}
module.exports = { eatBanana };
fruit.js
module.exports = {
apple: require('./apple'),
banana: require('./banana')
};
app.js
const fruit = require('./fruit');
console.log(fruit.apple.eatApple(), fruit.banana.eatBanana());
app.js (alternate)
const { apple, banana } = require('./fruit');
console.log(apple.eatApple(), banana.eatBanana());

Related

Load a CJS module config inside another file

I'm trying to load a config inside a node module but I'm not sure which is a good practice to do it, I have this scenario:
A config my.config.js:
module.exports = {
content: [
'./src/**/*.{tsx,ts}',
],
}
Then I have a module cli.mjs is supposed to load it:
import arg from 'arg'
import { readFile } from 'fs/promises'
import path from 'path'
let configPath = './my.config.js'
const args = arg({
'--config': String,
'-c': '--config',
})
if (args['--config']) {
configPath = args['--config']
}
console.log(readFile(path.resolve(configPath), { encoding: 'utf8' }))
This will just return a simple string with my config inside, not a javascript object:
`
module.exports = {
content: [
'./src/**/*.{tsx,ts}',
],
}
`
How should I load my config in the right way?
I've saw basically everyone use configs like that in TypeScript or CJS projects, but it's hard to me see how and where they parse these configs, probably I'm missing some basic information about this?

Problem with parsing javascript file marked as Shebang

I have a react app where I wanted to import a javascript file from a third-party library but file is mark with shebang #!/usr/bin/env node.
I found (e.g. here How to Configure Webpack with Shebang Loader to Ignore Hashbang Importing Cesium React Component into Typescript React Component) I can load file by overriding webpack configuration and adding a new loader shebang-loader (I also have tried shebang-loader2) but overriding webpack in react app is recommended only with #craco/craco so I added it to package.json and tried add loader to existing webpack-config.js.
I produced this lines of code. File craco.config.js:
const throwError = (message) =>
throwUnexpectedConfigError({
packageName: 'craco',
githubRepo: 'gsoft-inc/craco',
message,
githubIssueQuery: 'webpack',
});
module.exports = {
webpack: {
configure: (webpackConfig, {paths}) => {
const shebangLoader = { test: /node_modules\/npm-groovy-lint\/lib\/groovy-lint.js$/, loader: "shebang-loader" }
const {isAdded: shebangLoaderIsAdded1} = addAfterLoader(webpackConfig, loaderByName('url-loader'), shebangLoader);
if (!shebangLoaderIsAdded1) throwError('failed to add shebang-loader');
return webpackConfig;
},
},
};
It resolves problem with shebang and it ignores #!/usr/bin/env node but now I still get error
Module parse failed: Unexpected token (14:16)
File was processed with these loaders:
* ./node_modules/shebang2-loader/index.js
You may need an additional loader to handle the result of these loaders.
| const { getSourceLines, isErrorInLogLevelScope } = require("./utils");
| class NpmGroovyLint {
> "use strict";
| options = {}; // NpmGroovyLint options
| args = []; // Command line arguments
It looks like it does not recognise "use strict" line.
Can anyone put some suggestions what should be a problem ?
After few hours of investigation, I have finally come to a resolution. Firstly I have to say that there is no option to use NpmGroovyLint in react-like applications that run in browsers because after I resolved mentioned problem up here I figured that NpmGroovyLint uses node libraries as perf_hooks which are not available in a browser enviroment.
But I can post code that resolves the problem described in my question. It was needed to add a plugin to babel-loader named 'plugin-proposal-class-properties'. Here is my snipped of craco config. You can use it as a recipe occasionally.
const {addAfterLoader, getLoaders, loaderByName, removeLoaders, throwUnexpectedConfigError} = require('#craco/craco');
const throwError = (message) =>
throwUnexpectedConfigError({
packageName: 'craco',
githubRepo: 'gsoft-inc/craco',
message,
githubIssueQuery: 'webpack',
});
module.exports = {
webpack: {
configure: (webpackConfig, {paths}) => {
const {hasFoundAny, matches} = getLoaders(webpackConfig, loaderByName('babel-loader'));
if (!hasFoundAny) throwError('failed to find babel-loader');
const {hasRemovedAny, removedCount} = removeLoaders(webpackConfig, loaderByName('babel-loader'));
if (!hasRemovedAny) throwError('no babel-loader to remove');
if (removedCount !== 2) throwError('had expected to remove 2 babel loader instances');
//add plugin proposal class properties to existing babel loader
const propClassOptions = {...matches[1].loader.options, ...{plugins: ["#babel/plugin-proposal-class-properties"]}};
const propClassLoader = {...matches[1].loader, ...{options: propClassOptions}};
const babelLoaderWithPropClassPlugin = {...matches[1], ...{loader: propClassLoader}};
const shebangLoader = {
test: /node_modules\/npm-groovy-lint\/lib\/groovy-lint.js$/,
use: [{loader: 'shebang2-loader'}, {...{loader: require.resolve('babel-loader')}, ...{options: propClassOptions}}]
}
const {isAdded: babelLoaderWithPropClassIsAdded} = addAfterLoader(webpackConfig, loaderByName('url-loader'), matches[0].loader);
if (!babelLoaderWithPropClassIsAdded) throwError('failed to add ts-loader');
const {isAdded: babelLoaderIsAdded} = addAfterLoader(webpackConfig, loaderByName('babel-loader'), babelLoaderWithPropClassPlugin.loader);
if (!babelLoaderIsAdded) throwError('failed to add back babel-loader for non-application JS');
const {isAdded: shebangLoaderIsAdded1} = addAfterLoader(webpackConfig, loaderByName('url-loader'), shebangLoader);
if (!shebangLoaderIsAdded1) throwError('failed to add shebang-loader');
return webpackConfig;
},
},
};

Importing custom CommonJS module fails

I created a CommonJS module in project A in the following way:
const { WebElement } = require('selenium-webdriver');
const { By } = require('selenium-webdriver');
class VlElement extends WebElement {
constructor(driver, selector) {
...
}
async getClassList() {
...
}
}
module.exports = VlElement;
In project B I use the following code:
const VlElement = require('projectA');
class VlButton extends VlElement {
constructor(driver, selector) {
super(driver, selector);
}
...
}
module.exports = VlButton;
When running the code, VLElemlent cannot be found.
It is in my package.json and I can see VLElement under projectB > node_modules > projectA.
What am I doing wrong with my exports?
Thanks in advance.
Regards
Make sure you have a projectB/mode_modules/package.json with a main which points to the file that defines/exports VlElement, like this:
"main": "path/to/file/with/VlElement.js",
When you call require('projectA'); this has to be resolved to a file inside projectA so that it can be evaluated to (and return) the exports from that file. The main entry in the package.json allows this (but defaults to index.js, so if you are using that you don't need package.json, probably, but you should have it anyway).
You can have multiple files with various exports, but remember require('projectA'); can still only return one thing, so the way to do that is usually to have an index.js which looks something like:
module.exports = {
'something': require('./something.js'),
'otherthing': require('./otherthing.js'),
'etc': require('./etc.js'),
};

Mock require function in Jest

I am trying to mock require using Jest because I am working in a plugin system for electron that will also use NPM for resolving dependencies.
I need to then be able to mock require inside nodejs to be able to test something similar to the next logic.
const load = (installPath, pluginsName, extensionPoint) => {
require.main.paths.push(`${installPath}/node_modules`)
pluginsName.forEach(pluginName => {
let plugin = require(pluginName)
plugin.onLoad(extensionPoint)
});
}
module.exports = { load }
Is there any simple way of doing this?. As require is not a global, is there is any other way than wrapping and injecting it to test the logic?
As explained in this answer, it's possible to replace require in particular module by evaluating it with custom require, similarly to how Node does this internally:
const Module = require('module');
const vm = require('vm');
const childModuleAbsPath = path.resolve('./foo/bar.js');
const childModuleBody = fs.readFileSync(childModuleAbsPath);
const childModuleObj = { exports: {} };
const { dir: childModuleDirname, base: childModuleFilename } = path.parse(childModuleAbsPath);
const childRequire = jest.fn().mockReturnValue(...);
vm.runInThisContext(Module.wrap(childModuleBody))(
childModuleObj.exports,
childRequire,
childModuleObj,
childModuleDirname,
childModuleFilename
);

Is it possible to use Jest with multiple presets at the same time?

Is it possible to use Jest with multiple presets, say jsdom and react-native?
I'd like to test a React component that can work both on Web and in a React Native environment. The problem is that the component may use either React Native libraries or some document's methods.
When I run some tests, jest replies:
Cannot find module 'NetInfo' from 'react-native-implementation.js'
When I try to add
"jest": {
"preset": "react-native"
}
to package.json, I get:
ReferenceError: window is not defined
Presets are just plain Javascript objects, so in many circumstances you may simply merge them. For example, that's how I'm enabling ts-jest and jest-puppeteer simultaneously:
const merge = require('merge')
const ts_preset = require('ts-jest/jest-preset')
const puppeteer_preset = require('jest-puppeteer/jest-preset')
module.exports = merge.recursive(ts_preset, puppeteer_preset, {
globals: {
test_url: `http://${process.env.HOST || '127.0.0.1'}:${process.env.PORT || 3000}`,
},
})
If there are certain options that can't be 'merged' like that, just handle these cases manually.
Along these same lines, you can do this with the spread operator:
const tsPreset = require('ts-jest/jest-preset')
const puppeteerPreset = require('jest-puppeteer/jest-preset')
module.exports = {
...tsPreset,
...puppeteerPreset,
globals: {
test_url: `http://${process.env.HOST||'127.0.0.1'}:${process.env.PORT||3000}`,
},
}
For people that are looking for one preset plus typeScript
const { defaults: tsjPreset } = require('ts-jest/presets')
module.exports = merge.recursive(ts_preset, puppeteer_preset, {
preset: 'Your_Preset',
transform: tsjPreset.transform,
},
})

Categories