Confusion about require and import - javascript

I am confused about require and import in node.js.
Some modules use require and some use import can we use only one or the other for all modules?

Yes, you can exclusively use import statements to load modules.
You must configure Node to use the ESM loader. The most common method of doing this is setting the "type" field in package.json to "module":
// package.json
{
"type": "module"
}
See Determining the module system in the docs.

Related

How to support es modules and commonjs modules at the same time

Some packages we install from npm support both commonjs and es modules,
These packages can be imported as follows:
import express from 'express'
// or
const express = require('express')
I have created a package which I already published to npm using es modules.
and I since my another project which I'm working on is built with commonjs, I realized that I can not require it using the following syntax:
const stackPlayer = require('stack-player')
How can I support the two module systems in my package stack-player so that everyone around the world can use it?
Is there another method other than converting all of my project to es modules (which would be too complex since the project is 1 year old and is big enough to refuse the idea). ?
require() Usage
require() can, by default, only be used in CommonJS Modules. The built in method to import ECMAScript modules into CommonJS is using import(pathToFile).then(module => { }).
Support for require()
If you want to support require() for your package, you must provide a CommonJS module.
Here's a functioning example that demonstrates when and how to utilize require() or import(). There are some small differences how import() of a CommonJS module works compared to a ECMAScript Module. Especially that only the default property on the module object is available, when import() is used on a CommonJS file that exported something with module.exports.
index.js which imports different module types (from the demo above):(In case the stackblitz demo will be deleted:)
// executed as CommonJS module
console.time('');
import('./lib/example.cjs').then(({ default: example }) => {
console.timeLog('', 'import cjs', example() == 'Foo'); // true
});
import('./lib/index.mjs').then(({ example }) => {
console.timeLog('', 'import mjs', example() == 'Foo'); // true
});
try {
const example = require('./lib/example.cjs');
console.timeLog('', 'require cjs', example() == 'Foo'); // true
} catch (e) {
console.timeLog('', 'require cjs', '\n' + e.message);
}
try {
const example = require('./lib/index.mjs');
console.timeLog('', 'require mjs', example() == 'Foo');
} catch (e) {
console.timeLog('', 'require mjs', '\n' + e.message); // Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/lib/index.mjs not supported.
}
lib/example.cjs
module.exports = function example() {
return 'Foo';
};
lib/index.mjs
import example from './example.cjs';
export { example };
export default example;
Conditional Export for Packages
A conditional export can be supplied for packages to support require(), for example in a case where the CommonJS require() is no longer supported by your package. Refer to this link for more information.
The "exports" field allows defining the entry points of a package when imported by name loaded either via a node_modules lookup or a self-reference to its own name. It is supported in Node.js 12+ as an alternative to the "main" that can support defining subpath exports and conditional exports while encapsulating internal unexported modules.
package.json (example from the nodejs docs)
{
"exports": {
"import": "./index-import.js",
"require": "./index-require.cjs"
},
"type": "module"
}
If so, you have to provide two scripts: one for the CommonJS ("require": "filename") and one for the ECMAScript module ("import": "filename").
While index-require.js must provide the script via exports = ... or module.exports = ..., index-import.js must provide the script with export default.
Keyword Usage
You can only use specific keywords depending on the files module type.
CommonJS Modules
module.exports is used to define the values that a module exports and makes available for other modules to require. It can be set to any value, including an object, function, or a simple data type like a string or number.
exports, module
If you use them inside an ECMAScript module you'll get an undefined Error.
require()
require() inside ECMAScript modules is possible, but you have to use a workaround as mentioned in this answer or take a look at the docs for module.createRequire(fileName):
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
// sibling-module.js is a CommonJS module.
const siblingModule = require('./sibling-module');
If you call require() from within a CommonJS on an ECMAScript module, it throws a not supported Error:
Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/script.mjs not supported.
With a more detailed error message depending on the situation:
Instead change the require of script.mjs in /path/to/app.js to a dynamic
import() which is available in all CommonJS modules.
Or:
/path/to/script.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead rename /path/to/script.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in /path/to/package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).
ECMAScript Moduls (ESM)
export default is used to export a single value as the default export of a module. This allows for a more concise way to import values, as the import statement can omit the curly braces when importing the default export.
Named exports, on the other hand, allow multiple values to be exported from a module. Named exports use the export keyword followed by an identifier and a value. (export const foo = "bar")
import ... from ...
It can handle CommonJS files and interprets them as if you would've used require().
Example based on express:
import express, { Route, Router } from 'express'; // EJS
// is similar to:
var express = require("express"), { Route, Router } = express; // CJS
Both CommonJS and ECMAScript modules support the import() function, but the returned object can have more properties on ESM files.
Summary:
CJS modules don't need to be converted to ESM, as they can be imported into ESM using the import ... from ... syntax without any modifications to the CJS module. However, it's advisable to write new modules using ECMAScript Module syntax, as it is the standard for both web and server-side applications and enables seamless use of the same code on both sides the browser/client-side and node/server-side.
Specifications
Additionally, I find this article on CommonJS vs. ES modules in Node.js from logrocket.com to be very informative. It delves into the pros and cons of ECMAScript compared to CommonJS in more depth.
Links:
MDN: import()
NodeJS.org: Difference between ECMAScript modules and CommonJS modules
There are two main scenarios:
1. Your package is written using CommonJS (CJS) module loading
This means your package uses require() to load dependencies. For this kind of package no special work is needed to support loading the package in both ES and CJS modules. ES modules are able to load CJS modules via the import statement, with the minor caveat that only default-import syntax is supported. And CJS modules are able to load other CJS modules via the require() function. So both ES modules and CJS modules are able to load CJS modules.
2. Your package is written using ES module loading
This means your package uses import to load dependencies. But don't be fooled - sometimes, especially when using TypeScript, you may be writing import in your code, but it's getting compiled to require() behind the scenes.
Unfortunately, CommonJS modules do not support loading ES modules except (in Node.js) by using the import() function (which is a bit painful and not a great solution).
In order to support CommonJS in this case, your best bet is to transpile your package into a CommonJS module, and ship both CommonJS and ESM versions of your package.
I do this in a number of my own packages mostly by using Rollup, which makes it relatively easy.
The basic concept is this:
Write your package as an ES module.
Install rollup: npm i -D rollup
Run npx rollup index.js --file index.cjs --format cjs to convert your code into a CJS module.
Export both from your package.json:
{
"name": "my-package",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"exports": {
"import": "./index.js",
"require": "./index.cjs"
}
}
This way, the CJS module loader knows to load your index.cjs file, while the ESM loader knows to load your index.js file, and both are happy.

Import es module file from CommonJS TypeScript

I'm trying to import a package where its package.json specifies "type": "module". I saw Can Typescript import CommonJS Modules?, but its solution (--allowJs) does not work for me.
I've tried setting various values in my tsconfig.json for module, moduleResolution, allow allowJs. Both esModuleInteropt and allowSyntheticDefaultImports are set to true.
I've tried using import ... from, import(...).then(), import ... = require(...), and renaming my index.js and the package's index.js to index.cjs. They all result in the same error when I run in node:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: E:\src\...\node_modules\stringify-object\index.js
require() of ES modules is not supported.
require() of E:\src\...\node_modules\stringify-object\index.js from E:\src\...\index.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename E:\src\...\node_modules\stringify-object\index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from E:\src\...\node_modules\stringify-object\package.json.
I tried removing "type": "module" from the packages's package.json, but it relies on other packages with "type": "module".
The last time I tried to figure this out, I gave up and went with a different package. This really doesn't seem like it should be hard to do.
I found what I'd consider a workaround. If I set "module": "ESNext" in tsconfig.json and rename the output to index.mjs, it works. It's not particularly elegant, though. There just must be a better way.
I'd be happy to accept a different, better answer.
As pointed out in the comment, setting "type": "module" in my project's package.json makes it so I do not need to rename index.js to index.mjs.

Cant export/import functions js

Im using node.js and am trying to import functions from another script.
The requested module './Module.js' does not provide an export named 'default'
Is the error i recieved when trying to use
module.exports = randomfunction;
For context, i used
import randomfunction from "./module.js";
Your problem is because Node.js thinks Module.js is a ES module but you have written it as a CommonJS module.
You have to tell Node.js if your module is CommonJS (uses module.exports and require) or ES (uses import and export).
You set the module type using a file extension:
.cjs for CommonJS
.mjs for ES modules
.js for the package default
The package default is determined by the type property. If you specify "type": "module" then .js files will be treated as ES modules. If you don't specify that then it will treat them as CommonJS modules.
You appear to have set "type": "module" so you can use import in your .js files.
This means you have to rename your CommonJS module:
import randomfunction from "./module.cjs";
or
Rewrite your CommonJS module to be an ES module:
export default randomfunction;

How to move to ESM

I am using Egg framework for my NodeJs(v14.15.4) application
I want to use latest version of p-debounce library, the package is now pure ESM, Instead of const pDebounce = require('p-debounce') I must use import pDebounce from 'p-debounce' that not works on EggJs
If I use import
(node:10636) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
test.js:5
import pDebounce from 'p-debounce'
^^^^^^
SyntaxError: Cannot use import statement outside a module
If I use require
internal/modules/cjs/loader.js:1080
throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
^
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: .\node_modules\p-debounce\index.js
require() of ES modules is not supported.
require() of .\node_modules\p-debounce\index.js from .\test.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from .\node_modules\p-debounce\package.json.
If I add "type": "module" in the package.json
const path = require('path');
^
ReferenceError: require is not defined
I have many require in my application and not want to change all to import at the moment
What is "type": "module" in the package.json ?
How can I fix the error?
You seem to be overlooking a particular point in the changelog you linked:
If you cannot move to ESM yet, don't upgrade to this version.
As you've pointed out, your application has already progressed significantly, and you have many require that you do not want to change at the moment. There is no need to upgrade to the latest version of p-debounce at this time, according to your needs.
When you do decide to migrate to ESM format, in order to upgrade individual files one at a time, you can use the .mjs extension to treat the code as an ECMAScript module.
ES modules are the future.
There is a way around in ES modules to require a common.js module. Using createRequire
WORKING EXEMPLE
axios-curlirize is ES module and axios support both.
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const axios = require('axios');
import curlirize from 'axios-curlirize';
curlirize(axios);
const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos/1');
console.log(data)
Using ES modules you can use top-level await without function.

Relative vs. non-relative module import for custom modules

Update[09/12/2017 16:58 EST]
Added reason why I hesitate to use the natively-supported non-relative import with my own modules to the bottom of this question.
Update[09/12/2017 12:58 EST]:
Per request, I made the file structure below reflect my actual use case. Which is a nested view module requesting a utility module somewhere up the directory tree.
Per request
In both TypeScript and ES6, one can import custom module by
// Relative
import { numberUtil } from './number_util';
// OR
// Non-relative
import { numberUtil } from 'number_util';
And according to TypeScript docs (link), one should:
... use
relative imports for your own modules that are guaranteed to maintain
their relative location at runtime.
... Use non-relative paths when importing any
of your external dependencies.
My problem here is that I have a project structure like this:
project/
|--utils
| |--number_util.ts
|--views
| |--article_page
| |-- editor_view
| |--editor_text_area.ts
And when I include utils/number_util inside my editor_text_area module, the import statement looks like:
import { numberUtil } from './../../../utils/number_util';
Which is long and not readable and, worst of all, difficult to maintain: whenever I need to move editor_text_area, I will have to update each these relative paths, when in the meantime I can just use the non-relative way of
import { numberUtil } from 'utils/number_util';
Does anyone have any suggestions on how best to do module imports to achieve the highest readability and maintainability?
But using the non-relative way poses a problem (other than that it is not recommended by official docs): what if I installed an npm module that has the same name with the module I'm importing? On that note, it is not as safe as the uglier alternative mentioned above.
Depending on your project tooling and structure, you have some options.
A) You could publish part of your dependencies as stand-alone modules, perhaps in a private registry. You can then install them with npm and require them like any other external dependency.
B) Many module systems support some sort of path mapping. The vue-js webpack template uses webpack's alias feature to set # to the source code root. TypeScript supports path mapping too. After you introduce that mapping you can use
import { numberUtil } from '#/utils/number_util';
This approach basically introduces a private namespace for your modules.
It is safe, in that you could only ever shadow an npm module with the name # which is an invalid name and therefore cannot exist.
For your example, you would have to have these entries in your compilerOptions:
"baseUrl": ".",
"paths": {
"#/*": ["*"]
}
Or if you only want to import modules from utils, you could change the mapping to "#/*": ["utils/*"] and import using '#/number_util'.
C) Another thing to consider is to improve your project structure. Depending on your actual project, it might make sense to apply the facade pattern at one point or another. Maybe inject the dependency into editor_text_area instead of letting it import it itself.
you can add "baseUrl": "./src", to you tsconfig.json,
then you can import * as utils from 'utils' to import ./src/utils/index.ts

Categories