browserify / browser-resolve

resolve function which support the browser field in package.json
MIT License
102 stars 70 forks source link

fix: allow overriding entry point in browser field #101

Open achingbrain opened 3 years ago

achingbrain commented 3 years ago

Some modules are published with no main field in package.json and no /index.js, instead using exports to define the entry point for the module.

They sometimes specify an entry point for the browser using a "." or "./" key in the browser field, but currently these are broken with browserify as it tries to resolve /index.js when no main field is present.

The change here is to allow define the entry point for the module using the browser field:

{
  "browser": {
    ".": "path/to/index.js"
  }
}
ljharb commented 3 years ago

Can you elaborate on this?

Currently, resolve has zero support for the exports field (see https://github.com/browserify/resolve/pull/224), and until that time, a package that lacks a main entirely (and also lacks an index.js) simply shouldn't be resolveable. (that package should, for back compat reasons, continue publishing a main for the foreseeable future: i see you've filed https://github.com/rvagg/cborg/issues/9).

The "browser" field explicitly can't work with the "exports" field, so for this package - which doesn't actually provide a proper "browser" implementation - there's really no solution. The package links to./esm/cborg.js in its "browser" condition in the "exports" field, which is certainly what this package would select, but the implementation must be CJS, not ESM, for this package (and anything that expects the browser field) to support it.

ljharb commented 3 years ago

In other words, the browser field spec does not permit a key of "." in this case, because that would node-resolve to the "main" or "index.js", both of which are missing here. The package you linked simply isn't compatible (yet) with the browserify ecosystem.

rvagg commented 3 years ago

welp, you may as well join this party @ljharb https://github.com/mikeal/ipjs/issues/9 :shrug: this all sucks, and I can't believe how much it still sucks after so much investment by so many people. At this stage I just want to know the path to most likely success without having to be stuck in 2014 forever.

ljharb commented 3 years ago

I've subscribed to that issue, and I agree with mikeal's thoughts in the thread.

In my perspective, the only path to success is maintaining backwards compatibility. This means, either publishing packages as "only CJS" forever (which is fine, CJS and ESM are both first-class module systems in node, CJS is never going away, and isn't "legacy" in any way), or, publishing a dual package using one of these techniques: (in all approaches, "main" and "exports" "default" point to CJS, and any file that's a singleton must always have precisely one CJS file that holds the source of truth for the state)

  1. author in CJS, hand-write thin ESM wrappers to provide named exports only where needed. "exports" "import" points to ESM.
  2. author in babel-flavored or node-native ESM, use babel to spit out CJS files, named exports come from these automatically, so you can publish only the CJS and then "exports" has no need for "import" - there's no value to publishing the ESM here.
  3. author in both node native ESM and CJS. "exports" "import" points to ESM. both are published (but note the singleton warning above)

In all these examples, a top-level "browser" field should point to CJS files that, when bundled by a non-broken module bundler, work in a browser. The "exports" field's "browser" condition should hold the same. If in the future a tool exists that can benefit from pointing to native ESM, and "import" is not sufficient, then a different "exports" condition would need to be invented to point to that - but since no such tool exists yet that I'm aware of, this isn't yet a use case that needs addressing.