eslint-plugin-import: no-extraneous-dependencies doesn't support nested package.json

Example of my project structure:

├── package.json
├── src
│   ├── components
│   │   ├── Avatar
│   │   │   ├── Avatar.css
│   │   │   ├── Avatar.js
│   │   │   ├── README.md
│   │   │   └── package.json
│   │   ├── Button
│   │   │   ├── Button.css
│   │   │   ├── Button.js
│   │   │   ├── ButtonSpinner.css
│   │   │   ├── ButtonSpinner.js
│   │   │   ├── README.md
│   │   │   └── package.json

Root package.json contains all dependencies, nested package.json looks like this:

{
  "name": "Avatar",
  "main": "Avatar.js"
}

That’s why I got wrong warnings:

src/components/Avatar/Avatar.js
  1:1  error  'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it            import/no-extraneous-dependencies
  2:1  error  'classnames' should be listed in the project's dependencies. Run 'npm i -S classnames' to add it  import/no-extraneous-dependencies

Is this fixable?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 41
  • Comments: 51 (13 by maintainers)

Commits related to this issue

Most upvoted comments

When trying to pass a function to the rule with both eslint 5.16.0 and 6.0.0-alpha I get

	Configuration for rule "import/no-extraneous-dependencies" is invalid:
	Severity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '[Function: import/no-extraneous-dependencies]').

This would be great for me. I work on a monorepo (here’s why) with a root package.json and sub-directories with their own package.json. As it stands, I just have to disable this rule, which is a bummer, because it’s so useful.

As I understand it, in order to resolve the installed dependencies accurately (in a way that matches node’s module system), the getDependencies function would basically need to recurse up all found package.json files, updating the context arg as it goes, until the app root directory, then merge the dependencies object from each package.json (something like Object.assign({}, ..._.pick(packageContents, 'dependencies')), where packageContents is an array of the contents from root to leaf).

I understand the concerns about introducing this, but I would still like to put a strong favorable vote towards adding this as an option. Monorepos are fairly common and probably worth supporting. See Lerna for evidence of this, which can also serve as a way to make a quick example repo test case.

And in terms of the more general question of sub-packages not declaring all their dependencies and therefore having to do multiple npm installs in your codebase, I think that’s just part of the tradeoff one makes. Part of the benefit of Lerna is having a CLI that makes it easy to do monorepo npm stuff.

Expanding on my previous comment, here’s what my ESLint settings look like:

'import/resolver': {
  node: {
    moduleDirectory: [
      'node_modules',
      './src',
    ],
  },
},

Inside src I have 3 main folders: services, components, and scenes (this is for a large React Native app, by the way). Each of those main folders contains a package.json that looks like this (each with their respective names):

{
  "name": "services"
}

Elsewhere in my project I can now import a component (or a service or a scene) by simply typing:

import Button from 'components/Button'

This is much more manageable than typing something like:

import Button from '../../../../../../../../components/Button';

Currently, I get import/no-extraneous-dependencies: 'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it because the minimal package.json doesn’t have dependencies and the rule doesn’t care to look up higher in the directory tree.

If the rule allowed me to do the following, it would solve this issue:

'import/no-extraneous-dependencies': {
  moduleDirectories: ['./'] // or any other configuration that's most convenient for all
},

I just hit this today too. I think the only real way to fix is to enable an array of packageDirs to use, something like…

      'import/no-extraneous-dependencies': [
        'error',
        {
          devDependencies: true,
          packageDirs: [
            './', // this package
            '../../', // mono-repo root
          ],
        },
      ],

Here’s a version that works in all contexts (lerna root + packages / vscode) with a root level .eslintrc.js config :

'import/no-extraneous-dependencies': context => [
      'error',
      {
        devDependencies: true,
        packageDir: [context.getFilename(), __dirname]
      }
    ]

The context.getFilename() gets you the nearest package.json(it’s the same code that’s used inside the rule source), and the __dirname gets you the root package.json

edit: doesn’t cover sibling package dependencies though, since the require are declared using the fqpn @scope/package, for this to work the rule would need to accept package name exceptions

Looks like #685 was merged in May.

In my case, I was able to solve the problem with: "import/no-extraneous-dependencies": ["error", {"packageDir": "./"}], (.eslintrc.js & package.json are in the working directory root)

Leaving this here because this is the page google led me to, but I would recommend checking out the docs for this rule.

When will this be merged?

@rjhilgefort this solved it for me:

"import/no-extraneous-dependencies": ["error", {"devDependencies": true, "packageDir": "./"}]

Is there any news on @jacobrask issue? I guess many are experiencing this.

@kopax @kilpatty this is the code I currently use in my boilerplate:

'import/no-extraneous-dependencies': ['error', {'devDependencies': true, 'packageDir': path.join(__dirname, './')}],

That being said, I haven’t worked on a project that uses multiple package.json files in a long time, so your mileage may vary

Jumping here, monorepo user, maybe we could:

  • be able to provide a base dir (monorepo base)
  • allow to provide an option to walk up to the base dir for packages.json

Or just a way to express some different packages.json to read. Dunno

Just throwing a +1 for the solution that @tizmagik proposed. I’m hitting this problem in a monorepo architecture as well. I see that this issue is closed, should I created a new issue for this?

Hello!

If you want you can try my fix with npm i -D ramasilveyra/eslint-plugin-import#fix-root-pkg-copy-dont-remove (https://github.com/ramasilveyra/eslint-plugin-import/tree/fix-root-pkg-copy-dont-remove, fork it and rebuild if you want 😃 ) until #685 gets a review & merge. So far It works well on the projects where I had this problem.

Example of conf:

"import/no-extraneous-dependencies": ["error", { "packagePath": "./package.json" }]

so we are on 2020 no further improvements on this apart just to switch the rule off?

You don’t; eslint provides it.

Yup, an option to check all package.json for dependencies would work for me.

Also wondering if this has a solution other than “off”

Agreed that it’s more of a Node.js issue. And sure, being able to configure this rule to support nested package.json would be a good thing! I’m just saying that having this feature enabled by default could lead to some hard to find bugs for people that are not aware that the situation I explained above can happen.

In some specific situations, enabling this rule to go up the hierarchy of package.json will make it less powerful by introducting some unexpected behaviour.

Let’s say you have the following project structure:

├── package.json
├── file1.js
└── nested
    ├── package.json
    └── file2.js

with the following contents:

// package.json

{
  "name": "my-app",
  "dependencies": {
    "lodash": "3.10.1"
  }
}
// file1.js

const _ = require('lodash');

// Expected output: 3.10.1
console.log(_.VERSION);
// nested/package.json

{
  "name": "nested",
  "dependencies": {
    "babel-types": "6.16.0"
  }
}
// nested/file2.js

const _ = require('lodash');

// Expected output: 3.10.1
console.log(_.VERSION);

Then we have the following outputs:

node file1.js
> 3.10.1 // All good!

node nested/file2.js
> 4.16.4 // Wrong! Expected to also be 3.10.1: the version we depend on in the root package.json

Even though we declared the dependency on lodash 3.10.1 only once in the root package.json, it’s not actually this version that is imported in nested/file2.js. It happens because babel-types depends on lodash ^4.2.0 and with the npm flat module structure, this new version of lodash ends up directly in nested/node_modules/lodash and “shadows” the version of lodash required by our root package.json.

However, if nested/package.json also declared an explicit dependency on lodash 3.10.1 then it will be this version that npm would write in nested/node_modules/lodash. Indeed, the lodash package required by babel-types would be under nested/node_modules/babel-types/node_modules/lodash and all would be fine.

Thus, the only sane situations in which it would be good to support nested package.json is when the deep ones doen’t have any dependencies (dev included). Because then, nested/node_modules won’t exist and lodash 3.10.1 will get picked up by the Node.js module resolution algorithm.

In the end, the use-cases of @nkt and @migueloller will work fine, but it’s not generally safe - at least version wise - to support nested package.json.