Skip to content

Exports field trees with top-level conditions result in a misleading error messageΒ #325

@Sidnioulz

Description

@Sidnioulz

When the exports field of a package has top-level conditions leading to subpath conditions, enhanced-resolve does not parse the subpath conditions. Following a discussion with the maintainer, this is expected behaviour as that syntax is not supported by Node.js either.

However, enhanced-resolve parses the exports field without failing, and later on in a Webpack compilation, Webpack fails to resolve subpath imports. Instead, enhanced-resolve should detect this syntax and throw an error message somewhere around buildExportsFieldPathTree. The reasoning is that it's very hard to understand why subpath imports fail otherwise, because the Webpack and Node.js documentations don't explicitly state that this use of exports is unsupported.

Original ticket

(original title: Exports field trees with top-level conditions are not supported)

From my understanding, enhanced-resolve is supposed to know how to handle the exports field of package.json, including exports with conditional names, as there are checks for conditional mappings in the code.

Everything in this ticket relates to the lib/util/entrypoints.js file.

In the buildExportsFieldPathTree, we can see that exports that do not start with a dot are rejected. Conditional mappings are checked after the building of the treeRoot, so this means that exports with top-level conditions are wrongly assumed to be incorrect syntax. This results in treeRoot containing a single file entry that matches only . and has the entire exports field as its content.

package.json:

	"exports": {
		"import": {
			".": "./esm/index.js",
			"./*": "./esm/*.js"
		},
		"require": "./build/bundle.js"
	},

Webpack.config.js:

  config.resolve.conditionNames = ['import', 'node', 'default']

Obtained treeRoot:

{
  children: null,
  folder: null,
  wildcards: null,
  files: Map(1) { '' => { import: { ".": "./esm/index.js", "./*": "./esm/*.js" }, require: './build/bundle.js' }
}

As a consequence, imports of the form @org/mylib work, but @org/mylib/foo fail. Should you need a reproduction example, the one written in the Webpack 5 documentation does not work: https://webpack.js.org/guides/package-exports/#providing-commonjs-and-esm-version-stateful.

I am not sure what the appropriate approach is, hence the lack of patch.

One could perform a conditional mapping match before building treeRoot, and building the first matching sub-tree, resulting in a treeRoot like so:

{
  children: null,
  folder: null,
  wildcards: Map(1) { '' => './esm/*.js' },
  files: Map(1) { '' => './esm/index.js' }
}

Or one could map conditional trees into trees with a top-level relative path and conditional leaves, resulting in something like:

{
  children: null,
  folder: null,
  wildcards: Map(1) { '' => './esm/*.js' },
  files: Map(1) { '' => { import: "./esm/index.js", "require": "./build/bundle.js" } }
}

Or the treeRoot structure itself could be recursive, though I've no idea what impact this would have on the rest of the code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions