IanVS / prettier-plugin-sort-imports

An opinionated but flexible prettier plugin to sort import statements
Apache License 2.0
892 stars 21 forks source link

Allow Sort By Specifier, Especially Within an Import Group #131

Closed tryforceful closed 3 weeks ago

tryforceful commented 9 months ago

Is your feature request related to a problem?

Right now the grouping I have configured is like this:

importOrder: [
    '^vue$', // vue core
    '',
    '^@/logger$', // logger 
    '',
    '(.*)\.vue$', // components
    '',
    '^(@/composables/(.*)|@/store.*|vue-router|vue-i18n|@vueuse/.*)$', // composables
  ]

And it generates a sorted / grouped import set like this:

import { onMounted, ref } from 'vue';

import logger from '@/logger';

import Date from '@/components/atoms/inputs/Date.vue';
import Grape from '@/components/molecules/paginations/Grape.vue';
import Apple from '@/components/molecules/toolbars/Apple.vue';
import Fig from '@/components/organisms/drawers/Fig.vue';
import Cantaloupe from '@/components/organisms/headers/Cantaloupe.vue';
import Hawthorne from '@/components/organisms/headers/Hawthorne.vue';
import Banana from '@/components/organisms/tables/Banana.vue';
import Endive from '@/components/templates/Endive.vue';

import { useStore } from '@/store';
import { useRoute } from 'vue-router';

However, the Vue SFC components here are always going to have named, default imports. I would prefer to have them in natural sort order by specifier rather than by their import path, so that I can easily scan the list and see what is present or missing.

It doesn't matter to me what their relative path is to me in this case, because I have used the grouping feature to ensure that only Vue SFCs will be in this block. And even though there's nothing tightly restricting that the variable name I give the component would match the component file name, I don't see that as a blocker for this feature request.

Describe the solution you'd like

I'd like for the sorting to generate this order of imports instead:

import { onMounted, ref } from 'vue';

import logger from '@/logger';

import Apple from '@/components/molecules/toolbars/Apple.vue';
import Banana from '@/components/organisms/tables/Banana.vue';
import Cantaloupe from '@/components/organisms/headers/Cantaloupe.vue';
import Date from '@/components/atoms/inputs/Date.vue';
import Endive from '@/components/templates/Endive.vue';
import Fig from '@/components/organisms/drawers/Fig.vue';
import Grape from '@/components/molecules/paginations/Grape.vue';
import Hawthorne from '@/components/organisms/headers/Hawthorne.vue';

import { useStore } from '@/store';
import { useRoute } from 'vue-router';

Note that only the sort order of the components (the major block) have changed to be in natural sort order.

I feel that this could be accomplished with an alternative supported syntax for the importOrder key in eslintrc:

e.g.

importOrder: [
    ...
    ['(.*)\.vue$', {sortBy: 'specifier', sort: 'naturalAsc'}], // 2nd options parameter
    ...
  ]

or

importOrder: [
    ...
    {regex: '(.*)\.vue$', sortBy: 'specifier', sort: 'naturalAsc'}, // provide options and the regex together
    ...
  ]

This would allow you to sort a single grouping of imports in a different way, while allowing the others to be sorted as they are today (natural sort by import path).

Other Considerations

I've seen people ask in regards to sorting by specifier, "What if you have a destructured list of specifiers alongside named default imports?"

In the case I presented it is not an issue, but I can understand the code would need to have well defined behavior for all cases. I think it makes sense that all imports that involve only destructuring be sorted to the top:

import { Bar } from "@/components/molecules/paginations/Grape.vue";
import { Baz } from "@/components/organisms/headers/Hawthorne.vue";
import { Foo } from "@/components/organisms/drawers/Fig.vue";
import Apple from '@/components/molecules/toolbars/Apple.vue';
import Banana from '@/components/organisms/tables/Banana.vue';
import Cantaloupe from '@/components/organisms/headers/Cantaloupe.vue';
import Date from '@/components/atoms/inputs/Date.vue';
import Endive from '@/components/templates/Endive.vue';

I think they should be sorted naturally by their first variable name, but if that is too complex of a behavior, could we say that the sort behavior is "undefined" among destructured items (the first 3 lines above), but still have the proper natural sorting of the specifier (lines 4-8 above)?

IanVS commented 9 months ago

Hi @tryforceful, thanks for the suggestion. I can see how this would be useful for you, but I'm not 100% convinced that this is worth the extra complexity in the code and the additional options, but will leave this issue open to see how many :+1: votes it accumulates. Is this a common approach in the Vue community?

In the meantime, have you checked out https://github.com/apcomplete/prettier-plugin-sort-imports? It's based on the trivago plugin, so it doesn't treat side-effect imports safely like this plugin does, but it does look like it sorts based on specifiers, like you want. Maybe it will suit your needs, or maybe you can use it as a guide for forking and creating a version of this plugin.

tryforceful commented 9 months ago

Thank you for your quick reply! 🙏🏼 And for the recommendation of @apcomplete's fork.

I checked it out -- even though it does sort by specifier, unfortunately that fork is a bit stale and doesn't handle the latest TS in my repo (as const, satisfies, etc.). It also doesn't have support for types as a separate consideration 😢. Moreover I have some concerns about that fork being maintained in the future...

I also checked out using tools like eslint-plugin-perfectionist's sort-imports, but it sadly doesn't have the great level of customization that your Prettier plugin provides -- namely the ability to group and order by regex match.


I won't claim to speak for the entire Vue community but I think many would find use for this feature (those that care about sorting). In the older Options API you would have to list all your imported components like this:

import { defineComponent } from 'vue'

import Apple from '@/components/molecules/toolbars/Apple.vue';
import Banana from '@/components/organisms/tables/Banana.vue';
import Cantaloupe from '@/components/organisms/headers/Cantaloupe.vue';
import Date from '@/components/atoms/inputs/Date.vue';

export default defineComponent({
  components:{
    Apple,
    Banana,
    Cantaloupe,
    Date,
  },
  ...
});

There's no reason to not list the components in natural sort order in the defineComponent section, without their import path context. And having the import sort order not match the order in the components object key also seems like a misstep...


In the end I might just swallow the loss and use your plugin as-is, even if my requested feature doesn't get implemented, because the pros of the rest of your plugin outweigh the loss IMO. (Great job, and thank you!)

If I were to make a fork and add this functionality would you be willing to consider merging the PR?... Maybe let's see...?

apcomplete commented 9 months ago

FWIW your concerns are warranted. I forked that repo for a previous job and have no plans of continuing to update it. Honestly didn't know anyone but me was even using or looking at it.

IanVS commented 3 weeks ago

I'm going to close this out since it doesn't seem to have achieved a critical mass of upvotes. I know that's probably disappointing, but of course feel free to fork this fork and use that! Or, maybe another option is to use something like patch-package or pnpm patch to change it just for your own needs (which can be a bit easier to keep up-to-date with new versions of this package as they come out.