nodejs import module is not working as expecting, - node.js

I have the following structure.
node_modules
src
- app.js
- newrelic.js
package.json
package-lock.json
In newrelic.js its a simple class
const axios = require('axios')
export default class Newrelic {
static getName() {
return 'Hello;
}
}
in app.js
import Newrelic from "./newrelic";
console.log(Newrelic.getName())
When I run node src/app.js Sadly I receive
import Newrelic from "./newrelic";
^^^^^^^
SyntaxError: Cannot use import statement outside a module
What do I need to make this work?

In package.json add the field "type": "module" and your code will work.
More info here:
https://nodejs.org/api/esm.html

It is a very frequent question, when you deal with old packages. For example, it works with 'express-handlebars' v.6.x too.
Adding to file package.json the field "type": "module" resolves a similar conflict.

Related

"Cannot use import statement outside a module" with Axios

I have a Vue.js application where two files contain:
import axios from "axios"
These files are located in src/lib within the application and include the import statement on their first line.
Running tests on Github causes Axios 1.0.0 to be installed, no matter what the package.json says, and now any test involving these files fails with the above error.
Changing the statement to const axios = require("axios") fails also; node_modules/axios/index.js contains an import statement on line 1 and the exception is thrown there.
A suggestion I've seen quite often for such issues is to add "type": "module" to package.json (which is at the same level as src/). This causes all tests to fail with a demand to rename vue.config.js as vue.config.cjs. Doing that gets me: Error: You appear to be using a native ECMAScript module configuration file, which is only supported when running Babel asynchronously, which I do not understand.
Can anyone suggest what to do here?
I was able to fix this error by forcing jest to import the commonjs axios build by adding
"jest": {
"moduleNameMapper": {
"axios": "axios/dist/node/axios.cjs"
}
},
to my package.json. Other solutions using transformIgnorePatterns didn't work for me.
The 1.x.x version of axios changed the module type from CommonJS to ECMAScript.
The 0.x.x version of axios index.js file
module.exports = require('./lib/axios');
The 1.x.x version of axiox index.js file
import axios from './lib/axios.js';
export default axios;
Basically, jest runs on Node.js environment, so it uses modules following the CommonJS.
If you want to use axios up to 1.x.x, you have to transpile the JavaScript module from ECMAScript type to CommonJS type.
Jest ignores /node_modules/ directory to transform basically.
https://jestjs.io/docs/27.x/configuration#transformignorepatterns-arraystring
So you have to override transformIgnorePatterns option.
There are two ways to override transformIgnorePatterns option.
jest.config.js
If your vue project uses jest.config.js file, you add this option.
module.exports = {
preset: "#vue/cli-plugin-unit-jest",
transformIgnorePatterns: ["node_modules/(?!axios)"],
...other options
};
package.json
If your vue project uses package.json file for jest, you add this option.
{
...other options
"jest": {
"preset": "#vue/cli-plugin-unit-jest",
"transformIgnorePatterns": ["node_modules\/(?!axios)"]
}
}
This regex can help you to transform axios module and ignore others under node_modules directory.
https://regexper.com/#node_modules%5C%2F%28%3F!axios%29
Updating the version of jest to v29 fixed this in my project. It could be the case that you have an incompatible jest version.
I had the same issues and was able to solve this by using jest-mock-axios library
I experience similar problem but the error is caused by jest.
All the tests trying to import axios fail and throw the same exception:
Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
/monorepo/node_modules/axios/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import axios from './lib/axios.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
1 | import { describe, expect, it } from '#jest/globals'
> 2 | import axios from 'axios'
The solution is simply tell jest that axios should be transformed with babel:
const esModules = ['lodash-es', 'axios'].join('|')
# add these entries in module.exports
transform: {
[`^(${esModules}).+\\.js$`]: 'babel-jest',
},
transformIgnorePatterns: [`node_modules/(?!(${esModules}))`],
Note: I'm using Quasar Vue and this is their implementation.
Quick fix
Update the npm run test script from
"test": "react-scripts test",
to
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!axios)/\"",
In my case I had to add the following line to the moduleNameMapper object in the jest config:
axios: '<rootDir>/node_modules/axios/dist/node/axios.cjs',
I had the same issue, it works fine when changing axios to fetch.
axios (Fail)
try {
const response = await axios("api/fruit/all");
return response.data;
} catch (error) {
return error;
}
Fetch (Works fine)
try {
const response = await fetch("api/fruit/all",{method:"GET"});
const data = await response.json();
return data;
} catch (error) {
return error;
}

Vite: Cannot use import statement outside a module

I know little about bundler and I'm using vite to build project, I got a error when import some package to configure dev server :
SyntaxError: Cannot use import statement outside a module
So here is the thing:
import pinyin from 'pinyin/esm/pinyin-web.js'
export const somePlugin = {
name: 'someplugin',
configureServer(server) {
server.middlewares.use('/somepath', (req, res, next) => {
const foo = pinyin('foo')
next()
})
},
}
I don't use the normal way(import pinyin from 'pinyin') , because that need a package nodejieba which need to install unnecessary node-gyp, so I choose the web version that don't need nodejieba.
I've searched the error, some says add "type": "module" to package.json file. but it already exist in my package.json.
however, I make the change:
// import pinyin from 'pinyin/esm/pinyin-web.js'
import pinyin from 'pinyin/lib/pinyin-web.js'
and problem get solved,I was confused because I thought vite prefer ES module.
So,
1> what cause the problem above?
2> why should I import file with extensions ? eg: import pinyin from 'pinyin/lib/pinyin-web.js'
I have to add extensions .js or it will cause error. while in vite.config.ts I needn't add extensions.
3> I tried to add field optimizeDeps in vite.config.ts like this
export default defineConfig({
plugins: [vue(), somePlugin],
optimizeDeps: {
include: ['pinyin'],
},
})
but it seems to be useless, the offical doc says:
"During development, Vite's dev serves all code as native ESM. Therefore, Vite must convert dependencies that are shipped as CommonJS or UMD into ESM first."
did that work for the frontend part and package "pinyin" is for the dev server so whether add the
field optimizeDeps there is no difference.
codesandbox

Node.js 14 ES module import from another folder

This is a FYI and a question.
I am using Node.js 14.17.3 (LTS) and wanted to take advantage of using import ES syntax to use some files in another project (which need to remain where they are).
So, I made a simple node.js app to test the import. I added "type": "module" to the package.json file in the test app folder.
The (cut down test) file mixin-modules-definitions.js to import is:
const classModuleDefs = {
name: 'mydef'
}
export default classModuleDefs
Now, if I have that file in the same folder as the package.json file of my test app, this works fine:
import classModuleDefs from './mixin-modules-definitions.js'
console.log('classModuleDefs=', classModuleDefs)
But if I try to import from where the file actually lives like the following, I get the dreaded SyntaxError: Unexpected token 'export' error.
import classModuleDefs from '../src/services/mixin-modules-definitions.js'
And, if I then add "type": "module" to the nearest package.json file relevant to that path, the error goes away and the file is imported correctly.
As it turns out, the app I am importing from is ok with adding "type": "module" to its package.json file but I could imagine a scenario where it might not be ok.
Is it possible to import a file and have node resolve to use the package.json in my test node app without needing to also change the package.json in the "source" app.
Thanks,
Murray

Package.json with multiple entrypoints

I have a library which was used as follows
import { Foo } from '#me/core';
const foo = new Foo();
The package.json of that library looks like
"name": "#me/core",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
...
With dist/index.js its entrypoint. Now, however, I would provide an import for NodeJs projects only and one for web projects. Ideally I would have something like this for NodeJs
import { Foo } from '#me/core/nodejs';
and if you're working on a web-project you could do
import { Foo } from '#me/core/web';
My understanding is that both #me/core/nodejs and #me/core/web will be different NPM packages this way, which is not what I want. I want it to be in 1 npm package.
I tried to changed the library's index.ts file, from
export * from './foo';
into
import * as nodejs from './nodejs';
import * as web from './web';
export { web, nodejs };
This actually works, but I have to use it now (in a NODEJS project) as follows
import { nodejs } from '#me/core';
const foo = new nodejs.Foo();
Is there maybe a way to import this such that I don't need the nodejs everytime?
As you can see I'm not so sure what I should do here so any help would be appreciated!
UPDATE: Based on the suggestions by #Klaycon I see the following error:
As you're using ECMAScript modules, please refer to node.js docs on package entry points:
In a package’s package.json file, two fields can define entry points for a package: "main" and "exports". The "main" field is supported in all versions of Node.js, but its capabilities are limited: it only defines the main entry point of the package.
The "exports" field provides an alternative to "main" where the package main entry point can be defined while also encapsulating the package, preventing any other entry points besides those defined in "exports". This encapsulation allows module authors to define a public interface for their package.
So, in your package.json, you could define exports like this:
"main": "dist/index.js",
"exports": {
".": "./dist/index.js",
"./nodejs": "./dist/nodejs",
"./web": "./dist/web",
}
The accepted answer, using the exports field, is one way to do it, but not the only way -- and as you've found, isn't currently supported by Typescript.
Look at the #angular/material package, which works similarly to what you have in mind. In #angular/material/package.json, you've got a module field that points to an empty JS file, and a typings field that points to an empty .d.ts file. But you've also got #angular/material/button/package.json which points (indirectly) to the implementation of MatButton (and a bunch of other stuff). When you import {MatButton} from "#angular/material/button", it resolves this package file, looks at its module and typings fields, and uses those to resolve the runtime code and exported types, respectively.
You could do the same thing: #me/core/package.json would have module and typings fields that point to empty files; #me/core/node/package.json would point to code for your Node-specific Foo class, and #me/core/web/package.json would point to code for your browser version of Foo.
Try to add the following code to the tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "NodeNext"
}
}
And then you can import the module like this:
import { Foo } from '#me/core/nodejs';
If you Don't Want to Touch the tsconfig.json you can use the following for the import:
import { Foo } from '#me/core/dist/nodejs';
References
Package entry points
Support for NodeJS 12.7+ package exports
A Proposal For Module Resolution

NodeJS How to import JS file into TypeScript

I'm new with TypeScript. I'm currently learning NodeJS Loopback 4 framework which use Typescript language. And my question is how to import some function, class which has been exported in JS file into my TS file. After search several way but it still doesn't work with me.
Here's example:
// /src/index.ts
import {plus} from './lib/test';
console.log(plus(1,2));
// /src/lib/test.js
export function plus(x, y) {
return x + y;
}
I also try using definition typescript like this
// /src/lib/test.d.ts
export declare function plus(x: number, y: number): number;
But still got error when import this function in index.ts file
Error: Cannot find module './lib/test'
at Function.Module._resolveFilename (module.js:543:15)
It looks like the tsconfig.json doesn't have 'allowJs' enabled because it exports declarations.
Is there a reason you are not wanting this to be a typescript file? If you change test.js to test.ts, by making it a .ts should allow it to be recognised in your index file.
UPDATE
Full chat history to get to this point can be found here.
Repository used to test found here
OK so easy solution as #maaz-syed-adeeb mentions will work:
import { path } from './lib/test.js'
The reason the extension is important is because the definition file takes priority over a javascript file in a typescript environment. That is why the module import was blowing up.
To avoid specifying the .js extension you can also setup your directory structure like this:
src
|- index.ts
|- lib
|- test.js
|- test.d.ts
|- index.[js|ts]
in ./lib/index file export all from test:
//./src/lib/index.[js|ts]
export * from './test'
and then import all from lib:
// ./src/index.ts
import { path } from './lib'
// or
import { path } from './lib/test.js'
If you are using a blend of javascript and typescript (say you are moving over to typescript with an existing code base), you will need to update your tsconfig.json to include so you don't get the warnings in your IDE:
{
"compilerOptions": {
"allowJs": true,
"declaration": false
}
}
This is so your javascript files will be transpiled to your destination directory along with the typescript files.

Resources