eslint-plugin-import icon indicating copy to clipboard operation
eslint-plugin-import copied to clipboard

`no-unresolved` is not aware of `exports` definition in `package.json`

Open ignatiusreza opened this issue 5 years ago • 71 comments

From this announcement

Package entry points and the package.json “exports” field

There are now two fields that can define entry points for a package: “main” and“exports”. The “main” field is supported in all versions of Node.js, but its capabilities are limited: it only defines the main entry point of the package. A new package.json field “exports” can also define the main entry point, along with subpaths. It also provides encapsulation, where only the paths explicitly defined in “exports” are available for importing. “exports” applies to both CommonJS and ES module packages, whether used via require or import.

Might need to be fix in resolve package..

ignatiusreza avatar Jun 08 '20 08:06 ignatiusreza

Indeed, that’s where it needs to be fixed.

However, for back compat, you should have a file at the location the exports key would otherwise resolve to anyways - so even when it’s fixed in resolve, you’d still want it fixed now.

ljharb avatar Jun 08 '20 15:06 ljharb

Will this be resolved any time soon? We're going to have to yank no-unresolved from our Google code samples because it can no longer handle our SDKs.

inlined avatar Aug 05 '21 22:08 inlined

@inlined why would your Google SDKs be using exports, without main, in a way that's not backwards-compatible??

ljharb avatar Aug 05 '21 22:08 ljharb

We are using main. We also use exports for submodule paths so we can omit the lib folder generated by TypeScript.

For example, ./lib/v2/providers/https.js is exported at ./v2/https. See github.com/firebase/firebase-functions for more info.

inlined avatar Aug 05 '21 22:08 inlined

Right - but that's not backwards-compatible to pre-exports node. However, if you created an actual ./v2/https.js file that re-exported that provider, then not only would your package be backwards-compatible, but you wouldn't be blocked on resolve getting exports support.

ljharb avatar Aug 05 '21 23:08 ljharb

We control our execution environment and do not need to be backwards compatible to versions that don't support exports (every supported LTS of Node supports exports). Also, putting a root export would muddy project structure. It is quite common to have a /src and /lib folder for typescript projects. With exports it is quite understandable to remove lib from documented import paths.

inlined avatar Aug 05 '21 23:08 inlined

@inlined i'm not "of the opinion that exports should not use renaming features", i'm of the opinion that you have a really simple workaround while you wait.

None of my "opinions" are delaying exports support - the work is difficult and nobody else is doing it, and I have limited time. It will be done eventually.

ljharb avatar Aug 06 '21 00:08 ljharb

@inlined if google wants to help make it happen faster, please feel free to visit https://github.com/browserify/resolve?sponsor=1

ljharb avatar Aug 06 '21 01:08 ljharb

For what it's worth, here's how I solved this situation for a package of mine.

The import style of the library is:

import { fn } from "mylib/next";
import { fn } from "mylib/express";

I updated my package.json build steps with:

{
    "prepublishOnly": "npm run build && cp next/dist/* next/ && cp express/dist/* express/",
    "postpublish": "rm next/*.d.ts next/*.js next/*.map next/*.mjs && rm express/*.d.ts express/*.js express/*.map express/*.mjs"
}

What this does is make copies of the build output files right before npm publish and remove them right after. A bit hacky but it definitely works and will ensure the package works well even on bundlers not supporting the exports: {} field of package.json.

Thanks to the maintainers of eslint-plugin-import for the very hard work they do.

vvo avatar Oct 28 '21 14:10 vvo

@vvo you can also .gitignore those files (don't forget to make an .npmignore and unignore them there) and avoid the need to remove them afterwards.

ljharb avatar Oct 28 '21 14:10 ljharb

Indeed, but I don't want to see these files in my editor (VSCode), they would still appear as grayed out I guess. But I will still add them to gitignore so they never get ~~published~~ committed.

vvo avatar Oct 28 '21 14:10 vvo

@vvo gitignore is so they don't get committed; you DO want them published, which means you have to have a .npmignore that duplicates your gitignore but removes the lines that ignore build output.

ljharb avatar Oct 28 '21 14:10 ljharb

Thanks updated my comment to add committed. Also, I am using the "files: ["dist", "express", "next"]" setting of npm.

vvo avatar Oct 28 '21 14:10 vvo

Hi all, FYI I am successfully using this tiny ESLint import resolver to support ESM modules imports via package.json exports map:

https://gist.github.com/danielweck/cd63af8e9a8b3492abacc312af9f28fd

Duplicate issue? https://github.com/import-js/eslint-plugin-import/issues/1868

danielweck avatar Feb 23 '22 00:02 danielweck

I created a simple resolver that works thanks to enhanced-resolve:

resolver.js

'use strict';

const fs = require('graceful-fs');
const path = require('path');
const { builtinModules } = require('module');
const enhancedResolve = require('enhanced-resolve');
const CachedInputFileSystem = require('enhanced-resolve/lib/CachedInputFileSystem');

const builtins = new Set(builtinModules);

const nodeFileSystem = new CachedInputFileSystem(fs, 4000);
const defaultResolver = enhancedResolve.create.sync(opts());

function resolve(source, file, config) {
  if (builtins.has(source)) {
    return { found: true, path: null };
  }

  try {
    const resolver = config ? enhancedResolve.create.sync(opts(config)) : defaultResolver;
    const result = resolver(path.dirname(file), source);

    return { found: true, path: result };
  } catch (e) {
    return { found: false };
  }
}

function opts(config) {
  return Object.assign({
    fileSystem: nodeFileSystem,
    conditionNames: ['node'],
    extensions: ['.mjs', '.js', '.json', '.node'],
    preferRelative: true,
  }, config);
}

module.exports = {
  interfaceVersion: 2,
  resolve,
};

Usage (eslint.config.js):

// without config
module.exports = {
  // ...
  settings: {
    'import/resolver': path.resolve(__dirname, './resolver')
  },
  // ...
};

// with `enhanced-resolve` config
module.exports = {
  // ...
  settings: {
    'import/resolver': {
      [path.resolve(__dirname, './resolver')]: {
        extensions: ['.js', '.ts']
      }
    }
  },
  // ...
};

bertho-zero avatar Apr 07 '22 17:04 bertho-zero

Any updates?

linguofeng avatar May 10 '22 06:05 linguofeng

I workaround the issue by using no-unresolved's ignore option:

{
  // workaround for
  // https://github.com/import-js/eslint-plugin-import/issues/1810:
  "import/no-unresolved": ["error", { ignore: ["prosemirror-.*"] }],
}

DamienCassou avatar May 31 '22 13:05 DamienCassou

@bertho-zero it looks similar to https://gist.github.com/danielweck/cd63af8e9a8b3492abacc312af9f28fd, can you create a npm package from it?

piranna avatar Jun 18 '22 09:06 piranna

I just published a resolver package that solves this issue using resolve.exports: https://www.npmjs.com/package/eslint-import-resolver-exports

It's currently beta but seems to work for most of my projects. Feedback appreciated.

cyco130 avatar Jul 19 '22 07:07 cyco130

It's currently beta but seems to work for most of my projects. Feedback appreciated.

do you have examples that we could look at? I don't understand how to configure the resolver to get the same behavior as the one from the webpack resolver.

DamienCassou avatar Jul 19 '22 09:07 DamienCassou

do you have examples that we could look at?

Currently what's in the readme is all I have. I think it's best to use this as a fallback in addition to other resolvers as it only supports main, module, exports and nothing else. The readme shows how to use it with TypeScript resolver for example. You should probably keep using Webpack's resolver too.

Check the resolve.exports docs for configuration options.

cyco130 avatar Jul 19 '22 09:07 cyco130

You can use https://github.com/import-js/eslint-import-resolver-typescript which supports exports in package.json instead.

JounQin avatar Jul 19 '22 20:07 JounQin

That should only be a suggestion for TS users; the real solution here is for resolve to add support for exports.

ljharb avatar Jul 19 '22 20:07 ljharb

That should only be a suggestion for TS users; the real solution here is for resolve to add support for exports.

Of course, I didn't close this issue. 🤣

JounQin avatar Jul 19 '22 20:07 JounQin

I just published a resolver package that solves this issue using resolve.exports: https://www.npmjs.com/package/eslint-import-resolver-exports

It's currently beta but seems to work for most of my projects. Feedback appreciated.

Thank you @cyco130, that solved it for me.

stagas avatar Aug 08 '22 03:08 stagas

I'm proposing to fix this by writing a new resolver. I've been experimenting recently (with great success, I feel) in making code sync/async agnostic by making aggressive use of the strategy pattern, which allows you to write functionally pure code which makes requests to and receives responses from a stateful core. Because that request/response mechanism is yield, the core is able to complete the request synchronously, or it may leave the generator suspended while it waits for async lookups to complete.

Would anyone be interested in collaborating me on a project like that if it would fix this issue once and for all?

conartist6 avatar Jan 21 '23 16:01 conartist6

Fixing resolve would be of much larger impact than a new resolver.

ljharb avatar Jan 21 '23 20:01 ljharb

Hi @conartist6, I already have a working resolver I shared in my comment. I gladly accept contributions :)

cyco130 avatar Jan 22 '23 06:01 cyco130

Why not use createRequire(importer).resolve(importee,{paths:[]}) directly? In order to support configuring extensions, try resolve importee.[ext] and importee/index.[ext] again.

nia072011 avatar Aug 09 '23 06:08 nia072011

@nia072011 that wouldn't work in older node versions, and still wouldn't support ESM. It's not a terrible workaround tho pending the resolve implementation.

ljharb avatar Aug 09 '23 10:08 ljharb