Skip to content
This repository has been archived by the owner on Sep 2, 2023. It is now read-only.

Conditional exports naming usability discussion #452

Closed
guybedford opened this issue Dec 5, 2019 · 61 comments
Closed

Conditional exports naming usability discussion #452

guybedford opened this issue Dec 5, 2019 · 61 comments

Comments

@guybedford
Copy link
Contributor

guybedford commented Dec 5, 2019

Further to discussions from today re resolver stability and trying to incorporate the current feedback on conditional exports I've posted the PR at nodejs/node#30799 to open discussion around conditional exports naming and behaviours.

From the PR description -

A major priority for the modules implementation is resolver stability, and the dual mode story through conditional exports is a big remaining piece of this.

Common usability feedback out of various discussions on conditional exports so far has been that the "default" field may be seen to be a confusing name, and that it isn't clear when the "require" condition will match either.

To try and improve the overall usability this PR makes the following condition name changes:

  • Add a new "import" condition as the converse of the "require" condition, only applying for the ESM loader.

All conditions (except for "default") remain behind the --experimental-conditional-exports flag.

This makes the dual mode workflow look like:

{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "require": "./index.cjs",
    "import": "./index.js"
  }
}

instead of the previous:

{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "require": "./index.cjs",
    "default": "./index.js"
  }
}

the UX improvement being that the former seems like it will look more natural to most users unfamiliar with "exports".

@ljharb
Copy link
Member

ljharb commented Dec 5, 2019

Removing "default" will break the current implementation in node 13 for the packages I've published with "exports" - I hope we don't do that.

@GeoffreyBooth
Copy link
Member

I like this direction. "module" and "commonjs" make far more sense to me. It’s immediately obvious what each one is for.

Furthermore, "module" is a better fallback/“for every runtime” key than "default". Every runtime that supports "exports" will support ESM, so they will all support "module", so we might as well make that the fallback key; and it’s better than "default" because "default" isn’t necessarily ESM whereas "module" is. That’s a big benefit.

We should remove "default" immediately, perhaps in the next minor release, to minimize the number of people impacted by the change. The feature has an experimental warning, so we can change or remove it at any time, and there’s no guarantee of backward compatibility.

@ljharb
Copy link
Member

ljharb commented Dec 5, 2019

That we can technically get away with it doesn't mean that downstream users won't suffer as a result; this isn't imposing a cost on module authors, it's imposing a cost on their consumers.

Could we add and prefer "module" and "commonjs", but still support "default" until the next major?

@GeoffreyBooth
Copy link
Member

We also need to add to the docs some information about how conditions are recursive. For example:

{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "node": {
      "commonjs": "./index.cjs"
    },
    "module": "./index.js"
  }
}

This formulation should cause all Node consumers to load the CommonJS index.cjs; it’s as if the ESM version wasn’t shipped at all. But all other runtimes (browsers, Deno, etc.) would load the "module" key’s index.js. This is a way to avoid the dual package hazard, as there’s exactly one version of the package available for use in Node (in either ESM or CommonJS environments). This isn’t as good a solution as the ESM wrapper approach, as the latter provides named exports, but for a package like request that provides only a root export this works just as well.

@jkrems
Copy link
Contributor

jkrems commented Dec 5, 2019

Rename the "default" condition to "module" with it only applying for the ESM resolver.

Let's please phrase this as: Remove the default condition and add a module condition. The default condition was sugar for a clean array fallback. If there's any restrictions on "module", it's fundamentally a different condition (which isn't bad, just something I think should be made explicit). So the correct way to rewrite this:

{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "require": "./index.cjs",
    "default": "./index.js"
  }
}

Would be to use the non-default-using:

{
  "type": "module",
  "main": "./index.cjs",
  "exports": [{
    "require": "./index.cjs",
  }, "./index.js"]
}

There's other uses of default (e.g. "node" or "browser" vs. "default") where replacing default with module doesn't really make sense:

{
  "type": "module",
  "main": "./use-inline-crypto.cjs",
  "exports": {
    "node": "./use-node-crypto.cjs",
    "browser": "./use-web-crypto.cjs",
    "default": "./use-inline-crypto.cjs"
  }
}

There's no ES modules involved, so replacing "default" with "module" here would just break the package. But the following works perfectly fine:

{
  "type": "module",
  "main": "./use-inline-crypto.cjs",
  "exports": [{
    "node": "./use-node-crypto.cjs",
    "browser": "./use-web-crypto.cjs",
  }, "./use-inline-crypto.cjs"]
}

I'm a fan of the array fallbacks, so I can live with getting rid of "default". And I agree that adding a "module" condition makes sense, especially since we shipped without a "require" guard in 13 which limits what packages can do.

@coreyfarrell
Copy link
Member

This proposed scheme is confusing to me as commonjs is a format which can be loaded by node.js import() but the commonjs export is only used for require(). The package.json#type field using commonjs and module to reflect the format adds to this confusion for me. I'd much prefer for commonjs / module within exports to indicate format rather than supported loader.

@jkrems
Copy link
Contributor

jkrems commented Dec 5, 2019

I'd much prefer for commonjs / module within exports to indicate format rather than supported loader.

The confusing thing about the whole distinction is that we'll realistically not make this about the format. It will always be about the supported loader. E.g. require/commonjs guards can point to custom require hooks (e.g. .coffee) or native modules (.node) which isn't a CommonJS module but "something that refers to the require loader". Same with module: It's not ESM only. It's "things the import loader can load" which may include WASM for example. At least I wouldn't expect us to make up precedent rules between different file formats within the import loader. "Pick WASM first, then ESM" sounds super weird.

@bmeck
Copy link
Member

bmeck commented Dec 5, 2019

I'd agree with @jkrems here, it gets more complicated if we use the term commonjs. For example, a commonjs file could use import() but it wouldn't use the commonjs condition. I'm more neutral on default/module/import though. I think part of this confusion might be the default not defining which system is being used on a glance and it will just take some repetition to learn, perhaps naming it import would have matched require more, but i doubt we will be adding other systems of loading code anytime in the foreseeable future so default seems fine to keep for me.

@GeoffreyBooth
Copy link
Member

I'd much prefer for commonjs / module within exports to indicate format rather than supported loader.

The confusing thing about the whole distinction is that we'll realistically not make this about the format. It will always be about the supported loader.

Well that was what I liked about commonjs / module: that (I thought) it described the files in the package, not the loader to use. In general a package.json feels like it should be metadata about the package. Hence commonjs makes sense to me as “this is the CommonJS file for this path.” Even stuff like browser makes sense as I read it as “this is the browser-environment file for this path.”

So if the conditions describe the target files, and I remember the array syntax this time, my example above could be better written as:

{
  "type": "module",
  "main": "./index.cjs",
  "exports": [{
    "commonjs": "./index.cjs"
  }, {
    "module": "./index.js"
  }]
}

In this case, both Node loaders will load index.cjs, as both support "commonjs"-type files and that’s defined first in the array, making it top priority.

If the conditions instead describe the loader/method of importation, then I would call them require and import to make that connection clear. But then in order to achieve the same desired result (both Node loaders get CommonJS, other runtimes get ESM) you’d have to write as:

  "exports": {
    "node": {
      "require": "./index.cjs",
      "import": "./index.cjs"
    },
    "import": "./index.js"
  }

This feels counterintuitive to me, like it’s configuration for Node rather than metadata describing the package. Wouldn’t this also potentially introduce issues if the capabilities of loaders change over time?

@guybedford
Copy link
Contributor Author

guybedford commented Dec 5, 2019 via email

@bmeck
Copy link
Member

bmeck commented Dec 5, 2019

Wouldn’t this also potentially introduce issues if the capabilities of loaders change over time?

I do not believe this would cause issues, but loaders could potentially read/provide flags when delegating.

@devongovett
Copy link

I prefer commonjs/module over require/default and modern/legacy. It's specific about the module format and applicable beyond just Node.

@mjackson
Copy link

mjackson commented Dec 5, 2019

How about module and nomodule, like browsers do?

I realize I'm coming at this from the perspective of someone who ships mostly client-side code these days, but I'm personally planning on shipping UMD bundles for the libraries I maintain instead of strict CommonJS. People who use modules get ESM, nomodule (UMD) for everyone else.

Also, this may be out of scope but one thing that is sorely missing but is already being implemented in bundlers like webpack is the idea of dev vs. prod builds. Would love, love, love to see some discussion around this.

@developit
Copy link

I'm probably missing something here - what's the reason this isn't sufficient to achieve dual-mode?

{
  "type": "module",
  "main": "./index.cjs",
  "exports": {
    "module": "./index.js"
  }
}

Originally, based on discussions with various Node folks, my assumption was that the following would work for shipping ES5+CJS and EScurrent+MJS:

{
  "main": "index.cjs",
  "exports": {
    ".": "index.js"
  }
}

@bmeck
Copy link
Member

bmeck commented Dec 5, 2019

It seems there is some confusion. The flags currently are specifying data for the system loading a package, the flags are not set due to the right hand side of the export mapping (the dependency) nor are the flags set due to the format/context of call site of the load operation (the dependent).

@MylesBorins
Copy link
Contributor

@developit exports shadows main. It also isn't an issue of only 1 / 2 loaders, we need to be able to support many types of entries.

So an idea... why don't we support node, commonjs, module, default (resolved in that order).

this allows for a variety of entry points and resolution with fall back, does not break existing shipped implementation (default).

I'm not 100% we should be supporting the recursive syntax, I don't think we do currently but could be mistaken. @GeoffreyBooth are you passionate about that syntax? It seems like it could significantly complicate writing maps as well as parsing them.

@ljharb
Copy link
Member

ljharb commented Dec 5, 2019

@guybedford I'm not in favor of "legacy"; CJS is not legacy, it's just one of two module systems.

@DerekNonGeneric
Copy link
Contributor

It's clear that the exports object should describe two things:

  1. Environment (runtime)
  2. Loader (furnished by the runtime)

Node has two builtin loaders, namely "CJS" and "ESM" as can be seen in lib/internal/modules. I would explicitly use their internal names as keys, rather than "legacy", "current", etc.

{
  "exports": {
    "node": { 
      "cjs": "./index.cjs",
      "esm": "./index.mjs"
    },
    "browser": {
    }
  }
}

The rationale is that there may be a possible future where the builtin ESM loader is superseded by another loader (e.g. "ESM2", etc.). This would result in multiple "legacy" loaders existing.

@MylesBorins
Copy link
Contributor

@DerekNonGeneric I disagree and I think expanding this in exports significantly complicates things. I don't think we need to specify the difference between the environment and the loader here. I think we can fairly succinctly cover almost tall cases that node requires with the 4 keys I suggested above. Other environments are able to then extend with whatever keys they want. I would very much like us to avoid the recursive case

@GeoffreyBooth
Copy link
Member

There’s some confusion here. There are really two decisions at play here:

  1. Do the condition names represent loaders or the format to be loaded (which may not map one-to-one with loaders).

  2. Depending on what decision is made in 1, which names make the most sense?

I think there’s probably consensus that if the condition names represent the format of the files, then the names commonjs and module make the most sense. After all, "type" already describes the format of the files, and those are the names we chose.

However if the condition names represent names of loaders, then commonjs and module are confusing choices because those terms are already associated with describing formats of files (again, see "type").

And currently, the condition names do represent the names of loaders. That’s probably why require was chosen in the first place.

I think it’s safe to say that there’s a lot of confusion about this point. I certainly was confused; I thought that we weren’t just renaming things but also making the conditions be about the files rather than about the loaders (as in the conditions are metadata, not configuration).

So before we get into what the best condition names are, can the conditions be describing the formats of the files rather than configuring each loader what it should load? That seems to be the preference of several of the folks on this thread, or at least it’s the assumption many people seem to be making about how this works.

@bmeck
Copy link
Member

bmeck commented Dec 5, 2019

So before we get into what the best condition names are, can the conditions be describing the formats of the files rather than configuring each loader what it should load?

I do not believe so. Code like the following seem to be impossible to reconcile with that idea:

// foo.cjs
require('bar'); // sets the "require" condition
import('bar'); // does not set the "require" condition

@coreyfarrell
Copy link
Member

So before we get into what the best condition names are, can the conditions be describing the formats of the files rather than configuring each loader what it should load?

I do not believe so. Code like the following seem to be impossible to reconcile with that idea:

// foo.cjs
require('bar'); // sets the "require" condition
import('bar'); // does not set the "require" condition

I disagree that this is a problem, I think it's a major justification of conditional exports. Consider if bar has named exports provided through an ESM wrapper. It is expected that in this example require('bar') will load the commonjs format and import('bar') should prefer to load the ESM formatted file.

@bmeck
Copy link
Member

bmeck commented Dec 5, 2019

@coreyfarrell that is allowed, I don't understand the disagreement. The problem with interpreting the conditions as formats is multiple.

  • import('bar') as it stands could load a different non-ESM file, anything that import can load in fact, such as .cjs files. It does not state any preference or constraint on the right hand side of an exports key value pair to enforce it isn't JSON/WASM/CJS/etc. Its usage within CJS does not make it set conditions as if it were a require('bar').
  • require('bar') as it stands does not place restrictions on the dependency, nor on where its usage is; you can use require within ESM and it won't set conditions as if it were an import('bar').

Neither of these relate to the dependent or dependency formats. I do not believe that the conditions can actually be tied to the format of a dependency nor of the dependent given our current designs and doing so seems impossible since things like import and require potentially resolving to different dependencies by design.

@sokra
Copy link

sokra commented Dec 5, 2019

Here my 2 cents/opinions:

Like: 2 opposite conditions for Loader, e. g. module/commonjs or import/require
Rational: Make it easier to extend and allows users to be more specific

Dislike: module/commonjs
Like: import/require
Rational: commonjs is a little bit off because it would probably also affect UMD and AMD formats. import/require reflects the semantics of the condition: "the way a package is consumed". require still seems to be a bit off when using UMD or AMD format, but I think it's acceptable (AMD also uses require as name.

Dislike: Having a list of condition ordered and match in this fixed order: node, commonjs, module, default
Like: Having a Set of equal conditions matched in order of the properties in exports.
Rational: One is able to read the exports property from top-to-bottom to figure out which entrypoint is used without having to look up the precedence list. Having a precedence list doesn't really make sense to be when having orthogonal conditions. This gets more important when more orthogonal conditions are added (like dev/prod or es2019/es2020).
More info: An object with multiple keys would translate into an array of objects with a single key. A default key must always be the last one. { node: "./a", import: "./b", prod: "./c", default: "./d" } would be semantically equal to [ {node: "./a"}, {import: "./b"}, {prod: "./c"}, "./d" ]. Especially when allowing recursive conditions a precedence list is really confusing when using orthogonal conditions in a single object.

Like: development/production as condition.
Rational: Would eliminate hacks like if (process.env.NODE_ENV === 'production') { module.exports = require('./cjs/react.production.min.js'); } else { module.exports = require('./cjs/react.development.js'); } in react. Such hacks are not possible with ESM so a condition would help to archive the same behavior.

Like: webassembly and top-level-await as conditions
Rational: importing webassembly or using top level await would be hard errors when used and it's not supported.

Idea: else instead of default
Rational: default could be confused with the ESM default export. e. g. could mean a condition that is matched when the default exports is imported. Not proposing to add this, just saying it could be confusing. (no strong opinion

Dislike: versions like node>=13 as condition
Rational: We should check features instead of versions. The same mistake has been made with browser version checking instead of feature check in the past.

@ljharb
Copy link
Member

ljharb commented Dec 9, 2019

No, I am not OK with renaming "default", it will break packages out in the wild on node v13, and it will prevent backporting conditional exports to v12 for the same reason.

@thysultan
Copy link

I'm just here to say that i like "default" for the simple reason that...

{
  "exports": {
    "require": "./index.cjs",
    "default": "./index.js"
  }
}

"require" and "default" have the same number of characters.

@jkrems
Copy link
Contributor

jkrems commented Dec 9, 2019

No, I am not OK with renaming "default", it will break packages out in the wild on node v13, and it will prevent backporting conditional exports to v12 for the same reason.

I think the current PR is adding import as a new condition and deemphasizes default in the docs without removing support for it.

@devongovett
Copy link

I don't think that file format is valuable to express in the exports map.

Disagree. People have been doing this for years with main/module before exports existed and will be looking for a replacement. I don't think type is intuitive in this regard: it applies to all exports, not just main, so it's unclear how one should make a package that supports both ESM and CommonJS without changing the file extension of their code (is this even possible?). I think this will create a lot of confusion vs making the key determine the module format.

{
  "type": "module",
  "main": "foo.js", // this is ESM
  "exports": {
    ".": "bar.js" // this is also ESM
  }
}
{
  "main": "foo.js", // this is CommonJS
  "exports": {
    ".": "bar.js" // this is also CommonJS?
  }
}

Only way to make a package that supports both is like this?

{
  "main": "foo.js", // this is CommonJS
  "exports": {
    ".": "bar.mjs" // this is ESM
  }
}

This is pretty confusing IMO. Having commonjs and module conditions would be much simpler and easier to explain and document.

@jkrems
Copy link
Contributor

jkrems commented Dec 9, 2019

@devongovett Right, but your alternative means that if you import './bar.js' from within the package, it couldn't possibly know that it's supposed to interpret this file as an ES module. Also, what if one of your exports is JSON or WASM? Or if you have a file that's not directly exported but is used by both your CJS and ESM files, e.g. to share state? I don't think using exports to differentiate different kinds of .js files scales beyond very simple examples and gets super confusing afterwards.

If you don't want to change file extensions, you can always create a minimal package.json (just type) in a subdirectory where you put all your CJS. Having two .js files in the same directory next to each other but each is effectively a different file type doesn't sound like something that prevents confusion.

@devongovett
Copy link

I see. This needs to be very well documented then. I've already seen several examples similar to the ones above, assuming that anything inside exports is ESM and main is CommonJS when this is not the case. If you want to support both CJS and ESM in the same package, then one of them cannot use .js.

@devongovett
Copy link

if you import './bar.js' from within the package, it couldn't possibly know that it's supposed to interpret this file as an ES module.

Wait, couldn't that always be ESM since you're importing from ESM? You cannot import a CJS module, and you cannot require an ESM one. So it would just be the same as the parent module format. That's how tools supporting module have worked. I suppose import() is the odd one out here... 🤔

@jkrems
Copy link
Contributor

jkrems commented Dec 9, 2019

You cannot import a CJS module, and you cannot require an ESM one.

In node, you can import a CJS module. The default export of the resulting namespace will be the module.exports object.

@zackschuster
Copy link
Contributor

if someone wouldn't mind clarifying or pointing me at the right doc -- if a node import map mixes CJS with ESM, what happens if i do import { thing } from 'module' when 'module' could resolve to either format? does it fail when CJS is selected & succeed when ESM is selected?

@jkrems
Copy link
Contributor

jkrems commented Dec 10, 2019

if someone wouldn't mind clarifying or pointing me at the right doc -- if a node import map mixes CJS with ESM, what happens if i do import { thing } from 'module' when 'module' could resolve to either format? does it fail when CJS is selected & succeed when ESM is selected?

I'm not 100% sure if I understand the question correctly but from what you describe: We explicitly do not support "'module' may resolve to either CJS or ESM". What we do support in conditional exports is "'module' may resolve to different targets depending on if it is loaded via import or require". So in your example, since it's an import statement, it can only ever resolve to the mapping for import. If the import mapping is set to a CJS file, it would succeed (since we allow import of CJS). If the import mapping is set to an ESM file, it would also succeed. As would any future file formats like WASM/JSON.

If the require (!) mapping is set to an ESM file (or any other file format not supported by require), an attempt to require('module') would fail. It's not really any different than the situation today where it would fail if you set main to a file format not supported by require.

@GeoffreyBooth
Copy link
Member

If the import mapping is set to a CJS file, it would succeed (since we allow import of CJS).

Though the { thing } part wouldn’t work for CommonJS. You’d need import moduleDefault from 'module'; const { thing } = moduleDefault;.

@zackschuster
Copy link
Contributor

thank you both for the clarifications. the situation is much clearer to me now 😄

@GeoffreyBooth
Copy link
Member

nodejs/node#30799 has landed, so now we have import and require and default. Do we want to keep default?

  • Pro: It provides a condition equivalent to “no condition,” what you’d get if you hadn’t been using conditional exports.
  • Con: Seems a bit redundant now that we have import.

@jkrems
Copy link
Contributor

jkrems commented Dec 12, 2019

Con: Seems a bit redundant now that we have import.

I'm not sure I follow this argument. How is it redundant with import?

"exports": {
  "browser": "./browser.cjs",
  "default": "./node.cjs" // this would break with the `import` condition
}

EDIT: Replace browser with development or featureset2019 or anything else that's not exactly require. :)

@GeoffreyBooth
Copy link
Member

I'm not sure I follow this argument. How is it redundant with import?

It’s redundant because any runtime that supports or will ever support "exports" supports ESM; so there’s no need for another fallback that’s matched after import. An equivalent to your example without default would be to replace default with node, or to define both require and import to point to the same CommonJS file. Or put another way, I don’t think there are any use cases that default enables that aren’t already achievable by require and import.

So if it doesn’t provide any independent functionality, its only value is as a shorthand or alternate syntax, or for “completeness” if we feel like we need a condition to represent the “no condition” case. The question is whether these benefits are worth the cost of added API (one more thing to maintain, one more thing to learn, one more thing developers can use incorrectly, etc.).

@ljharb
Copy link
Member

ljharb commented Dec 13, 2019

… weighed against the cost of breakage in the wild if it's removed, and ESM is ever backported to an LTS node like v12.

@jkrems
Copy link
Contributor

jkrems commented Dec 13, 2019

An equivalent to your example without default would be to replace default with node, or to define both require and import to point to the same CommonJS file.

So you mean that default can be replaced by two fields because we assume that there's only two loading systems in JS:

"exports": {
  "browser": "./browser.cjs",
  "import": "./generic.cjs",
  "require": "./generic.cjs"
}

That seems a bit... ugly? At that point, I would hope we'd suggest the much less verbose variant which is also much less likely to be used incorrectly by accident (by forgetting a field etc):

"exports": [{
  "browser": "./browser.cjs"
}, "./generic.cjs"]

So yes - default is redundant with array fallbacks. But I don't think it has a real connection to the import condition. I don't think we can remove default without encouraging more use of string-in-array.

@coreyfarrell
Copy link
Member

@jkrems the last example you gave gets reformatted by any tool which modifies package.json. The result becomes:

"exports": [
  {
    "browser": "./browser.cjs"
  },
  "./generic.cjs"
]

This is an example if why I would use default if available over the array notation. For my own packages I would only use of array notation in the edge case of needing to override default resolver priorities.

@GeoffreyBooth
Copy link
Member

GeoffreyBooth commented Dec 13, 2019

So yes - default is redundant with array fallbacks. But I don't think it has a real connection to the import condition. I don't think we can remove default without encouraging more use of string-in-array.

Let’s consider what these terms mean. As I understand them:

  • node: this file (or files, if there are conditions under node) are intended for the Node runtime
  • browser/electron/etc.: ditto for other runtimes
  • require: this file should be used by any runtime that loads via require
  • import: this file should be used by any runtime that loads via import
  • default: this file should be used by any runtime

Putting a CommonJS file in import would work for Node but for no other runtime, and so should therefore be considered an antipattern; it would by definition break anywhere but Node. CommonJS files should only be defined in require or node, never import or default.

Why not CommonJS in default? Because if default is intended for any runtime, then a user really should only ever be putting ESM in there; nothing else is cross-compatible outside of Node. And if default should always be getting only ESM, then it’s really the same as import but with the possibility of a footgun that import doesn’t have.

@jkrems
Copy link
Contributor

jkrems commented Dec 13, 2019

Why not CommonJS in default? Because if default is intended for any runtime, then a user really should only ever be putting ESM in there; nothing else is cross-compatible outside of Node.

What about... JSON? What about any future file format that may be usable from require but also be widely supported by other JS runtimes? Also, and this is purely aesthetics, the following just looks... off:

"exports": {
  "react-native": "./rn-path.js", // runtime
  "electron": "./electron-path.js", // runtime
  "browser": "./browser-path.js", // runtime
  "node": "./node-path.js", // runtime
  "import": "./generic-path.js" // ... module loader?
}

"exports": {
  "production": "./prod.js", // optimization-level
  "import": "./generic-path.js" // ... module loader?
}

It reads like "if it's an apple, do X. if it's affected by gravity, do Y". It's a distracting level of detail that has nothing to do with the intent - which is "for everything that's not an apple".

Putting a CommonJS file in import would work for Node but for no other runtime.

I don't believe that's true. Bundlers have been reading a "browser" field with browser CJS code for a long time. And it's possible to run CJS in browsers (not efficiently or in production), just not with the built-in ESM loader. But that aside - there will always be packages that point something like import or default to files that do not actually work in every JS runtime. I don't think such a field would be useful. The semantics can only be "this is the broadest version I offer". If a user tries to run it, they'll find out that the file format isn't (yet) supported in their runtime. Or they'll find out that the JS syntax used is too new. Or that the file needs certain APIs not available in their runtime (e.g. global URL).

To me it's the same principle as shipping a version of your website to user-agents you don't recognize. Yes, they may fail to understand the page. But it's not on the server to prevent them from trying. So yes, if all you have is CJS, I do believe that you set default to it. Because who knows - maybe the runtime can handle it. If it doesn't, there's no harm done (and they'll have to find a different package anyhow). At least it had a chance.

@guybedford
Copy link
Contributor Author

Closing as resolved.

@SMotaal
Copy link

SMotaal commented Jan 7, 2020

@guybedford do we remove it from minutes in #461 or keep for update(s)?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests