Jest error "Cannot use import statement outside a module" when importing node-fetch even with the CommonJS format - node.js

I'm pretty new to node.js and I'm confused with the import/export system. If I install a package using NPM in my project's node_modules directory, should I eye-check it to know whether it has used the ES6 module system or the CommonJS module system to export its things, and then use the same system for my imports accordingly?!
Node's documentation says it's interoperable in imports:
An import statement can reference an ES module or a CommonJS module.
However, it doesn't seem to work in my case. My problem is, I have set "module": "commonjs", in my tsconfig.json file and so the compiled output will have commonJS imports, however, in a typescript test file I have imported node-fetch like this: import fetch from 'node-fetch', then when I compile it (tsc) and run jest on the files in the build directory it gives this error:
SyntaxError: Cannot use import statement outside a module
16 | const supertest_1 = importDefault(require("supertest"));
---> 17 | const node_fetch_1 = importDefault(require("node-fetch"));
When I search the above error on StackOverflow the existing answers say "jest does not support ES6 modules completely yet (the support is experimental)", however, the point is, I am not using ES6 module imports in this case at all!. As I explained, the compiled files will have commonJS imports... (and jest is running those compiled tests too).
Here are some parts of my code that might be relevant to this question:
// jest.config.js
const { defaults } = require('jest-config');
/** #type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
testMatch: ["**/dist/test/**/*", ...defaults.testMatch],
};
// test/example-test.ts
import app from '../src/app';
import request from "supertest";
import fetch from 'node-fetch';
describe(" ..... ", () => { //...
Is it a problem of jest? Or a problem of node-fetch? Or even maybe the imports in the compiled output of TypeScipt?
Also here's the compiled import:
// dist/test/example-test.js
//...
const app_1 = __importDefault(require("../src/app"));
const supertest_1 = __importDefault(require("supertest"));
const node_fetch_1 = __importDefault(require("node-fetch"));

Oh wow figured it out, for future visitors...
Don't include a jest.config.js and add a babel.config.js with:
module.exports = {
presets: [['#babel/preset-env']],
};
makes sure to have
"#babel/core": "^7.18.9",
"#babel/preset-env": "^7.18.9",
"babel-jest": "^28.1.3",
in package.json

Related

Unexpected token error "export" + ERR_REQUIRE_ESM Babel transpilation cyclic errors

I'm trying to integrate the #amcharts/amchart5 lib.
I'm facing an issue where babel tries to transpile the lib.
First of all, when I'm importing the modules like:
import * as am5 from "#amcharts/amcharts5";
import * as am5xy from "#amcharts/amcharts5/xy";
I get an error:
Unexpected token error "export" at #amcharts/amcharts5/index.js:1
#amcharts/amcharts5/index.js looks like:
export { Root } from "./.internal/core/Root";
export { Theme } from "./.internal/core/Theme";
export { addLicense, registry, disposeAllRootElements } from "./.internal/core/Registry";
export { ready } from "./.internal/core/util/Utils";
export { Bullet } from "./.internal/core/render/Bullet";
export { Button } from "./.internal/core/render/Button";
export { Circle } from "./.internal/core/render/Circle";
export { Ellipse } from "./.internal/core/render/Ellipse";
export { Star } from "./.internal/core/render/Star";
...
...
Now, I explicitly add this to be transpiled in babel register(since babel by default ignore node_modules)
require('#babel/register')({
ignore: [/node_modules\/(?!(#amcharts\/amcharts5)\/).*/],
})
But now I get
Error [ERR_REQUIRE_ESM]: require() of ES Module d3-scale from #amcharts/amcharts5/Slice.js not supported.
Instead change the require to a dynamic import() which is available in all CommonJS
modules.
It seems babel is converting the amcharts files correctly but is also converting the ESM modules imports to require which is throwing the error.
Using:
Node#16
webpack#5.73
babel#7
amcharts#5.2.32
Using babel.config.json for configuring the babel options with babel-preset-react & babel-preset-env. Also using transform-runtime plugin.
How do I resolve this cyclic dependency?
I cannot use type module in package.json and convert all requires to imports. I want a solution that is backward compatible.

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.

ts-jest is not transpiling modules imported with Dynamic Imports

I am using dynamic imports in my nodejs code to import some modules. Ex:
const initialize = (await import('./init')).default;
await initialize();
But when i run jest, it throws the following error:
import sequelize from 'sequelize';
^^^^^^
SyntaxError: Cannot use import statement outside a module
I am assuming that ts-jest is not transforming/transpiling the code that is imported dynamically.
My jest.config.js file has:
/** #type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
rootDir: './src/test',
globalSetup: '../setup/test_setup.ts',
};
Please advice on how to resolve this
The error was occurring when the globalSetup code was being run. Not sure if ts-jest doesn't transpile globalSetup code.
To fix the issue, I added this to the top of the test_setup.ts file (which has the globalSetup code):
require('ts-node').register({ transpileOnly: true });
(https://github.com/kulshekhar/ts-jest/issues/411#issuecomment-850944698)
You need to install ts-node for this to work.

The injectable 'PlatformLocation' needs to be compiled using the JIT compiler, but '#angular/compiler' is not available

My Angular application is served via Node 16.13.0. After updating to Angular 13, I'm receiving the following error:
JIT compilation failed for injectable [class PlatformLocation]
file:///Users/btaylor/work/angular-apps/dz-outages-ui/node_modules/#angular/core/fesm2015/core.mjs:4058
throw new Error(message);
^
Error: The injectable 'PlatformLocation' needs to be compiled using the JIT compiler, but '#angular/compiler' is not available.
The injectable is part of a library that has been partially compiled.
However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.
Ideally, the library is processed using the Angular Linker to become fully AOT compiled.
Alternatively, the JIT compiler should be loaded by bootstrapping using '#angular/platform-browser-dynamic' or '#angular/platform-server',
or manually provide the compiler with 'import "#angular/compiler";' before bootstrapping.
at getCompilerFacade (file:///Users/btaylor/work/angular-apps/dz-outages-ui/node_modules/#angular/core/fesm2015/core.mjs:4058:15)
at Module.ɵɵngDeclareFactory (file:///Users/btaylor/work/angular-apps/dz-outages-ui/node_modules/#angular/core/fesm2015/core.mjs:32999:22)
at file:///Users/btaylor/work/angular-apps/dz-outages-ui/node_modules/#angular/common/fesm2015/common.mjs:90:28
at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
at async loadESM (node:internal/process/esm_loader:88:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)
I have tried numerous solutions, such as: Angular JIT compilation failed: '#angular/compiler' not loaded
Currently, I have "type": "module" in my package.json
I have updated my postinstall command to: ngcc --properties es2020 browser module main --first-only --create-ivy-entry-points
I also added import '#angular/compiler'; to my main.ts file.
The project will compile, but won't run via Node.
I believe I have found the solution (presuming you are using jest as your test runner). In the test-setup.ts file my project still was using the outdated import for jest-preset-angular. Instead of import 'jest-preset-angular'; try using import 'jest-preset-angular/setup-jest';.
This addressed the issue for me.
It seems angular 13 made babel-loader a mandatory requirement to link Ivy-native packages. https://github.com/angular/angular/issues/44026#issuecomment-974137408 - and the issue is happening as core Angular libraries are already compiled with the Ivy package format.
I haven't yet made the change in my own projects, but processing .mjs files with babel and a special linker should be enough.
import { dynamicImport } from 'tsimportlib';
/**
* Webpack configuration
*
* See: http://webpack.github.io/docs/configuration.html#cli
*/
export default async (options: IWebpackOptions) => {
const linkerPlugin = await dynamicImport('#angular/compiler-cli/linker/babel', module);
const config: any = {
module: {
rules: [{
test: /\.mjs$/,
loader: 'babel-loader',
options: {
compact: false,
plugins: [linkerPlugin.default],
},
resolve: {
fullySpecified: false
}
}
}
}
}
I'll make more updated to this post once I am able to test it myself.
If I correctly understand, you use Angular Universal also.
I want to share my experience with the same problem which came to my project after updating Angular from v8 to v13.
First of all, I want to say that the main problem was with the incorrect started file for SSR. In my project, it was server.js and now it's main.js.
So, maybe your project also tries to start from the incorrect file which doesn't have the necessary code to start it.
More details:
I updated the project step by step, increasing the version as recommended by the Angular team and according to https://update.angular.io/?l=3&v=8.2-13.0
Unfortunately, at every step, I only checked the version of SPA without SSR and only compiled the project to check that all is OK, but didn't start it with SSR. Now I can't say when the problem with the JIT compiler started.
And when I finished updating and fixing bugs with SPA, compiled the project with SSR, and tried to start it I saw this problem:
Error: The injectable 'PlatformLocation' needs to be compiled using the JIT compiler, but '#angular/compiler' is not available.
I tried different solutions like you, but nothing helped. After that, I created a new clean project with ng13 and added Angular Universal. And compare my project with new, generated by Angular-CLI.
What I changed in my project(sorry, I can't show the project - NDA):
angular.json
"projects": {
"projectName": {
...
"architect": {
...
"server": {
...
"options": {
"outputPath": "dist/server",
//"main": "src/main.server.ts", //removed
"main": "server.ts", //added
...
}
}
}
}
}
package.json
"scripts": {
...
//"serve:production:ssr": "node dist/server --production" // removed. It was incorrect file server.js which gave the error from this case
"serve:production:ssr": "node dist/server/main --production", // added
...
"postinstall": "ngcc --properties es5 browser module main --first-only" // added. In my case, actual version is es5
}
3.server.ts
import 'zone.js/node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
...
// after all imports I inserted previous code into function run()
...
export function run() {
// previous code from project with some small changes
}
//and added next code from clean project:
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from '../src/main.server';
src/main.server.ts
// was only:
export { AppServerModule } from './app/app.server.module';
// replaced by code from clean project:
/***************************************************************************************************
* Initialize the server environment - for example, adding DOM built-in types to the global scope.
*
* NOTE:
* This import must come before any imports (direct or transitive) that rely on DOM built-ins being
* available, such as `#angular/elements`.
*/
import '#angular/platform-server/init';
import { enableProdMode } from '#angular/core';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
export { AppServerModule } from './app/app.server.module';
export { renderModule, renderModuleFactory } from '#angular/platform-server';
src/main.ts
...
//this part of code
document.addEventListener("DOMContentLoaded", () => {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.log(err));
});
// replaced by
function bootstrap() {
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
};
if (document.readyState === 'complete') {
bootstrap();
} else {
document.addEventListener('DOMContentLoaded', bootstrap);
}
//but I don't think it plays a role in solving the JIT problem. Wrote just in case
tsconfig.server.json
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "./out-tsc/app-server",
"module": "commonjs",// it's only in my case, I don't have time for rewrote server.ts now
"types": ["node"],
},
"files": [
"src/main.server.ts",
"server.ts"
],
"include":["server/**/*.ts","node/*.ts"],
"angularCompilerOptions": {
"entryModule": "./src/app/app.server.module#AppServerModule"
}
}
Ok, I think that's all. After all these edits I don't have file /dist/server.js and start the project from /dist/server/main.js without error from this case.
N.B.: When I was updating the project step-by-step I noticed that the process of updating nguniversal by Angular-CLI didn't change anything in the project. Only the version of packages. That's why I recommend comparing your project with an actual clean project manually

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

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...

Resources