Skip to content

Nodejs workspaces and imports issue #3174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
JPStrydom opened this issue Apr 1, 2025 · 6 comments
Closed

Nodejs workspaces and imports issue #3174

JPStrydom opened this issue Apr 1, 2025 · 6 comments

Comments

@JPStrydom
Copy link

Reproduceable Examples

Code Sandbox Repo Explaining The Issue

ZIP File Containing A Copy Of The Code Sandbox Repo

Introduction

We've been struggling to get eslint-plugin-import to work in a monorepo using NPM workspaces and NPM imports

Setup

Our monorepo has the following structure:

├── packages/
│   ├── pakckage-a/
│   │   ├── utils/
│   │   │   ├── convert.js
│   │   │   └── index.js
│   │   ├── work/
│   │   │   ├── index.js
│   │   │   └── work.js
│   │   ├── index.js
│   │   └── package.json
│   └── pakckage-b/
│       ├── index.js
│       └── package.json
├── package.json
└── eslint.config.mjs

The root package.json file looks like this:

{
  "name": "eslint-plugin-import-issue",
  "version": "1.0.0",
  "description": "A repo to reproduce the eslint-plugin-import issue around workspaces and import aliases",
  "type": "module",
  "workspaces": [
    "./packages/*"
  ],
  "scripts": {
    "lint": "eslint --fix",
    "start:package-b": "npm run start --workspace=@package/b"
  },
  "devDependencies": {
    "eslint": "^9.23.0",
    "eslint-plugin-import": "^2.31.0"
  }
}

The package-a package.json file looks like this:

{
  "name": "@package/a",
  "version": "0.0.0",
  "type": "module",
  "main": "index.js",
  "imports": {
    "#package-a/utils": "./utils/index.js",
    "#package-a/work": "./work/index.js"
  }
}

The package-b package.json file looks like this:

{
  "name": "@package/b",
  "version": "0.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "@package/a": "^0.0.0"
  }
}

Issue description

When running npm run start:package-a from the root, the code executes correctly:

npm run start:package-b                     

> eslint-plugin-import-issue@1.0.0 start:package-b
> npm run start --workspace=@package/b


> @package/b@0.0.0 start
> node index.js

{ work: { input: 'WORK' } }
{ convert: { input: 'CONVERT' } }

This implies that the imports are resolving correctly and that code works.

The error comes in when running ESLint with the import plugin. The current ESLint config file currently looks like this:

// eslint.config.mjs
import importPlugin from 'eslint-plugin-import';

export default [
  { files: ['**/*.cjs'], languageOptions: {sourceType: 'commonjs'} },
  {
    files: ['**/*.js', '**/*.mjs'],
    languageOptions: { sourceType: 'module', parserOptions: { ecmaVersion: 'latest' } },
  },
  importPlugin.flatConfigs.recommended
];

When running ESLint, the following errors get raised:

npm run lint

> eslint-plugin-import-issue@1.0.0 lint
> eslint --fix


/project/workspace/packages/package-a/index.js
  1:15  error  Unable to resolve path to module '#package-a/utils'  import/no-unresolved
  2:15  error  Unable to resolve path to module '#package-a/work'   import/no-unresolved

/project/workspace/packages/package-a/work/work.js
  1:25  error  Unable to resolve path to module '#package-a/utils'  import/no-unresolved

/project/workspace/packages/package-b/index.js
  1:9   error  convert not found in '@package/a'  import/named
  1:18  error  work not found in '@package/a'     import/named

✖ 5 problems (5 errors, 0 warnings)
  1. package-a makes use of Nodejs "imports" to simplify imports within the project.
    • It doesn't look like the plugin supports these kind of aliased Nodejs imports (e.g. 1:25 error Unable to resolve path to module '#package-a/utils' import/no-unresolved)
    • How can we configure this plugin to support these kind of imports?
  2. package-b makes use of Nodejs "workspaces" to manage nested packages. It installs package-a as one of it's dependencies.
    • It doesn't look like the plugin supports these kind of Nodejs workspace imports out of the box (e.g.1:9 error convert not found in '@package/a' import/named)
    • How can we configure this plugin to support these kind of imports?

What we've tried

We've tried adding eslint-import-resolver-node:

{
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.mjs', '.cjs', '.json']
      }
    }
  }
}

We've tried adding moduleDirectory:

{
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.mjs', '.cjs', '.json'],
          moduleDirectory: ['node_modules', 'packages/']
      }
    }
  }
}

We've tried using import/external-module-folders as explained here:

{
  settings: {
    'import/internal-regex': '^@package/'
  }
}

Unfortunately, none of the above solutions worked. Hopefully there is just a simple config we're missing.

Any idea how these kinds of imports can be supported?

@JounQin
Copy link
Collaborator

JounQin commented Apr 1, 2025

@JounQin JounQin closed this as not planned Won't fix, can't repro, duplicate, stale Apr 1, 2025
@JPStrydom
Copy link
Author

Are we sure it's a duplicate issue?

And how will the typescript resolver fix a vanilla nodejs issue?

@JounQin
Copy link
Collaborator

JounQin commented Apr 1, 2025

Are we sure it's a duplicate issue?

And how will the typescript resolver fix a vanilla nodejs issue?

@JPStrydom

Yes, pretty sure, the current built-in resolver-node uses resolve which doesn't support imports/exports in package.json.

You can try https://github.com/import-js/eslint-import-resolver-typescript (also works for .cjs/.js/.jsx/.mjs) with:

import { createNodeResolver } from 'eslint-plugin-import-x'

export default [
  {
    settings: {
      'import-x/resolver-next': [createNodeResolver()],
    },
  },
]

@JounQin JounQin closed this as completed Apr 1, 2025
@JPStrydom
Copy link
Author

Thanks for the feedback, much appreciated! Those seem to resolve the issue, but I'm now getting:

 Unexpected token .

when using the ?. operator. Is there a way I can tell the import plugin to allow ?. operators, my eslint file looks like this:

import globals from 'globals';
import importPlugin from 'eslint-plugin-import';

export default [
  { languageOptions: { globals: globals.node } },
  { files: ['**/*.cjs'], languageOptions: { sourceType: 'commonjs' } },
  {
    files: ['**/*.js', '**/*.mjs'],
    languageOptions: { sourceType: 'module', parserOptions: { ecmaVersion: 'latest' } }
  },
  importPlugin.flatConfigs.recommended,
  {
    files: ['eslint.config.mjs', 'lint-staged.config.mjs', 'prettier.config.mjs'],
    rules: { 'import/no-default-export': ['off'] }
  },
  { settings: { 'import/resolver': 'typescript' } }
];

@JounQin
Copy link
Collaborator

JounQin commented Apr 1, 2025

Try to add ecmaVersion: 'latest' into the first languageOptions.

By the way, you can combine the first config with settings together.

@JPStrydom
Copy link
Author

Awesome, thanks @JounQin! That did the trick. Really appreciate it 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants