Skip to content

Instantly share code, notes, and snippets.

@danielweck
Last active November 11, 2022 08:38
Show Gist options
  • Save danielweck/cd63af8e9a8b3492abacc312af9f28fd to your computer and use it in GitHub Desktop.
Save danielweck/cd63af8e9a8b3492abacc312af9f28fd to your computer and use it in GitHub Desktop.
ESLint import resolver for ESM modules via package.json exports map
module.exports = {
settings: {
// optionally, if TypeScript project:
// 'import/parsers': {
// '@typescript-eslint/parser': ['.ts', '.tsx'],
// },
'import/resolver': {
// optionally, if TypeScript project:
// https://github.com/alexgorbatchev/eslint-import-resolver-typescript
// typescript: {
// alwaysTryTypes: false,
// project: ['./PATH/TO/tsconfig.json'],
// },
[path.resolve('./eslint-plugin-import-resolver.js')]: { someConfig: 1 },
},
},
},
const path = require('path');
const { resolve: resolveExports } = require('resolve.exports');
// optionally handle NodeJS built-ins just in case not handled by another ESLint module resolver in the chain
const { builtinModules } = require('module');
const builtins = new Set(builtinModules);
/**
* @param {string} source source
* @param {string} file file
* @param {Object} _config config
*/
const resolve = (source, file, _config) => {
if (builtins.has(source)) {
// return { found: false }; // this also works?
return { found: true, path: null };
}
try {
const moduleId = require.resolve(source, { paths: [path.dirname(file)] });
return { found: true, path: moduleId };
} catch (/** @type {any} */ err) {
if (err.code === 'MODULE_NOT_FOUND' && err.path?.endsWith('/package.json')) {
const { name, module, main, exports } = require(err.path);
const resolved = resolveExports({ name, module, main, exports }, source);
const moduleId = path.join(path.dirname(err.path), resolved);
return { found: true, path: moduleId };
}
return { found: false };
}
};
module.exports = {
interfaceVersion: 2,
resolve,
};
@dding-g
Copy link

dding-g commented Apr 25, 2022

Hello.
Thank you for your problem resolve!

I have opinion on catching error.

//...
	catch (/** @type {any} */ err) {
		if (err.code === 'MODULE_NOT_FOUND' && err.path?.endsWith('/package.json')) {
			const { name, module, main, exports } = require(err.path);
			const resolved = resolveExports({ name, module, main, exports }, source);
			const moduleId = path.join(path.dirname(err.path), resolved);
			return { found: true, path: moduleId };
		}
//...

In my case, use subpath on node.

When cannot resolve moduleId in try, err.path is always package.json.

When moduleId cannot be defined in the try syntax, the error.path in the catch syntax is always package.json.
I think, printing a path that cannot be resolved is much useful. also it is already defined in 'source'.
EX:) error Unable to resolve path to module '#utils/error' import/no-unresolved

Then, what kind of error situation should the following syntax be executed?

//...
			const { name, module, main, exports } = require(err.path);
			const resolved = resolveExports({ name, module, main, exports }, source);
			const moduleId = path.join(path.dirname(err.path), resolved);
//...

Thank you. :)

@k7sleeper
Copy link

Thanks for that solution.

I had to change the following line:

const possibleBuildinModuleId = moduleId.startsWith('node:') ? moduleId.slice(5) : moduleId;
if (builtinModules.includes(possibleBuildinModuleId)) {
   return { found: false };
}

@k7sleeper
Copy link

Nevertheless, I get the following error:

 build-scripts\create-run-env\index.js:25:8
  ✖  25:8  Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move index.js to same directory as #build-scripts/constants.js or consider making #build-scripts/constants.js a package.  import/no-relative-parent-imports

  1 error

So, this solution does not cooperate with rule import/no-relative-parent-imports

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