Transforming UMD modules to ES modules in RollupJS ("The requested module X does not provide an export named 'default'") - node.js

I am currently having a TypeScript project that includes Ethers.js, which in turn includes bn.js.
The problem is
SyntaxError: The requested module './../../../bn.js/lib/bn.js' does not provide an export named 'default'
It appears to me this is because BN is in UMD format (see at https://github.com/indutny/bn.js/blob/master/lib/bn.js#L1)
(function (module, exports) {
'use strict';
// Utils
function assert (val, msg) {
if (!val) throw new Error(msg || 'Assertion failed');
}
and the correponding .ts declaration is
"use strict";
/**
* BigNumber
*
* A wrapper around the BN.js object. We use the BN.js library
* because it is used by elliptic, so it is required regardless.
*
*/
import _BN from "bn.js";
import BN = _BN.BN;
import { Bytes, Hexable, hexlify, isBytes, isHexString } from "#ethersproject/bytes";
import { Logger } from "#ethersproject/logger";
import { version } from "./_version";
const logger = new Logger(version);
It may be there's something that could be done at importing (source) or in Rollup. Difficult to tell!
Here is a screenshot of the build errors (one variation, depending on if building or running directly)
Question: Is there a way to transform this format to an ESM format in application Rollup pipeline?
I have tried using #rollup/plugin-commonjs and #rollup/plugin-node-resolve as in
resolve({ browser: true, preferBuiltins: false }), commonjs()]
(or see the project as whole at https://github.com/veikkoeeva/erc1155sample/blob/main/web/rollup.config.js, the error shows with npm run test or npm run start (in console log)).
Thus far I've had no luck cracking this, though. Hence coming here wondering if there's a dumb issue I don't see or if this is a genuinely tougher issue.
Edit: indeed, following https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module and maybe named exports is the key here...

Related

How to deal with node modules in the browser?

My title is a bit vague, here is what I'm trying to do:
I have a typescript npm package
I want it to be useable on both node and browser.
I'm building it using a simple tsc command (no bundling), in order to get proper typings
My module has 1 entry point, an index.ts file, which exposes (re-exports) everything.
Some functions in this module are meant to be used on node-only, so there are node imports in some files, like:
import { fileURLToPath } from 'url'
import { readFile } from 'fs/promises'
import { resolve } from 'path'
// ...
I would like to find a way to:
Not trip-up bundlers with this
Not force users of this package to add "hacks" to their bundler config, like mentioned here: Node cannot find module "fs" when using webpack
Throw sensible Errors in case they are trying to use node-only features
Use proper typings inside my module, utilizing #types/node in my code
My main problem is, that no matter what, I have to import or require the node-only modules, which breaks requirement 1 (trips up bundlers, or forces the user to add some polyfill).
The only way I found that's working, is what isomorphic packages use, which is to have 2 different entry points, and mark it in my package.json like so:
{
// The entry point for node modules
"main": "lib/index.node.js",
// The entry point for bundlers
"browser": "lib/index.browser.js",
// Common typings
"typings": "lib/index.browser.d.ts"
}
This is however very impractical, and forces me to do a lots of repetition, as I don't have 2 different versions of the package, just some code that should throw in the browser when used.
Is there a way to make something like this work?
// create safe-fs.ts locally and use it instead of the real "fs" module
import * as fs from 'fs'
function createModuleProxy(moduleName: string): any {
return new Proxy(
{},
{
get(target, property) {
return () => {
throw new Error(`Function "${String(property)}" from module "${moduleName}" should only be used on node.js`)
}
},
},
)
}
const isNode = typeof window === undefined && typeof process === 'object'
const safeFs: typeof fs = isNode ? fs : createModuleProxy('fs')
export default safeFs
As it stands, this trips up bundlers, as I'm still importing fs.

sinonjs - stub a library referenced internally as a function using node modules (no require)

I have an external library that is exported as a function, in the Stub documentation it only accepts an input with the first parameter as object and the second parameter as method , so how could I stub a library that is exported as a function in a Node ES Modules environment (without Commonjs)?
(In my specific case, I had used a library that use the internet to work, and I wanted to test derivated functions without accessing the internet, so I want to stub the external function library)
Attempts:
I couldn't use solutions like proxyquire as it is a solution based on require and module cache deletion, which are not supported within Node's ES modules.
I don't want to use proxyquire-universal because it simulates the operation of require in normal ES, and it's just a function in the whole project that I wanted to test, I was looking for a simpler solution
Changing the import mode doesn't work as it's not recompiled like in babel, so even if I import as import * as obj from 'lib' only the function name is changed
I had this error environment with 3 files:
An external library called "sum" for example, which I don't want to change, exported as follows:
External Library: externalSum.js
module.exports = function sum(a, b){
console.log(">>>>> running without stub <<<<<")
return a + b
}
This library used in the middle of a project file called mathProblems
Internal File: mathProblems.js
import sum from 'externalSum'
export function sumMore1(a) {
return sum(a, 1);
}
And I have a test file
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from 'externalSum'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj) // NOT STUBBING
sum_stub.withArgs(1, 1).returns(2) // NOT STUBBING
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I didn't find this solution anywhere on the internet, i found some solutions that work for node with request or babilon, but not for my case using ES Modules:
https://github.com/sinonjs/sinon/issues/562
https://minaluke.medium.com/how-to-stub-spy-a-default-exported-function-a2dc1b580a6b
So I wanted to register the solution in case anyone needs it.
To solve this, create a new file, which can be allocated anywhere in the project, in this case I will call it sumImport.js:
Internal File: sumImport.js
import sum from 'externalSum';
// export as object
export default {
sum
}
The object needs to be called inside the created function I want to test, and changed the import way:
Internal File: mathProblems.js
import sumObj from './sumImport.js';
export function sumMore1(a) {
const { sum } = sumObj;
return sum(a, 1);
}
And I finally managed to import as an object in the test:
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from '../sumImport.js'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj, "sum") // STUBBED
sum_stub.withArgs(1, 1).returns(2) // STUBBED
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I hope it helps someone and if someone else has some better solutions I would also be grateful!

Cypress: Cannot use cy.task() to load dataset to Mongo before tests

I'm trying to use cy.taks() to load certain datasets to mongo before a test is run. But I'm getting errors. I've got a module where I export 2 functions, one from dropping a collection, and the other to load an object to a collection. Here is my cypress/plugins/index.js:
module.exports = (on, config) => {
on("task", {
"defaults:db": () => {
const {dropCollection, createUser } = require("../../lib/connectDB");
dropCollection("users");
createUser(userData)
},
});
};
Here is my /lib/connecDB.js:
export function dropCollection(collection) {
return mongoose.connection.dropCollection(collection);
}
export async function createUserInDB(userData) {
await User.create(userData);
}
So when I run the test, I'm getting:
cy.task('defaults:db') failed with the following error:
Unexpected token 'export'
Tried as well importing these function outside the index.js export, but getting same result.
I'd say it is something about export/import. The functions are exported as ES6, and imported as ES5.
I've tried to import the function the ES6 like:
import { dropCollection, createUser } from '../lib/connectDB'
And then export the plugin function also as ES6, but then I get:
Error: The plugins file is missing or invalid.
Your `pluginsFile` is set to `C:\Users\someRoute\cypress\plugins\index.js`, but either the file is missing, it contains a syntax error, or threw an error when required. The `pluginsFile` must be a `.js`, `.ts`, or `.coffee` file.
I've also tried to import required modules outside the function like:
const globalDbUtils = require('../lib/connectDB')
And then use the functions as
globalDbUtils.dropCollection("collectionName")
globalDbUtils.createUser(userData)
But I'm getting last error. I've tried pretty much everything, I tried to import Mongoose models straight, mongo client etc...Also I tried to import just one function and return it (just copy/pasting official doc...) but cannot make it work. I researched for a couple of days getting nothing, I found there is a npm package that helps u doing this, but since cypress allows you to do this by using no more plugins, I'd like to do it with no more tools than cypress itself.
Anyone knows what I am doing wrong?
Thanks in advance!
You need to use require instead of import at the top of your file and when exporting at the bottom use
module.exports = { createUserInDB }
Instead of exporting as you are currently doing.

Node repl module not found

I am trying to write a simple node script with some classes.
I first define a class
export default class Checkout() {
constructor () {
console.log('checkout')
}
check() {
console.log('check')
}
}
Then I am trying to use it
>node
>repl
check = new Checkout()
Uncaught ReferenceError: Checkout is not defined
require('Checkout')
Uncaught Error: Cannot find module 'Checkout'
How can I solve this? I am coming from Ruby where the console is pretty straight forward.
Code in a file isn't included in any other scope outside of that file until you require or import the file. When requiring/importing, the name the object you exported from the file doesn't automatically get used, you still have to specify it.
However, you're currently mixing require (CommonJS module format) with export default (ECMAScript Module format). There is only very limited interoperability between these formats using dynamic import(), but it's not yet available in the Node REPL (open issue here). If you need to test your Checkout class in the REPL, you'd need to just switch to using CommonJS:
module.exports = class Checkout() {
constructor () {
console.log('checkout')
}
check() {
console.log('check')
}
}
Usage:
> Checkout = require('checkout.js')
> check = new Checkout()

Node.js lazy require module

I'm writing a lazy module require() or import
const lazy = new Proxy({},
{
get: function (target, name) {
console.log('lazy require', { target, name })
return require(name)
}
}
)
/**
* #param {string} Module Name
* #example const expo = requirez('expo')
*/
export default function requirez(name) {
return lazy[name]
}
and oddly when I run it I get:
Cannot find module "."
The console.log statement logs:
lazy require {target: {…}, name: "./Linking"}
So require(name) should be getting called as: require("./Linking")
Not as require(".") which the error is indicating.
Found a related bug report:
https://github.com/webpack/webpack/issues/4921
Since node require tree resolution is evaluated/analyzed statically and webpack assumes this it fails on dynamic resolution.
Also, on browser webpack will have to transpile the required bundle before running in the browser thus dynamic lazy requires can't be run after transpilation. You'd be missing the transpiled source of that required module.
I've tried using import() but it also has bugs:
https://github.com/webpack/webpack/issues/4292#issuecomment-280165950

Resources