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

module gets prioritised over main #5756

Closed
catamphetamine opened this issue Oct 4, 2017 · 29 comments
Closed

module gets prioritised over main #5756

catamphetamine opened this issue Oct 4, 2017 · 29 comments

Comments

@catamphetamine
Copy link

Do you want to request a feature or report a bug?

bug

What is the current behavior?

module entry in package.json gets prioritised over the main entry (even on Node.js).

Consider deepmerge package: https://unpkg.com/[email protected]/dist/es.js

This:

...
export default deepmerge_1;

gets transpiled into:

/* 2043 */
/*!********************************************************************!*\
  !*** /Users/donaldtrump/work/JITU/node_modules/deepmerge/dist/es.js ***!
  \********************************************************************/
/*! exports provided: default */
/*! all exports used */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
....

var deepmerge_1 = deepmerge;

/* harmony default export */ __webpack_exports__["default"] = (deepmerge_1);

/***/ }),

And therefore another module using it and not suspecting anything crashes:
https://unpkg.com/[email protected]/src/Client.js

var deepmerge = require('deepmerge');

  this.options = deepmerge(defaultOptions, options || {}, true);

Because it naturally expects the plain and simple CommonJS export which is supposed to be a function.

Webpack tries to be overly smart in this case resulting in an incompatibility.

If the current behavior is a bug, please provide the steps to reproduce.

Create an application using rest-facade and it will throw "deepmerge is not a function" (because it's an object now for no reason).

What is the expected behavior?

Not throwing the aforementioned error

If this is a feature request, what is motivation or use case for changing the behavior?

Please mention other relevant information such as the browser version, Node.js version, webpack version and Operating System.

@sokra
Copy link
Member

sokra commented Oct 8, 2017

I don't think webpack behaves wrong here. If a module field is provided this should be preferred over the main field for every system that support ESM. It's expected from the package to export the same API via CommonJS and ESM (which is not the case for deepmerge).

@catamphetamine
Copy link
Author

catamphetamine commented Oct 8, 2017

So what you're saying is that all Node.js modules exporting module.exports = ... won't work in Webpack which is about 99.999% of all npm modules.

@sokra
Copy link
Member

sokra commented Oct 9, 2017

no that works fine.

The problem is specifying module and main in a package with different APIs.

Example:

// index.js
exports.value = "a";
// index.mjs
export const value = "b";
{
  "main": "./index",
  "module": "./index.mjs"
}
// cjs.js
const { value } = require("module");
console.log(value);

In node.js without ESM support this will log a.

In webpack this will log b.


=> Export the same API for your packages when providing ESM and CJS!

@catamphetamine
Copy link
Author

What about this then:

// index.common.js
module.exports = function convert(from, to) {}
// index.es6.js
export default function convert(from, to) {}
{
  "main": "./index.common",
  "module": "./index.es6"
}
// application.js
var convert = require('convert')

// Throws "convert is not a function"
convert('a', 'b')

// Outputs "{ default: function convert() {} }"
console.log(convert)

The result is the application is broken because Webpack tried to be overly smart and chose module over main which resulted in wrong import.

@haggholm
Copy link
Contributor

One problem is that different modules in the same build may import the same package in different ways. This is currently causing us problems in a project that uses moment and react-datetime.

Because we use ES6 imports, moment has already been imported by looking at the module: field. However, react-datetime imports it using require(). It seems that (because it’s the same import path, import 'moment' vs. require('moment')) the latter also gets the ES6 module—even though DateTime.js is a CommonJS module.

@catamphetamine
Copy link
Author

catamphetamine commented Oct 10, 2017

Perhaps all modules loaded during Webpack build should be marked by the module system they're using: either CommonJS or ES6. Then, if a CommonJS-marked module require()s something then Webpack must look into the main field first.

@pacey
Copy link

pacey commented Feb 1, 2018

Is there a fix or workaround for this? I have the issue where deepmerge is coming in as transitive dependency so I can't change the way that library is importing/requiring it.

@TehShrike
Copy link

Related: #6584

@skhamoud
Copy link

@pacey can you update on how to deal with this . I have no control over it whatsover .

@pacey
Copy link

pacey commented Jun 22, 2018

@skhamoud I couldn't find a way around it, so I actually have up on using Webpack for that build

@skhamoud
Copy link

skhamoud commented Jun 22, 2018

Thanks @pacey , unfortunately I don't have that option and the alias fix suggestion from deepmerge docs didn't work either , thanks anyway .

@srolel
Copy link

srolel commented Jul 6, 2018

Perhaps a way to override this would be useful?

@MarkHalls
Copy link

I was having this exact problem. I found that I could change it to prioritize using main instead of module with the following config option:
resolve: { mainFields: ["main", "module"] },

Unfortunately this solution may cause other dependencies to break if they want module instead of main. Your mileage may vary.

@Antonio-Laguna
Copy link

Antonio-Laguna commented Jan 10, 2019

What's the way ™ to go with this?

We have a lot of libraries that export module in ES6 and main transpiled but we end up having stuff not being transpiled then. Taking a couple of famous React modules such as:

https://github.com/react-bootstrap/react-bootstrap/blob/master/package.json

or

https://github.com/react-toolbox/react-toolbox/blob/dev/package.json

I can see that I'm not going against convention here (nor the OP) yet I'm surprised not many people run into this. React needs to run both on the browser and node if SSR is in place so I'm not sure how to proceed here.

Example library here: https://github.com/firstandthird/domodule/blob/master/package.json#L6

Both including node_modules on babel loader and doing the switch indicated on the above solution seem to go against every other thing which, again, surprises me.

@retorquere
Copy link

Can I configure webpack to only look at main? I'm loading transliteration and I currently have to do

import { transliterate } from 'transliteration/dist/node/src/node'

to prevent the umd-ified transliteration being loaded as specified in their 'package.json in the browser field (the umd-ified version does not work for me).

@haggholm
Copy link
Contributor

@retorquere I haven’t used it, but that seems to be what resolve.mainFields is for: https://webpack.js.org/configuration/resolve/#resolve-mainfields

@retorquere
Copy link

@haggholm when I set

  resolve: {
    extensions: ['.ts', '.js'],
    mainFields: [ 'main' ], // or [ 'main', 'module' ], same results
  },

and I do

import { transliterate } from 'transliteration'

(where transliteration has "main": "dist/node/src/node/index.js"
I get

    ERROR in Circular dependency detected:
    node_modules/inherits/inherits.js -> node_modules/util/util.js -> node_modules/inherits/inherits.js
    
    ERROR in Circular dependency detected:
    node_modules/util/util.js -> node_modules/inherits/inherits.js -> node_modules/util/util.js

Whereas without mainFields: [ 'main' ], and using import { transliterate } from 'transliteration/dist/node/src/node' (which should do the same), webpack proceeds without error.

@retorquere
Copy link

I've just updated all my packages, among which transliteration (2.0.4 -> 2.1.2) and webpack (4.28.4 -> 4.29.0), and the problem has disappeared, no mainFields required.

stefcameron added a commit to focus-trap/tabbable that referenced this issue Oct 7, 2020
Fixes #99

AFAICT, this seems to be the best solution to what __a lot__ of folks
in the community have been experiencing with ESM bundles, referenced
from a package's `module` entry, that aren't also transpiled down
to ES5 (which is technically in conflict with ESM, which is an ES6
feature).

The problem arises because this package states both `module` and
`main`, but Webpack defaults `mainFields` to
`['browser', 'module', 'main']`, in order. So it will always pick
`module` over `main` by default, because it doesn't understand
that the build's target is NOT a browser that supports ESM...

See webpack/webpack#5756
stefcameron added a commit to focus-trap/focus-trap that referenced this issue Oct 7, 2020
Fixes #172 again.

AFAICT, this seems to be the best solution to what __a lot__ of folks
in the community have been experiencing with ESM bundles, referenced
from a package's `module` entry, that aren't also transpiled down
to ES5 (which is technically in conflict with ESM, which is an ES6
feature).

The problem arises because this package states both `module` and
`main`, but Webpack defaults `mainFields` to
`['browser', 'module', 'main']`, in order. So it will always pick
`module` over `main` by default, because it doesn't understand
that the build's target is NOT a browser that supports ESM...

See webpack/webpack#5756
stefcameron added a commit to focus-trap/tabbable that referenced this issue Oct 7, 2020
Fixes #99

See webpack/webpack#5756

The initial approach was to add a `browser` target to package.json and
point it at the UMD build so that bundlers would pick that up instead of
the ESM build, since they (except for Rollup) tend to prefer `browser`
first, then `module` (ESM), then `main`.

But that would potentially affect tree-shaking, and we don't want to
hamper that optimizing process.

So this PR transpiles the code in the ESM bundle down to ES5, so that
it runs in IE9+ like the CJS and UMD bundles do, while keeping the
import/export ESM statements.
stefcameron added a commit to focus-trap/focus-trap that referenced this issue Oct 7, 2020
Fixes #172 again.

See webpack/webpack#5756

The initial approach was to add a `browser` target to package.json and
point it at the UMD build so that bundlers would pick that up instead of
the ESM build, since they (except for Rollup) tend to prefer `browser`
first, then `module` (ESM), then `main`.

But that would potentially affect tree-shaking, and we don't want to
hamper that optimizing process.

So this PR transpiles the code in the ESM bundle down to ES5, so that
it runs in IE9+ like the CJS and UMD bundles do, while keeping the
import/export ESM statements.
@rdsedmundo
Copy link

rdsedmundo commented Dec 16, 2020

I hit this problem and it took me a while to figure out what was going on.

The error that was thrown is not intuitive at all so you start investigating everything. That's what I got:

/node_modules/date-fns/esm/format/index.js:1
import isValid from '../isValid/index.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module

I use webpack for compiling my backend application and I have webpack-node-externals to avoid bundling the node_modules folder. The date-fns package has both module and main, so webpack chose to use the ESM version, and Node 12 wasn't able to run it. If I remove the externals configuration it works as expected, because webpack will then parse the ES6 import before outputting it to the bundle, but I don't want to package date-fns inside the bundle, so I tried the mainFields solution posted above.

But by using mainFields: ['main', 'module'] I started hitting another error:

/node_modules/date-fns/esm/_lib/toInteger/index.js:1
export default function toInteger(dirtyNumber) {
^^^^^^

SyntaxError: Unexpected token 'export'

So in the end I stuck with mainFields: ['main'] just and it works.

@alexander-akait
Copy link
Member

alexander-akait commented Feb 16, 2021

I think we can generate a warning for these cases, so developers be able to identify the problem faster

@vince1995
Copy link

I've solved that with this snippet:

    resolve: {
        alias: {
            "deepcopy": require.resolve("deepcopy")
        }
    }

@ghost
Copy link

ghost commented Aug 27, 2021

I'm dying, this is still a issue lol

@vankop
Copy link
Member

vankop commented Sep 15, 2021

Specifying mainFields is probably best work around for webpack@5. Nothing to do from webpack side..

Feel free to report new issues with repro.

@vankop vankop closed this as completed Sep 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests