When I create an npm package, sometimes it would face the need to backward old dependency package version.
If the new version has new api, I may write the code in this pattern:
import pkg from 'some-pkg';
const isNewVersion = pkg.newVersionApi !== 'undefined';
if (isNewversion) {
pkg.newVersionApi();
} else {
pkg.oldVersionApi(); // backward compatible api
}
And with this pattern, when I want to write the test, I only can test the installed version code. The other version's code can't be tested.
For real example, in React v15 and v16, React v16 has new API Portal. Before Portal release, v15 has unstable_renderSubtreeIntoContainer api to realize similar feature.
So the code for React would be like:
import ReactDOM from 'react-dom';
const isV16 = ReactDOM.createPortal !== 'undefined';
if (isV16) {
ReactDOM.createPortal(...);
} else {
ReactDOM.unstable_renderSubtreeIntoContainer(...);
}
So I want to ask is there any method to test with different dependency version?
Currently, one method I think of is to install the other version again and test it. But it only can do on local. It can't work on ci and it can't count in coverage together.
I think that is not only for react test. It may face on node.js test. Any suggestion can be discussed.
Updated
This question maybe is related to install two versions dependency in npm. But I know currently installing two versions dependency is not workable.
Here is a might be solution, not sure it will work as you expect. But, you will have a direction to move forward.
package.json
{
"name": "express-demo",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.3",
"debug": "~2.6.3",
"express": "~4.15.2",
"jade": "~1.11.0",
"morgan": "~1.8.1",
"serve-favicon": "~2.4.2",
"webpack": "^3.8.1",
"webpack-dev-middleware": "^1.12.0",
"webpack-hot-middleware": "^2.20.0"
},
"customDependecies": {
"body-parser": [
"",
"1.18.1",
"1.18.0"
]
}
}
Note in above package.json file, I have added a new key customDependecies which I will use for installing multiple dependencies. Here I am using body-parser package for demo. Next you need file, that can read this key and install the deps.
install-deps.js
const {spawnSync} = require('child_process');
const fs = require('fs');
const customDependencies = require('./package.json').customDependecies;
spawnSync('mkdir', ['./node_modules/.tmp']);
for (var dependency in customDependencies) {
customDependencies[dependency].forEach((version) => {
console.log(`Installing ${dependency}#${version}`);
if (version) {
spawnSync('npm', ['install', `${dependency}#${version}`]);
spawnSync('mv', [`./node_modules/${dependency}`, `./node_modules/.tmp/${dependency}#${version}`]);
} else {
spawnSync('npm', ['install', `${dependency}`]);
spawnSync('mv', [`./node_modules/${dependency}`, `./node_modules/.tmp/${dependency}`]);
}
});
customDependencies[dependency].forEach((version) => {
console.log(`Moving ${dependency}#${version}`);
if (version) {
spawnSync('mv', [`./node_modules/.tmp/${dependency}#${version}`, `./node_modules/${dependency}#${version}`]);
} else {
spawnSync('mv', [`./node_modules/.tmp/${dependency}`, `./node_modules/${dependency}`]);
}
});
}
spawnSync('rm', ['-rf', './node_modules/.tmp']);
console.log(`Installing Deps finished.`);
Here, I am installing deps one by one in tmp folder and once installed, I am moving them to ./node_modules folder.
Once, everything is installed, you can check the versions like below
index.js
var bodyParser = require('body-parser/package.json');
var bodyParser1181 = require('body-parser#1.18.1/package.json');
var bodyParser1182 = require('body-parser#1.18.0/package.json');
console.log(bodyParser.version);
console.log(bodyParser1181.version);
console.log(bodyParser1182.version);
Hope, this will serve your purpose.
Create 3 separate projects (folders with package.json) and a shared folder:
A shared folder containing the test module (my-test). Export a function to run the test;
A client project importing my-test and dependency v1. Export a function that calls the test function in my-test.
A client project importing my-test and dependency v2. Export a function that calls the test function in my-test.
A master project that imports both client projects. Run each exported function.
You're going to have to run them separately. Create a separate project folder for each dependency version. Ex. React10, React11, React12. Each will have its own package.json, specified for the correct version. When you run the integration and/or versioned tests, you'll run your standard unit tests across each version, but it may also be advisable to add any version specific unit tests to that folder.
Creating a make file would make your life easier when running your full testing suite. If you do this you can easily integrate this into CI.
In addition to the other suggestions, you can try the approach described at Testing multiple versions of a module dependency.
Here's an example of using this approach to test against multiple webpack versions:
npm install --save-dev module-alias
npm install --save-dev webpack-v4#npm:webpack#4.46.0
npm install --save-dev webpack-v5#npm:webpack#5.45.1
The module-alias package handles the magic of switching between package versions while still supporting normal require('webpack') (or whatever your module is) calls.
The other installs will create two versions of your dependency, each with a distinct directory name within your local node_modules/.
Then, within your test code, you can set up the dependency alias via:
const path = require('path');
require('module-alias').addAlias(
'webpack',
path.resolve('node_modules', 'webpack-v4'),
);
// require('webpack') will now pull in webpack-v4
You'd do the same thing for 'webpack-v5' in a different test harness.
If any of your sub-dependencies have a hardcoded require('webpack') anywhere in their own code, this will ensure that they also pull in the correct webpack version.
Related
I have been working on Cypress's latest version (10.2.0) with BDD in cucumber. Everything's working fine just my feature files are not accessible. I have tried using SpecPattern as written in the documentation but no help.
The spec pattern line is as follows:
specPattern: "/cypress/e2e/**/*.feature"
Also, My cucumber is imported and running fine
Cypress.config file:
const { defineConfig } = require("cypress");
const cucumber = require('cypress-cucumber-preprocessor').default
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('file:preprocessor',cucumber())
// implement node event listeners here
},
Package.Json File:
"devDependencies": {
"cypress-cucumber-preprocessor": "^4.3.1"
},
"cypress-cucumber-preprocessor":{
"nonGlobalStepDefinitions" : false
}
}
Image showing there is no feature file in this directory
While my feature file is present in the same directory shown in the above picture
Any help would be appreciated
You should change your cypress-cucumber library.
This indicates you have an old version
const cucumber = require('cypress-cucumber-preprocessor').default
You should use this version
// package.json
"#badeball/cypress-cucumber-preprocessor": "^11.3.0",
// cypress.config.js
const preprocessor = require("#badeball/cypress-cucumber-preprocessor");
Repo, instructions etc Github
Update your specPattern to "**/*.feature". This will search for .feature files throughout your project and not just a specific folder.
I am trying to build a CLI for a node js only todo app using commander and conf modules in node js, with chalk to colour the output . I am not sure how to resolve the errors being returned:
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension
contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
I'm getting the above error for both conf and commander
Any suggestions on how I could go about debugging this, or changing approach to using readline and events/EventEmitter would be better, will be appreciated, Thanks
Below is a REDACTED version of code:
list.js
const conf = new (require('conf'))();
const chalk = require('chalk');
function list() {
const todoList = conf.get('todo-list');
if (todoList && todoList.length) {
console.log(
chalk.blue.bold(
'Tasks in green are done. Tasks in yellow are still not done.'
)
}
}
module.exports = list;
index.js file
const { program } = require('commander');
const list = require('./list');
program.command('list').description('List all the TODO tasks').action(list);
program.command('add <task>').description('Add a new TODO task').action(add);
program.parse();
package.json file
{
"main": "index.js",
"type": "module",
"keywords": [],
"dependencies": {
"chalk": "^5.0.0",
"chalk-cli": "^5.0.0",
"commander": "^8.3.0",
"conf": "^10.1.1"
},
"bin": {
"todos": "index.js"
}
}
In your package.json you have:
"type": "module",
This means files with the .js suffix are assumed to be ECMAScript rather than CommonJS. If you want to use CommonJS you can change the file suffix or change the "type" property.
Or you can use the new syntax. In ECMAScript you use import, in CommonJS you use require.
To read more about "type" see: https://nodejs.org/dist/latest-v16.x/docs/api/packages.html#determining-module-system
After some more research I found out I was 'muddying the waters' between CJS or ESM modules.
CJS modules use require and that is the old way of doing things prior to ES6 modules
ESM modules use import
My package.json says type: module telling NodeJS that I am using ESM. But the code is saying CJS.
These are the steps I take to fix this:
rename index.js to index.mjs
update package.json accordingly
replace all require calls with import statements
replace module.exports = list with default export = list (or used a named export)
For some reason, my jest configuration doesn't work with the latest version of d3-path#3.0.1. It worked fine with version 2.0.0. I guess it has something to do with d3-path switching to ESM, but I was already using ES6 in my own code, so I don't get why it suddenly doesn't work anymore. I have the following packages installed:
"dependencies": {
"d3-path": "^3.0.1"
},
"devDependencies": {
"#babel/core": "^7.15.8",
"#babel/preset-env": "^7.15.8",
"babel-jest": "^27.3.1",
"jest": "^27.3.1"
}
My babel.config.js:
module.exports = {
presets: [['#babel/preset-env', {targets: {node: 'current'}}]],
};
My index.js:
import { path } from 'd3-path'
export default () => path()
The test file:
import fn from '../src/index.js'
describe('test', () => {
it('works', () => {
fn()
expect(2 + 2).toBe(4)
})
})
The error message:
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export {default as path} from "./path.js";
^^^^^^
SyntaxError: Unexpected token 'export'
> 1 | import { path } from 'd3-path'
To reproduce:
git clone https://github.com/luucvanderzee/jest-problem.git
cd jest-problem
npm i
npm run test
// The test runs without failure- this is because we're currently still using d3-path#2.0.0
npm uninstall d3-path && npm install d3-path // (upgrade to d3-path#3.0.1)
npm run test
// Now the test fails.
How should I configure jest and/or babel to solve this issue?
EDIT:
I already tried the following (from this page of the jest docs):
Creating a jest.config.js file with the following:
module.exports = {
transform: {}
}
Changing my "test" command from "jest" to "node --experimental-vm-modules node_modules/jest/bin/jest.js"
This gives me another error:
/home/luuc/Projects/javascript/jest-problem/test/test.test.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import fn from '../src/index.js'
^^^^^^
SyntaxError: Cannot use import statement outside a module
I also don't get what is meant by
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
Isn't the problem that the module is not transformed? Would adding an ignore pattern not just lead to the module not getting transformed?
Problem
The error happens because jest does not send the content of node_modules to be transformed by babel by default.
The following output line of npm run test indicates one way to solve the problem:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
Solution
The configuration of jest should be updated in order to instruct it to transform the ESM code present in d3-path dependency.
To do so, add the following to a jest.config.js file in the root directory of the project:
module.exports = {
transformIgnorePatterns: ['node_modules/(?!(d3-path)/)']
}
npm run test runs fine after that.
The transformIgnorePatterns option is documented here.
Edit - including more modules
In order to include all modules starting with d3, the following syntax may be used:
transformIgnorePatterns: ['/node_modules/(?!(d3.*)/)']
TLDR;
transformIgnorePatterns: [
"/node_modules/(?!d3|d3-array|d3-axis|d3-brush|d3-chord|d3-color|d3-contour|d3-delaunay|d3-dispatch|d3-drag|d3-dsv|d3-ease|d3-fetch|d3-force|d3-format|d3-geo|d3-hierarchy|d3-interpolate|d3-path|d3-polygon|d3-quadtree|d3-random|d3-scale|d3-scale-chromatic|d3-selection|d3-shape|d3-time|d3-time-format|d3-timer|d3-transition|d3-zoom}|internmap|d3-delaunay|delaunator|robust-predicates)"
]
For the ones reaching this page after updating recharts dependency, here I found the solution, provided by them.
I have the following functionality that locally loads some products out of stock depending on the command run in the terminal:
const updates = process.env.option === "oos" ? oosUpdates : insUpdates
log.info("updates: ", JSON.stringify(updates))
const result = await loadUpdateVariants(cache, updates as UpdateType[])
In my package.json I have:
"update:oos": "ts-node src/loaders/mock/update-variants.ts --option oos",
"update:ins": "ts-node src/loaders/mock/update-variants.ts --option ins"
however when I run yarn update:oos I am getting the instock products meaning that this process.env.option === "oos" is not working out correctly.
process.env gives you access to environment variables. If you want to use environment variables, you can set them like this:
"update:oos": "OPTION=oos ts-node src/loaders/mock/update-variants.ts",
"update:ins": "OPTION=ins ts-node src/loaders/mock/update-variants.ts"
This is the "vanilla" (without any extra dependencies) approach - but may encounter issues on Windows. If you want your command to work well cross-platform, you might consider using cross-env
How do i use the nodejs_binary rule to do a standard npm run start. I am able to run a typical node project using this rule. However i want to run a the start script in package.json. So far i have the following below in my build file
load("#build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
nodejs_binary(
name = "app",
data = [":app_files"],
node="#nodejs//:bin/npm",
entry_point = "workspace_name/src/server.js",
node_modules = "#npm_deps//:node_modules",
args=["start"]
)
This does not start the server..somehow npm command is not running properly. it indicates usage of the command in incomplete.
I am currently able to do this within the WORKSPACE
bazel run #nodejs//:bin/yarn (runs yarn install and installs all node-modulse)
bazel run #nodejs//:bin/npm start (this starts the server)
In my package.json i have
{
"scripts": {
"start": "babel-node src/server.js",
...
}
...
}
I do i get this to work with nodejs_binary rule and subsequently node_image
I changed from using npm to using yarn..workspace_name/src/server.js.. is called now but Then i had different set of problems, babel-node was not found.
I modified the rule a bit. After careful study...I realise that there is a dependency on babel-node that is not satisfied at the time yarn run start is called. The following worked after i had run bazel run #nodejs//:bin/yarn before running the rule.
nodejs_binary(
name = "app",
args = ["start"],
data = [
":app_files",
"#//:node_modules",
],
entry_point = "workspace_name/src/server.js",
node = "#nodejs//:bin/yarn",
node_modules = "#npm_deps//:node_modules",
)
It appears that "#//:node_modules" solves the babel-node dependency issue. So the rule above does not work on its own...it needs me to do bazel run #nodejs//:bin/yarn (more like npm/yarn install to make the node_modules, which contain babel-node dependecy available when npm/yarn start is run)
So my problem is that I do not want to have to manually run bazel run #nodejs//:bin/yarn before executing my rule. how do i do this.
I suppose it would work if i stopped depending on babel-node...but then i would have to change my code to not use es6 syntax (that is a hustle). Is there a way i can do this with a genrule? or something...
What I ended up doing was that i made a babel nodejs_binary rule. Then used that to compile my source files in a gen rule
# Make babel binary
nodejs_binary(
name = "babel",
entry_point = "npm_deps/node_modules/babel-cli/bin/babel",
install_source_map_support = False,
node_modules = "#npm_deps//:node_modules",
)
# Compile source files with babel
genrule(
name = "compiled_src",
srcs = [
":src_files",
],
outs = ["src"],
cmd = "$(location babel) src --out-dir $#",
tools = [":babel"],
)
Note that in this case src in cmd = "$(location babel) src --out-dir $#" is a folder in the :src_files filegroup.
filegroup(
name = "src_files",
srcs = glob([
"src/**/*",
...
]),
)
After this it was unnecessary to use npm start, just used default node. I could just do
nodejs_binary(
name = "app",
data = [":compiled_src"],
entry_point = "workspace_name/src/server.js",
node_modules = "#npm_deps//:node_modules",
)