node-fetch / node-fetch

A light-weight module that brings the Fetch API to Node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

node-fetch 2.1.2 broken with webpack (node-fetch 1.7.3 works)

ilanc opened this issue · comments

I put together a small repo: https://github.com/ilanc/node-fetch-webpack

Issue

With node-fetch 2.1.2 you have to use the ES6 module import form:

import fetch from 'node-fetch'

If you use the commonjs form:

const fetch = require('node-fetch')

then the resulting bundle won't work properly:

node-fetch-webpack/bundle.js:103
fetch('https://google.com/').then(res => res.text()).then(body => console.log('success'));
^

TypeError: fetch is not a function
...

Workaround

Seems to work with both module load formats in node-fetch 1.7.3

If you webpack node-fetch, you literally just get this:

https://github.com/bitinn/node-fetch/blob/master/browser.js

It would be great for people to inform us: what does webpack want? Why do we keep running into this sort of problems with webpack?

ref: https://github.com/bitinn/node-fetch/search?q=webpack&type=Issues

Hi @bitinn you need to add the target: 'node' to the webpack config (e.g. see https://github.com/ilanc/node-fetch-webpack/blob/master/webpack.config.js) otherwise it defaults to webpacking for the web (i.e. a browser). There are also babel settings to consider as well (e.g. babel-preset-env, .babelrc).

I tried looking through the node-fetch issues before filing this and didn't see anyone with the same problem.

I can have a look at this tomorrow if you want a pull request. I thought I'd post here so that people who are googling for answers can find it. It seems to be something that broke recently because it works in 1.7.3.

Can you first explain what exactly you are doing with webpack if you are NOT bundling it for frontend?

Your issue has no actual error from webpack, so how are we going to troubleshoot this, as node-fetch should have no knowledge of the bundler you want to use.

All of the same benefits that you get from bundling in frontend dev apply equally to writing console and server-side code i.e. you can use the latest javascript language features without having to worry about the environment within which your code is deployed and you can ship a complete app.bundle.js which requires no npm install. Try doing some AWS Lambda dev if you want to see the benefits of using a bundler for node code.

I realise that maintaining a library must be a huge headache given the state of javascript ecosystem. I'll see what I can do today to (1) fix the problem (2) come up with a unit test. However please appreciate that (1) is more my concern than (2) and I already have a workaround for (1) which is to use an older version of your library (i.e. v 1.7.3)

Hmmm the problem is the mixing of module types. 1.7.3 has commonjs and 2.1.2 has es6 modules:

// 1.7.3
module.exports = Fetch;

// 2.1.2
export default function fetch(url, opts) {

Seems like this is an evolving area of node development - see here and here. I'm just going to stick on 1.7.3 for now - I thought there would be a quicker fix.

I am going to close this as we still don't know why webpack doesn't take export default function, which it should.

If webpack expect ALL backend modules to add their config file then they are badly mistaken (which I believe they are smart enough to avoid).

Your issue is more on how to use webpack properly, than node-fetch having a "bug".

Of course, if people got a good suggestion on why and how to mitigate this, then we may at least update our readme to give some pointers.

I thought about this at lunch. There's another workaround:

// use this
const fetch = require('node-fetch').default
// instead of this
const fetch = require('node-fetch')

This is probably the best workaround because it supports:

  1. node-fetch 2.1.2
  2. commonjs code without bundling i.e. node app.js
  3. commonjs code with webpack bundling

see ilanc/node-fetch-webpack@3632977

Came across this issue, and found the corresponding discussion on the webpack issue tracker: webpack/webpack#4742

It has some workarounds for library authors that might be relevant. I don't have enough context to know if a workaround on the node-fetch side is the right way to go (or which workaround to use), so I didn't make a PR.

@TimothyJones Thx for linking this issue.

But it's not an easy fix to just dump module field, the source of problem is webpack, not node-fetch, to be honest.

const fetch = require('node-fetch').default

Is not a workaround, it is what export default means in commonJS terms. And, on the contrary const fetch = require('node-fetch') would translate to import * as fetch from "node-fetch" in ESM terms. the problem is not webpacks translation, (every transpiler will transpile export default X into module.exports.default = X, so importing with require().default is correct). The problem is assuming that require === import, which it's not, it's not syntactic sugar, they literally mean different things.

If @ilanc is bundling anyway, he could just use import instead of require?

Edit; note that module.exports = X (which would enable const X = require()) does not have an equivalent in ESM! Which one might love or hate, but is not something webpack can be blamed for.

The problem is that Webpack favors modules over CommonJS by default. This is different from Node which results in mixing import and require.

node-fetch provides 2 flavors: CommonJS, and Modules: https://github.com/bitinn/node-fetch/blob/master/package.json#L5-L7

When Node sees require("node-fetch), it'll pick the main config while Webpack picks module. You can reconfigure this in webpack.config.js:

    resolve: {
        mainFields: ["main"],
    },

This still doesn't work though, because main references lib/index and the first extension that Webpack uses is .mjs. That can be changed as well:

    resolve: {
        extensions: [".js"],
        mainFields: ["main"],
    },

Now Webpack uses the same resolve algorithm as Node, and will pick up the CommonJS variant of node-fetch. Webpack does warn about the optional encoding module. If you prefer you can hide that warning using this config in webpack.config.js:

    stats: {
        warningsFilter: "Module not found: Error: Can't resolve 'encoding'"
    },

import fetch from 'node-fetch' worked for me. Please update the readme.

v3 may have fixed this since we're using Pika which should have specifically fixed this problem.

I put together a small repo: https://github.com/ilanc/node-fetch-webpack

Issue

With node-fetch 2.1.2 you have to use the ES6 module import form:

import fetch from 'node-fetch'

If you use the commonjs form:

const fetch = require('node-fetch')

then the resulting bundle won't work properly:

node-fetch-webpack/bundle.js:103
fetch('https://google.com/').then(res => res.text()).then(body => console.log('success'));
^

TypeError: fetch is not a function
...

Workaround

Seems to work with both module load formats in node-fetch 1.7.3

save my day, thanks

commented

if webpack is used in the project, global.fetch won't work. Add following library in app root level.( recommended for serverless-webpack / any other web pack for lambda codestart )

require('cross-fetch/polyfill');

If you're using this within a node_module you're maintaining, falling back to require("fetch").default if it's not undefined may be a good approach for you

https://github.com/Flagsmith/flagsmith-nodejs-client/pull/21/files#diff-537b33488a6027f3f9f309df24d9af4c6819c635cdae1d650245a259e4f8e335R1