Skip to content
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

Add no-shallow-imports rule #2408

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

joshuason
Copy link

@joshuason joshuason commented Mar 22, 2022

Prevent shallow (barrel) imports.

Why?

Obviates the need for tree shaking.

Todo:

  • Add more test cases
  • Fix commented-out test case (for full coverage)
  • Update examples within docs
  • Add line to README.md

@joshuason joshuason marked this pull request as ready for review April 1, 2022 08:45
@joshuason
Copy link
Author

@ljharb Hey Jordan, just wondering if there's any feedback on this pull request. Cheers :)

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should work by looking at the consuming side - i think it should work by looking at the exporting side.

@@ -21,6 +21,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
- [`no-named-default`, `no-default-export`, `prefer-default-export`, `no-named-export`, `export`, `named`, `namespace`, `no-unused-modules`]: support arbitrary module namespace names ([#2358], thanks [@sosukesuzuki])
- [`no-dynamic-require`]: support dynamic import with espree ([#2371], thanks [@sosukesuzuki])
- [`no-relative-packages`]: add fixer ([#2381], thanks [@forivall])
- Add [`no-shallow-imports`] rule
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line needs to be moved up to the "unreleased" section

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! :)

Comment on lines +47 to +50
import example2 from '..'; // imports from /index.js
import example2 from '../dir2'; // imports from /dir2/index.js
import example2 from '../dir2/index'; // same as above but explicit
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these incorrect? What if dir2/index has a single default export?

```js
// index.test.js

import * as index from './index'; // allowed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import * is a substandard pattern that should never be encouraged.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should import * be disallowed by this rule?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily, but the docs shouldn't suggest it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it shouldn't suggest it, but I think it is important for the docs to show that the pattern is still allowed with this rule - as users may assume that it is disallowed or vice versa. Maybe the docs should show it as correct but discouraged / not recommended?

Suggested change
import * as index from './index'; // allowed
import * as index from './index'; // allowed but discouraged (to disallow this, use the `import/no-namespace` rule)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that's a reasonable compromise.

@joshuason
Copy link
Author

joshuason commented Apr 19, 2022

i think it should work by looking at the exporting side.

Hmmm, sorry, I don't understand what you mean by this. How would the exporting side preemptively know what's consuming it?

@ljharb
Copy link
Member

ljharb commented Apr 19, 2022

It wouldn't. The problem is caused by a file re-exporting something.

If you don't re-export anything, it's impossible to have a "barrel" approach unless you put everything in the same file.

@joshuason
Copy link
Author

Why is file re-exporting a bad thing? Should we be discouraging nested barrel exports?

@ljharb
Copy link
Member

ljharb commented Apr 19, 2022

It's a bad thing because it creates an otherwise nonexistent need for tree-shaking - which due to the nature of JS, can only ever do a half-assed job at deleting unused code.

Yes, nobody should ever be using barrel exports, nested or otherwise, and everything should always be deep-imported from the most granular place it can be.

}
};

const isCalledIndex = filename => filename.slice(0, 5) === 'index';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isCalledIndex = filename => filename.slice(0, 5) === 'index';
const isCalledIndex = filename => filename.split('.')[0] === 'index';

Think this might be slightly better because otherwise:

isCalledIndex('index-lorem-ipsum.tsx') 

will be true where as it should be false.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isCalledIndex = filename => filename.slice(0, 5) === 'index';
const isCalledIndex = filename => path.basename(filename, path.extname(filename)) === 'index';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb path.basename(filename, path.extname(filename)) === 'index' would evaluate to false if the filename is index.test.js for example

filename.split('.')[0] === 'index' might be safer - what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that sounds good

const isShallowImport = (node, context) => {
const filenamePath = context.getFilename();
const sourcePath = node.source.value;
const segments = sourcePath.split('/');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const segments = sourcePath.split('/');

const filenamePath = context.getFilename();
const sourcePath = node.source.value;
const segments = sourcePath.split('/');
const lastSegment = segments[segments.length - 1];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const lastSegment = segments[segments.length - 1];
const lastSegment = path.basename(filenamePath, path.extname(filenamePath));

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

Successfully merging this pull request may close these issues.

None yet

4 participants