Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading node-fetch@3 in CJS and ESM #1279

Closed
jimmywarting opened this issue Sep 8, 2021 · 10 comments
Closed

Loading node-fetch@3 in CJS and ESM #1279

jimmywarting opened this issue Sep 8, 2021 · 10 comments
Labels

Comments

@jimmywarting
Copy link
Collaborator

jimmywarting commented Sep 8, 2021

Pure ESM package

node-fetch is an ESM-only module - you are not able to import it with require(). You don't necessary have to convert your hole project to ESM just b/c we did it. There are ways to load ESM modules in cjs package too (more about this in FAQ). You can also stay with the v2 branch - We will keep updating v2 with bug/security issues. But not so much with new features...

This means you have the following choices:

  1. Use ESM yourself. (preferred)
    Use import fetch from 'node-fetch' instead of const fetch = require('node-fetch') to import the package. You also need to put "type": "module" in your package.json and more. Follow the below guide.
  2. you could use async import(…) from CommonJS instead of require(…). This is supported in CommonJS also
  3. Stay on the existing version of the package until you can move to ESM.

You also need to make sure you're on the latest minor version of Node.js. At minimum Node.js 12.20, 14.14, or 16.0.

I would strongly recommend moving to ESM. ESM can still import CommonJS packages, but CommonJS packages cannot import ESM packages synchronously.

ESM is natively supported by Node.js 12.17 and later.

FAQ


Have questions about another tools? look trough other issues or ask new ones
We will continue to update this with new solutions from other accepted answers

@node-fetch node-fetch locked as too heated and limited conversation to collaborators Sep 8, 2021
@jimmywarting jimmywarting pinned this issue Sep 8, 2021
@jimmywarting jimmywarting changed the title Loading node-fetch@3 in cjs/esm Loading node-fetch@3 in CJS and ESM Sep 8, 2021
@jimmywarting
Copy link
Collaborator Author

How can I move my CommonJS project to ESM?

  • Add "type": "module" to your package.json.
  • Replace "main": "index.js" with "exports": "./index.js" in your package.json.
  • Update the "engines" field in package.json to Node.js 12: "node": "^12.20.0 || >=14.13.1".
  • Remove 'use strict'; from all JavaScript files.
  • Replace all require()/module.export with import/export.
  • Use only full relative file paths for imports: import x from '.';import x from './index.js';.
  • If you have a TypeScript type definition (for example, index.d.ts), update it to use ESM imports/exports.
  • Optional but recommended, use the node: protocol for imports.

@jimmywarting
Copy link
Collaborator Author

jimmywarting commented Sep 8, 2021

Can I import ESM packages in my CJS project?

Yes, but only asynchronous. The async import('node-fetch') function is supported in CJS contexts too (in v12.17).
if you would like you can create a wrapper function:

const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));

(the next request won't re-import node-fetch cuz modules gets cached)
This can make the Node process boot up faster and only lazy loads the node-fetch when it's needed

Another way to preload node-fetch could be to:

const fetchPromise = import('node-fetch').then(mod => mod.default)
const fetch = (...args) => fetchPromise.then(fetch => fetch(...args))

if your bundler rewrites your async import to require: then have a look at:
TypeScript/WebPack rewrites my async imports to require in CJS projects

@jimmywarting
Copy link
Collaborator Author

jimmywarting commented Sep 8, 2021

How do I get typing support if I'm using async import?

For TypeScript users who are only after the Types, you can do: Type-Only Imports

import type fetch, { Request } from 'node-fetch' 

(this will not import or transpile anything - this typing annotation will just be discarded)

for JSDoc users, this can work:

/**
 * @typedef {import('node-fetch').Request} Request
 * @typedef {import('node-fetch').Response} Response
 */

@jimmywarting
Copy link
Collaborator Author

Can I import ESM packages in my TypeScript project?

Yes, but you need to convert your project to output ESM. See below.

@jimmywarting
Copy link
Collaborator Author

How can I make my TypeScript project output ESM?

  • Add "type": "module" to your package.json.
  • Replace "main": "index.js" with "exports": "./index.js" in your package.json.
  • Update the "engines" field in package.json to Node.js 12: "node": "^12.20.0 || >=14.13.1".
  • Add "module": "ES2020" to your tsconfig.json.
  • Use only full relative file paths for imports: import x from '.';import x from './index.js';.
  • Remove namespace usage and use export instead.
  • Optional but recommended, use the node: protocol for imports.
  • You must use a .js extension in relative imports even though you're importing .ts files.

If you use ts-node, follow this guide.

@jimmywarting
Copy link
Collaborator Author

jimmywarting commented Sep 8, 2021

I'm having problems with ESM and TypeScript

If you have decided to make your project ESM ("type": "module" in your package.json), make sure you have "module": "ES2020" in your tsconfig.json and that all your import statements to local files use the .js extension, not .ts or no extension.

@jimmywarting
Copy link
Collaborator Author

jimmywarting commented Sep 8, 2021

TypeScript/WebPack/esbuild/rollup rewrites my async import() to require() in CJS projects

If you have something like this async import:

const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args))

But it will be builded into this require statement

const fetch = (...args) => Promise.resolve().then(() => __importStar(require('node-fetch'))).then(({ default: fetch }) => fetch(...args))

Then you can do:

const _importDynamic = new Function('modulePath', 'return import(modulePath)')

async function fetch(...args) {
  const {default: fetch} = await _importDynamic('node-fetch'))
  return fetch(...args))
}

which is used to an encapsulated addition for loading ESM via dynamic import or require for non-ESM code

If you want this to be fixed in TypeScript, vote for this issue to be fixed:
microsoft/TypeScript#43329


An alternative solution could be to include a file that typescript compiler won't touch, then you need to add allowJs: true (Maybe you also need to add a entry point to exclude)

// fetch.js
module.export = const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args))

// tsconfig.json
{ "allowJs": true, "checkJs": true }

// main.ts
import fetch from './fetch.js'

@jimmywarting
Copy link
Collaborator Author

I'm having problems with ESM and ts-node

Follow this guide and ensure you are on the latest version of ts-node.

@jimmywarting
Copy link
Collaborator Author

jimmywarting commented Sep 20, 2021

Can I run a ESM file/project without a package.json?

Yes. Even if you don't have a package.json and no "type": "module" then it's possible to use the --input-type flag or change the filename to .esm

Node.js treats JavaScript code as CommonJS modules by default. Authors can tell Node.js to treat JavaScript code as ECMAScript modules via the .mjs file extension, the package.json "type" field, or the --input-type flag. See Modules: Packages for more details.

https://nodejs.org/api/packages.html#packages_determining_module_system

@jimmywarting
Copy link
Collaborator Author

jimmywarting commented Oct 12, 2022

if typescript is rewriting your import statement to require()
then you could also try

const _importDynamic = new Function('modulePath', 'return import(modulePath)')

async function fetch(...args) {
  const {default: fetch} = await _importDynamic('node-fetch'))
  return fetch(...args))
}

or just update to latest NodeJS version (v18+) that has fetch built in

You can also install node-fetch v2 that is still in cjs format

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

1 participant