webpack/webpack

`browser` vs `module` fields in `package.json`

alex35mil opened this issue ยท 24 comments

Do you want to request a feature or report a bug?
I don't think it's a bug, but confusing and inefficient behavior.

What is the current behavior?
When module's package.json contains browser, module & main fields, webpack is bundling browser build by default.

What is the expected behavior?
The key point of the module build is to optimize compiled bundle. And even if a package has hq ES build, it's not being used by default in favor of legacy UMD/IIFE builds. So expected behavior is to use module build by default.

sokra commented

We may need a field module-browser...

Another way to solve it nodejs/node-eps#60. Not advocating this approach, just want to let you know.

UPD: there is also esnext prposal. Started to collect all those undocumented package.json fields in small repository

@alexfedoseev found solution, package should be in this form to work properly with wepback

{
  "name": "main-module-browser",
  "main": "dist/index.js",
  "module": "dist/index.esm.js",
  "browser": {
    "./dist/index.js": "./dist/index.browser.js",
    "./dist/index.esm.js": "./dist/index.browser.esm.js"
  }
}

Full example https://github.com/stereobooster/main-module-browser-test

Just have similiar problem and happen to found this issue.
@stereobooster What is the difference between index.js and index.browser.js? Is it umd and cjs or something else?

@DrSensor here is full spec https://github.com/defunctzombie/package-browser-field-spec

it doesn't specify types of the packages. One thing that matters is if code uses browser-specific (example: Dom) or server-specific (example: filesystem) features.

Does this mean that currently webpack's order of importing is browser > module > main?

I would also prefer module > browser > main.

I would also prefer module > browser > main.

That would break things. What happens when you need to load WebCrypto for the browser and node's crypto for node?

Or similar naunced APIs - atob, Buffer, etc.

@solderjs I only define module and main fields on all my package.json files.

module:
It works perfect, because webpack will always use module, which I point towards files bundled with esm (ES Module) format (eg. export default something).

main:
My main fields point to commonjs bundled files, which are automatically chosen when someone uses my package in a nodeJS environment I believe.

browser:
I'm not sure why the browser field exists in the first place... ๐Ÿง

conclusion:
I was always under the impression that I was doing to right... But now I don't know what's right and what's less right, and how I should best do it anymore. ๐Ÿ˜…

@mesqueeb for code that can not work in a browser (using node-only APIs, for example), the browser field lets bundlers automatically select an alternative implementation (using browser-only APIs, for example). When writing universal code, you never need the browser field.

Note that node itself will not have any "module" field, so anyone currently using that field is already incurring technical debt.

@ljharb thanks for the insight.

I do however feel that it's important to use the "module" field. Because, exactly as you point out:

Node itself doesn't (use) the "module" field

since Node uses the "main" field, we should export our libraries with a commonJS export method in the file we point to in the "main" field. Because Node has difficulties reading ES Module type exports (export default).

So the reason they added the "module" field in package.json is for us devs to be able to also use the ES6 import export method directly without having to rely on commonJS, while still keeping backwards compatibility support to Node by setting a commonJS exported index file on "main".

I hope you understand what I mean. ๐Ÿ™ƒ

I've a question: If I've a node package that has module & main fields and I've an app using this package while bundling to target node.

  • Does using "require" from the package in my app goes to module or main?

  • Does using "import" from the package in my app goes to module always?

Or it's always module if exist and then main? Because I know that webpack can "require" an ESM module (or maybe I'm wrong).

sokra commented

Always module

Thanks @sokra! ๐Ÿ‘๐Ÿป

CxRes commented

What is the guidance with node now providing an exports field? Do we still continue to use module or browser as previous or do we use them as a properties of exports?

AFAICT webpack@5 will use exports if it is specified and fall back to module and browser otherwise, webpack@4 will continue to use module and browser, and for the time being there are no plans in rollup to support exports.

So I think for quite a while package maintainers will have to support both if they do want to make use of exports but don't want to break a considerable amount of tooling.

CxRes commented

@ctavan Thanks for the answer. I have to say though, this esm transition situation really really sucks! Emotions are flaring up everywhere (not just with this issue, like on the other thread you link) and no end to the confusion for developers in sight!!!!!!

endel commented

Hi everyone, what about prioritizing the "browser" field on webpack in case the filename is .mjs?

Please give it a read to this thread - this seems to be a generalized problem across all modern ESM bundlers: vitejs/vite#1154. Maybe this is a good time to get a consensus to define the best approach for this problem?

Here is a repository I've prepared demonstrating the problem: https://github.com/endel/esm-browser-node

Not all browsers that people support, support ESM.

The "browser" field speaks to a content swap. It has nothing to do with format specification. If it did, then all browser entries would most likely be CommonJS (re browserify) but UMD files emerged as a convention.

There should definitely be some detection as to what the browser entry is pointing at. The .mjs extension should be enough evidence โ€“ or exports[key].browser if webpack is parsing that already.

But again, browser is about content... either through polyfills or implementation changes, the "browser" entry is the browser/non-Node.js variant of XYZ module behavior.

I recognize a lot of folks just want ES modules to work in their builds (at this point there's few in the JS ecosystem who don't feel the ES modules transition pain), but using module over browser is an incomplete solution. Really what you want is the ability to specify ES and CommonJS builds for both Node.js and Browser environments.

It is really important that Webpack retain the ability to use a different set of built files (within the imported package) as compared to server side (Node.js, aka main and module).

Personally I think the best option is to introduce browser-module. Preference order would be:

  1. browser-module
  2. browser
  3. module
  4. main

If one of the options doesn't exist, fall to the next.

They can be combined, which is actually parts of the exports design intention, and I think webpack supports this already:

{
  "exports": {
    "browser": {
      "import": "./browser.mjs",
      "require": "./browser.js"
    },
    // ...
}

I think we can close it in favor exports, also good answer #4674 (comment) for legacy projects, feel free to feedback

And docs https://webpack.js.org/guides/package-exports/#providing-different-versions-depending-on-target-environment, anyway feel free to improve it

I think we can close it in favor exports, also good answer #4674 (comment) for legacy projects, feel free to feedback

And docs https://webpack.js.org/guides/package-exports/#providing-different-versions-depending-on-target-environment, anyway feel free to improve it

Hi ! I don't get your snippet, how does it work ? It choose randomly ?