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

Webpack not prioritizing the pkg.module for dependencies #5673

Closed
jshcrowthe opened this Issue Sep 13, 2017 · 10 comments

Comments

Projects
None yet
4 participants
@jshcrowthe
Copy link

jshcrowthe commented Sep 13, 2017

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

bug

What is the current behavior?

Webpack prioritizes the browser field in a package.json, over the module field (if both are passed)

See:

this.set("resolve.mainFields", "make", (options) => {
if(options.target === "web" || options.target === "webworker")
return ["browser", "module", "main"];
else
return ["module", "main"];
});

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

Created two gists, a module and a consumer. They are available here:

Repro Steps:

  1. git clone https://gist.github.com/jshcrowthe/64ec07274e4ac966b3e116078c586dfa
  2. cd 64ec07274e4ac966b3e116078c586dfa
  3. npm install
  4. npm start
  5. Open browser and navigate to webpack-dev-server URL
  6. Observe console logs, should look something like the following:

screen shot 2017-09-13 at 12 01 58 pm

What is the expected behavior?

Webpack should prioritize the pkg.module field over pkg.browser. Browserify also relies on the pkg.browser but does not have support for ES Modules. Seeing as webpack is able to more intelligently parse the ES Module bundles, it should prioritize that bundle if available.

NOTE: I am aware that you can change the resolve.mainFields to modify this however you'd like. I'm suggesting that the default behavior should be changed

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

n/a

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

  • Webpack: 3.5.6
  • Node.js: 8.4.0
  • NPM: 5.4.1
  • Yarn: 1.0.1
  • OS: macOS 10.12.6
@bfred-it

This comment has been minimized.

Copy link

bfred-it commented Sep 14, 2017

That appears to be correct. You can use the browser field as a map instead:

 {
   "name": "test-module",
   "version": "1.0.0",
   "description": "A test module exporting a `pkg.module`, `pkg.browser`, and `pkg.main`",
   "author": "Josh Crowther <jshcrowthe@gmail.com>",
   "license": "MIT",
   "main": "index.js",
-  "browser": "index.browser.js",
+  "browser": {
+    "index.js": "index.browser.js"
+  },
   "module": "index.esm.js",
   "jsnext:main": "dist/index.esm.js"
 }
@sokra

This comment has been minimized.

Copy link
Member

sokra commented Sep 14, 2017

The order of these fields is nowhere spec'ed. We chose this order. browser seem to be more specific for in-browser usage, while module is more generic for all targets.

@sokra sokra closed this Sep 14, 2017

@jshcrowthe

This comment has been minimized.

Copy link

jshcrowthe commented Sep 14, 2017

@sokra thanks for the response! Would you be willing to explain your rationale behind:

browser seem to be more specific for in-browser usage, while module is more generic for all targets.

Based on the current state of ES modules in Node (which I understand is still subject to change), I would disagree with that statement. Here's what I'm thinking (tell me if you think I'm crazy):

As a library author I have a bunch of environments that I'd want to target. For the context of this issue, the ones I'm thinking of are:

  1. Browser - minified standalone JS executable
  2. Browser - via CJS
  3. Browser - via ESM
  4. Node - via CJS
  5. Node - via ESM

Now we don't have enough specced fields to cover all of these entrypoints (the only specced one is main), so the community has come up with some non-standard ones (i.e. browser, module, jsnext:main, etc). As I'm sure you're already aware, different bundlers support these non-standard fields to different degrees, but the main, browser, and module fields seem to be agreed on by the big players (i.e. Browserify, Webpack, Rollup, am I missing more that I should be aware of?).

Library author: if my goal is to provide entrypoints for all of these environments, I can do so by doing the following:

  1. Browser(minified standalone JS executable) - This file would typically be thrown on a CDN and served alone. It is contained in the package, but not explicitly listed as an entrypoint in the package.json. Users can npm install our package and directly reference the node_modules dir via script tag. They can also access this binary via a CDN service like https://unpkg.com/

  2. Browser(CJS) - This file would be attached to the browser field of the package.json. Webpack and Browserify support this out of the box, and rollup supports it via plugin.

  3. Browser(ESM) - This file would be attached to the 'module' field of the package.json. Webpack and Rollup support this and are able to do static optimizations to the resulting bundles they generate. AFAICT, Browserify is still waiting for Node support to be resolved before they act (see: browserify/browserify#1186)

  4. Node(CJS) - This file would typically be attached to the main field of the package.json. Optionally, this can be omitted in favor of an index.js at the root of the package.

  5. Node(ESM) - Per this section of the ESM draft, you could accomplish this by doing the following:

    • By providing an entrypoint of the same name as your CJS node entrypoint but with the .mjs extension and changing the package.json main field as demonstrated below:
    {
         ...
         "main": "path/to/file" // previously: "main": "path/to/file.js"
    }
    • By providing an index.mjs, alongside a index.js, at the root of the package and omitting the main field entirely from the package.json.

Consumer of a library: I would think one would always want to get the ES Modules version of a library where available, and fallback to the other versions. We currently have a clean way to delineate between the two* and prioritizing the ES Module build benefits Webpack users by ultimately reducing bundle size, and allowing for auto chunking of dynamic imports (further reducing blocking bundle size).

*: pkg.browser/pkg.module for browser envs and main or index.js/index.mjs for Node

The hard part as a library author is, though Webpack supports the reprioritization of the resolve.mainFields, it is easier to expect all of my users to update Webpack, than it is to ask them all to add and maintain additional code in their Webpack config.

Thoughts?

EDIT: P.S. I'm a tad long-winded, thanks in advance for your understanding :)

@gauntface

This comment has been minimized.

Copy link

gauntface commented Sep 14, 2017

If I'm understanding this correctly, the issue is this:

{
    "main": "./node/index",
    "module": "./browser/index.mjs",
    "browser": "./build/browser/index.min.js",
}

In the above case, the node versions can be handled by "./node/index.{js,mjs}" (When node supports modules).

The module version of the browser code is covered off by "module" and would be beneficial for build tools like Rollup and WebPack to use as it gives the developer control over treeshaking, transpilation, minification etc.

The built browser code is used by tools like browserify who don't support modules and need to just pull in a chunk of code.

For users of WebPack, it makes sense to use module over browser to give developers control over how the javascript is passed etc.

Possible Problems:

  • module is actually node code and can't be used in a browser context (Where as the "browser" field can).
  • module requires certain plugins to be parsed and work.

My gut says most node modules exports a module field are doing so with the intention of a bundler working on it to produce a browser bundle, but it's a guess at best.

  • Does WebPack get used for node code?
  • Is there a general feel for how module is used in packages at the moment, i.e. is it node or browser environments?
  • Is it better / easier for WebPack to handle browser vs es2015 code in terms of parsing, transpiling, etc?

cc @addyosmani who probably has a gauge on some of this (and is wiser at WebPack than myself)

@sokra sokra reopened this Sep 15, 2017

@sokra

This comment has been minimized.

Copy link
Member

sokra commented Sep 15, 2017

Does WebPack get used for node code?

yes.

In my opinion it's wrong to publish browser-only code at the module field. The module field doesn't say anything about the target enviroment. It's like main but with the only difference that ESM is used.

The browser is a field which can be used to provide a browser-only replacement for the package.

If module has higher priority than browser people would publish browser-only code to module, but this will break non-browser builds with ESM support (like webpack compiling for node.js, electron, ...).

It's possible to publish different code for all targets with this package.json:

{
  "main": "lib/index",
  "module": "lib/index.mjs",
  "browser": {
    "./lib/index.js": "./lib/index.browser.js",
    "./lib/index.mjs": "./lib/index.browser.mjs"
  }
}
  1. Browser - via CJS -> lib/index.browser.js
  2. Browser - via ESM -> lib/index.browser.mjs
  3. Node - via CJS -> lib/index.js
  4. Node - via ESM -> lib/index.mjs

Note: It's probably better if you put all environment-specific code in a module and only replace this one with the browser field.

@gauntface

This comment has been minimized.

Copy link

gauntface commented Sep 15, 2017

@sokra that looks like a good option to me.

Last Q and then I'll 🤐 would it make sense for browser to follow the same pattern as main where I could define "browser": "lib/index.browser" and the file extension is tested for .mjs and .js?

Thanks for the info all :)

@sokra

This comment has been minimized.

Copy link
Member

sokra commented Sep 16, 2017

would it make sense for browser to follow the same pattern as main where I could define "browser": "lib/index.browser" and the file extension is tested for .mjs and .js?

This will work when all tools support .mjs. webpack will start to support it with version 4.

@sokra sokra closed this Sep 16, 2017

@jshcrowthe

This comment has been minimized.

Copy link

jshcrowthe commented Sep 17, 2017

So I looked into the browser as a map, as has been suggested, and I'm fairly certain that it doesn't work. Webpack seems unable to resolve the module with pkg.browser set to a map.

I updated my gist here: https://gist.github.com/jshcrowthe/acea97f8b18e81c0a797137b8a38f1e4

Thoughts?

@bfred-it

This comment has been minimized.

Copy link

bfred-it commented Sep 17, 2017

@jshcrowthe I may have used the wrong syntax, perhaps you need to start the paths with ./ as shown in the browser field spec

https://github.com/defunctzombie/package-browser-field-spec/blob/master/README.md

@jshcrowthe

This comment has been minimized.

Copy link

jshcrowthe commented Sep 17, 2017

Ahhh that did it. Thanks @bfred-it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment