webpack / enhanced-resolve

Offers an async require.resolve function. It's highly configurable.
MIT License
916 stars 186 forks source link

`exports.types` after `exports.default` #384

Closed regseb closed 1 year ago

regseb commented 1 year ago

To import a library, I use jsdelivr which uses enhanced-resolve. I had a problem with the order of the properties in exports: default wasn't at the end. https://github.com/jsdelivr/jsdelivr/issues/18496

{
  "exports": {
    ".": {
      "default": "index.js",
      "types": "index.d.ts"
    }
  }
}

I prefer to define sources before types because sources are more important than types. And for the old properties main and types, npm-package-json-lint defines main before types. It's more readable to have the same order for main / types and exports sub-properties (e.g. cronnor/package.json).


enhanced-resolve forces the default property to the end: assert default. Could be last only. The Node.js documentation indicates this:

Node.js implements the following conditions, listed in order from most specific to least specific as conditions should be defined:

  • "node-addons" - similar to "node" and matches for any Node.js environment. This condition can be used to provide an entry point which uses native C++ addons as opposed to an entry point which is more universal and doesn't rely on native addons. This condition can be disabled via the --no-addons flag.
  • "node" - matches for any Node.js environment. Can be a CommonJS or ES module file. In most cases explicitly calling out the Node.js platform is not necessary.
  • "import" - matches when the package is loaded via import or import(), or via any top-level import or resolve operation by the ECMAScript module loader. Applies regardless of the module format of the target file. Always mutually exclusive with "require".
  • "require" - matches when the package is loaded via require(). The referenced file should be loadable with require() although the condition matches regardless of the module format of the target file. Expected formats include CommonJS, JSON, and native addons but not ES modules as require() doesn't support them. Always mutually exclusive with "import".
  • "default" - the generic fallback that always matches. Can be a CommonJS or ES module file. This condition should always come last.

I understand that default should always be the last property among node-addons, node, import, require and default. But the property types can be positioned wherever you want.


I propose to filter the values of the exports to keep only the properties node-addons, node, import, require and default (maybe add webpack).

function conditionalMapping(conditionalMapping_, conditionNames) {
+   const conditionKeys = ["node-addons", "node", "import", "require", "default"];
    /** @type {[ConditionalMapping, string[], number][]} */
-   let lookup = [[conditionalMapping_, Object.keys(conditionalMapping_), 0]];
+   let lookup = [[
+       conditionalMapping_,
+       Object.keys(conditionalMapping_).filter((c) => conditionKeys.includes(c)),
+       0,
+   ]];

    loop: while (lookup.length > 0) {

Or exclude the types properties:

function conditionalMapping(conditionalMapping_, conditionNames) {
    /** @type {[ConditionalMapping, string[], number][]} */
-   let lookup = [[conditionalMapping_, Object.keys(conditionalMapping_), 0]];
+   let lookup = [[
+       conditionalMapping_,
+       Object.keys(conditionalMapping_).filter((c) => c !== "types"),
+       0,
+   ]];

    loop: while (lookup.length > 0) {
alexander-akait commented 1 year ago

You can setup it using options, I don't think it is a good idea to change default logic

alexander-akait commented 1 year ago

Also I think you need to go to typescript community and ask them about it and maybe ask them to change Node.js specific logic (not sure it is possible)

alexander-akait commented 1 year ago

If you have types after default you need consider it as a bug https://github.com/microsoft/TypeScript/issues/46334#issuecomment-943669872

alexander-akait commented 1 year ago

Feature request: Built-in typescript warning about it https://github.com/microsoft/TypeScript/issues/46861

regseb commented 1 year ago

In Community Conditions Definitions documentation, Node specifies that the "types" property should always be included first.

So don't put "types" after "default".