prettier-plugin-sort 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,20 +2,20 @@
2
2
 
3
3
  A [Prettier](https://prettier.io/) plugin focused on sorting.
4
4
 
5
- - Sort `import` declarations in JS / TS files
6
- - Sort named specifiers inside `export { … }`
7
- - Sort top-level keys, string arrays, and dependency maps in `package.json`
8
- - Zero runtime dependencies
5
+ - Sort top-level `import` and `export { ... }` in JavaScript, TypeScript, and Flow
6
+ - Sort fields in `package.json`
9
7
 
10
8
  Read this in other languages: English | [中文](./README.zh.md)
11
9
 
12
10
  ## Install
13
11
 
12
+ Requires Prettier 3.9 or newer.
13
+
14
14
  ```shell
15
15
  npm i -D prettier prettier-plugin-sort
16
16
  ```
17
17
 
18
- Enable it in your Prettier config:
18
+ Add the plugin to your Prettier config:
19
19
 
20
20
  ```json
21
21
  {
@@ -23,74 +23,92 @@ Enable it in your Prettier config:
23
23
  }
24
24
  ```
25
25
 
26
- Then run Prettier as usual, for example `npx prettier --write .`.
26
+ ## Scope
27
+
28
+ ES module sorting supports these Prettier parsers:
29
+
30
+ - `babel`
31
+ - `babel-flow`
32
+ - `babel-ts`
33
+ - `typescript`
34
+ - `flow`
35
+ - `acorn`
36
+ - `espree`
37
+ - `meriyah`
38
+
39
+ The same sorting also applies to **embedded** JavaScript and TypeScript in Vue or Markdown.
27
40
 
28
- ## What gets sorted
41
+ The plugin only handles ES module syntax. CommonJS `require()` and `module.exports`, along with TypeScript `export =`, remain unchanged.
29
42
 
30
- ### Imports
43
+ `package.json` sorting is enabled for these Prettier parsers:
44
+
45
+ - `json`
46
+ - `json-stringify`
47
+
48
+ Sorting is enabled only when the file is named `package.json`.
49
+
50
+ ## Options
31
51
 
32
- #### Grouping and sorting
52
+ | Option | Type | Default | Description |
53
+ | ------------------------ | ----------------- | ------------------------------------------------------- | ------------------------------------------------------------------------ |
54
+ | `esmImportSort` | `boolean` | `true` | Group and sort top-level static imports and the specifiers inside braces |
55
+ | `esmImportGroups` | `ImportGroup[]` | `["builtin", "external", "parent", "sibling", "index"]` | Set the order of import groups |
56
+ | `esmImportSeparation` | `boolean` | `true` | Add blank lines between groups and above and below side-effect imports |
57
+ | `esmImportTypeStyle` | `TypeImportStyle` | `"separate"` | Control the form and order of type-only imports |
58
+ | `esmImportMerge` | `boolean` | `true` | Safely merge imports from the same module |
59
+ | `esmExportSpecifierSort` | `boolean` | `true` | Sort specifiers inside `export { ... }` |
60
+ | `packageSort` | `boolean` | `true` | Sort fields in `package.json` |
33
61
 
34
- With the default config, imports are grouped and ordered like this.
62
+ ## Import sorting
35
63
 
36
- Before:
64
+ The plugin groups and sorts top-level static imports. It also sorts the specifiers inside braces. Import specifiers that use `as` are sorted by their local names.
65
+
66
+ The plugin leaves dynamic `import()` calls and import-like text in strings or comments unchanged. Set `esmImportSort` to `false` to disable all import sorting.
67
+
68
+ The plugin collects static imports separated by other top-level statements at the first import position, then sorts them.
69
+
70
+ Before sorting:
37
71
 
38
72
  <!-- prettier-ignore -->
39
73
  ```typescript
40
- import App from './App.tsx';
74
+ import App from './App';
41
75
  import fs from 'node:fs';
42
- import lodash from 'lodash';
43
- import path from 'node:path';
44
76
  import react from 'react';
45
77
  ```
46
78
 
47
- After:
79
+ After sorting:
48
80
 
49
81
  ```typescript
50
82
  import fs from 'node:fs';
51
- import path from 'node:path';
52
83
 
53
- import lodash from 'lodash';
54
84
  import react from 'react';
55
85
 
56
- import App from './App.tsx';
86
+ import App from './App';
57
87
  ```
58
88
 
59
- Each import belongs to a group. For example, `node:fs` is a `builtin` (covers Node.js and Bun **runtime** built-ins), while `react` and `lodash` are `external` npm packages. The plugin first classifies each import by group, then sorts alphabetically within each group.
89
+ ### Grouping
60
90
 
61
- Grouping and ordering follow the conventions of [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import)'s [import/order](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md) rule.
91
+ `esmImportGroups` accepts these values:
62
92
 
63
- The default import config is:
93
+ | Group | Matches |
94
+ | ---------- | -------------------------------------------------------------- |
95
+ | `builtin` | Modules beginning with `node:` or `bun:`, plus `bun` |
96
+ | `external` | Packages and module paths not matched by any other group |
97
+ | `internal` | Module paths beginning with `/`, `~`, `@/`, or `#` |
98
+ | `parent` | Parent-relative paths such as `../utils` |
99
+ | `sibling` | Sibling paths such as `./Button` |
100
+ | `index` | `.`, `./`, `./index`, and `./index` with an optional extension |
64
101
 
65
- ```json
66
- {
67
- "plugins": ["prettier-plugin-sort"],
68
- "importOrderGroups": ["builtin", "external", "parent", "sibling", "index"],
69
- "importOrderSeparation": true,
70
- "importOrderTypeImports": "separate",
71
- "importOrderMergeDuplicates": true
72
- }
73
- ```
74
-
75
- Group matchers:
102
+ Modern Node.js code should reference built-ins explicitly with [`node:` URLs](https://nodejs.org/api/esm.html#node-imports). The plugin also classifies only `node:`-prefixed Node.js built-ins as `builtin`. Module specifiers without the prefix, such as `fs` and `path`, belong to `external`.
76
103
 
77
- | Group | Matches | Examples |
78
- | ---------- | ----------------------------------------------------------- | ---------------------------------- |
79
- | `builtin` | `node:*`, `bun:*`, `bun`, and unprefixed Node built-ins | `node:fs`, `path`, `bun` |
80
- | `external` | npm packages, and anything that doesn't match another group | `react`, `@scope/pkg` |
81
- | `internal` | Project absolute paths and aliases | `/utils`, `~/app`, `@/shared` |
82
- | `parent` | Parent-relative paths | `../Button` |
83
- | `sibling` | Sibling paths (excluding index) | `./Icon` |
84
- | `index` | Current directory index | `.`, `./`, `./index`, `./index.ts` |
104
+ The plugin removes duplicate entries and appends omitted default groups in their default order. The default configuration does not include `internal`, so it sorts last unless explicitly added.
85
105
 
86
- > **Note on `internal` detection:** the plugin currently uses hardcoded specifier prefixes (`/`, `~`, `@/`) and does not read `tsconfig paths` or any bundler config. An `importOrderInternalPatterns` option for custom regex matching may be added in a future release.
87
-
88
- Reorder or drop groups through `importOrderGroups`. For example, adding `internal` explicitly:
106
+ Projects that use one of these path aliases can add `internal`:
89
107
 
90
108
  ```json
91
109
  {
92
110
  "plugins": ["prettier-plugin-sort"],
93
- "importOrderGroups": [
111
+ "esmImportGroups": [
94
112
  "builtin",
95
113
  "external",
96
114
  "internal",
@@ -101,232 +119,94 @@ Reorder or drop groups through `importOrderGroups`. For example, adding `interna
101
119
  }
102
120
  ```
103
121
 
104
- Before:
105
-
106
- <!-- prettier-ignore -->
107
- ```typescript
108
- import App from './App.tsx';
109
- import react from 'react';
110
- import shared from '@/shared';
111
- ```
122
+ The plugin does not read `compilerOptions.paths` or resolve aliases from bundler configuration.
112
123
 
113
- After:
124
+ Set `esmImportSeparation` to `false` to remove blank lines between groups and above and below side-effect imports.
114
125
 
115
- <!-- prettier-ignore -->
116
- ```typescript
117
- import react from 'react';
126
+ ### Type-only import style
118
127
 
119
- import shared from '@/shared';
128
+ `esmImportTypeStyle` controls how type-only imports are written and accepts four values:
120
129
 
121
- import App from './App.tsx';
122
- ```
123
-
124
- Set `importOrderSeparation` to `false` if you don't want blank lines between groups.
125
-
126
- #### Type imports
127
-
128
- By default the plugin splits `type` imports into their own statement.
129
-
130
- Before:
131
-
132
- <!-- prettier-ignore -->
133
- ```typescript
134
- import { useState, type FC } from 'react';
135
- ```
136
-
137
- After:
138
-
139
- <!-- prettier-ignore -->
140
- ```typescript
141
- import type { FC } from 'react';
142
- import { useState } from 'react';
143
- ```
144
-
145
- The shape of `importOrderTypeImports` mirrors conventions in the ESLint ecosystem, especially the `fixStyle` option of [@typescript-eslint/consistent-type-imports](https://typescript-eslint.io/rules/consistent-type-imports).
146
-
147
- Using `import { c, type B, a } from 'mod';` as an example:
148
-
149
- | Mode | Result |
130
+ | Value | Input: `import { c, type B, a } from 'mod'` |
150
131
  | -------------- | ---------------------------------------------------------------- |
151
132
  | `separate` | `import type { B } from 'mod';`<br>`import { a, c } from 'mod';` |
152
133
  | `inline-first` | `import { type B, a, c } from 'mod';` |
153
134
  | `inline-last` | `import { a, c, type B } from 'mod';` |
154
135
  | `mixed` | `import { a, type B, c } from 'mod';` |
155
136
 
156
- `separate`, `inline-first`, and `inline-last` sort type and value specifiers independently within their own group. `mixed` sorts all specifiers together alphabetically (case-insensitive), keeping the `type` keyword inline where needed.
157
-
158
- #### Merging same-source imports
159
-
160
- By default, multiple `import` statements from the same source are merged into one.
161
-
162
- Before:
163
-
164
- <!-- prettier-ignore -->
165
- ```typescript
166
- import { useState } from 'react';
167
- import { useEffect } from 'react';
168
- ```
169
-
170
- After:
171
-
172
- ```typescript
173
- import { useEffect, useState } from 'react';
174
- ```
175
-
176
- `importOrderMergeDuplicates` only handles the merge step itself. The arrangement inside the braces is entirely controlled by `importOrderTypeImports`. For instance, merging `import { useState } from 'react';` and `import { type FC, useEffect } from 'react';` produces:
137
+ `separate` is the default. Type-only default and namespace imports keep their original form.
177
138
 
178
- - `separate` (default): the merged statement is split back into two at the type-import stage, so you end up with an independent `import type` statement
179
- - `inline-first`: `import { type FC, useEffect, useState } from 'react';`
180
- - `inline-last`: `import { useEffect, useState, type FC } from 'react';`
181
- - `mixed`: `import { type FC, useEffect, useState } from 'react';`
139
+ `import type { T }` and `import { type T }` have different runtime behavior under [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/verbatimModuleSyntax.html). The plugin converts between them only when the same module also has a value import. Otherwise, it preserves the original form to avoid changing module loading behavior.
182
140
 
183
- Set `importOrderMergeDuplicates` to `false` if you want to keep the original separate statements. Side-effect imports (`import 'mod';`) are never merged because their order has runtime semantics.
141
+ ### Merging and sorting boundaries
184
142
 
185
- #### Side-effect imports
143
+ With `esmImportMerge` enabled, imports from the same module are merged when it is safe to do so.
186
144
 
187
- The order of side-effect imports (`import 'mod'`) often carries runtime meaning, such as CSS cascade order or polyfills that must load before a framework. The plugin never moves other imports across a side-effect import — imports on each side are sorted independently, and the side-effect import itself stays in place.
145
+ The plugin does not merge imports with different import attributes, comments that cannot move safely, or conflicting default or namespace bindings. Side-effect imports also remain separate.
188
146
 
189
- Before:
147
+ Sorting never crosses these boundaries:
190
148
 
191
- <!-- prettier-ignore -->
192
- ```typescript
193
- import Button from './Button';
194
- import App from './App';
195
- import 'normalize.css';
196
- import theme from './theme';
197
- import Icon from './Icon';
198
- ```
149
+ - Side-effect import order can affect CSS cascading or polyfill loading, so these imports are not sorted. They keep their relative positions and separate the sorting segments around them.
150
+ - A declaration marked with `prettier-ignore` stays unchanged. Imports before and after it are sorted separately.
151
+ - A standalone comment separates the imports before and after it. A comment attached to an import moves with that declaration.
152
+ - The plugin preserves shebangs, Prettier file pragmas, and position-sensitive ESLint directives.
153
+ - Specialized declarations such as `import source`, `import defer`, and Flow `import typeof` may be reordered as a whole. The plugin never rewrites or merges them.
199
154
 
200
- After:
155
+ ## Export sorting
201
156
 
202
- ```typescript
203
- import App from './App';
204
- import Button from './Button';
157
+ `esmExportSpecifierSort` sorts top-level `export { ... }` and `export type { ... }` entries by name. Aliased entries are sorted by their exported names.
205
158
 
206
- import 'normalize.css';
207
-
208
- import Icon from './Icon';
209
- import theme from './theme';
210
- ```
211
-
212
- Imports on each side are sorted independently. The side-effect import itself stays in place.
213
-
214
- Sorting rules:
215
-
216
- - Imports are classified into groups. Within each group they are sorted alphabetically
217
- - Default group order: `builtin` → `external` → `parent` → `sibling` → `index`
218
- - A blank line is inserted between groups by default. Disable with `importOrderSeparation`
219
- - `type` imports are split into their own statement by default. Use `importOrderTypeImports` to inline them instead
220
- - Multiple imports from the same source are merged into one by default. Disable with `importOrderMergeDuplicates`
221
- - Side-effect imports (`import 'mod'`) carry semantic order and are never moved. Imports on either side are sorted independently
222
-
223
- ### Exports
224
-
225
- By default, named specifiers inside `export { … }` are sorted alphabetically.
226
-
227
- Before:
159
+ Before sorting:
228
160
 
229
161
  <!-- prettier-ignore -->
230
162
  ```typescript
231
163
  export { useState, useEffect, type FC } from 'react';
232
164
  ```
233
165
 
234
- After:
166
+ After sorting:
235
167
 
236
168
  ```typescript
237
169
  export { type FC, useEffect, useState } from 'react';
238
170
  ```
239
171
 
240
- The plugin only reorders what's inside the braces. It doesn't move the export statement itself and doesn't merge two same-source exports. Set `exportOrder` to `false` to disable this behavior.
241
-
242
- Sorting rules:
172
+ The plugin does not move or merge export declarations. If an export list contains comments, the declaration remains unchanged to preserve comment placement.
243
173
 
244
- - Named specifiers inside `export { … }` and `export type { … }` are sorted alphabetically
245
- - The position of the export statement in the file is not changed
246
- - Multiple export statements from the same source are not merged
174
+ ## `package.json` sorting
247
175
 
248
- ### package.json
176
+ `package.json` field order follows [the default rules from sort-package-json 4.0.0](https://github.com/keithamus/sort-package-json/blob/v4.0.0/defaultRules.md). Nested content such as `scripts`, `exports`, and dependency fields follows the same version's rules.
249
177
 
250
- With the default config, the output looks like this.
251
-
252
- Before:
178
+ Before sorting:
253
179
 
254
180
  <!-- prettier-ignore -->
255
181
  ```json
256
182
  {
257
183
  "version": "1.0.0",
258
- "keywords": ["sort", "prettier", "plugin"],
259
- "name": "demo",
184
+ "name": "example",
260
185
  "dependencies": {
261
- "typescript": "^6.0.0",
262
- "prettier": "^3.0.0"
186
+ "typescript": "^7.0.0",
187
+ "prettier": "^3.9.0"
263
188
  }
264
189
  }
265
190
  ```
266
191
 
267
- After:
192
+ After sorting:
268
193
 
269
194
  ```json
270
195
  {
271
- "name": "demo",
196
+ "name": "example",
272
197
  "version": "1.0.0",
273
- "keywords": ["plugin", "prettier", "sort"],
274
198
  "dependencies": {
275
- "prettier": "^3.0.0",
276
- "typescript": "^6.0.0"
199
+ "prettier": "^3.9.0",
200
+ "typescript": "^7.0.0"
277
201
  }
278
202
  }
279
203
  ```
280
204
 
281
- Top-level key order follows the field list maintained by [sort-package-json](https://github.com/keithamus/sort-package-json), staying aligned with widely adopted community conventions.
282
-
283
- Rules the plugin follows:
205
+ Setting `packageSort` to `false` disables key sorting only. Prettier still formats the file as usual.
284
206
 
285
- - Top-level keys are reordered to the canonical sequence (`name` → `version` → ... → `dependencies`)
286
- - `scripts` and `betterScripts` keys are sorted with pre/post lifecycle grouping and `:` namespace ordering
287
- - `exports` keys are sorted: path subkeys first, conditions alphabetically with `default` moved to the end; nested export maps are recursively sorted
288
- - `dependencies`, `devDependencies`, `peerDependencies` and other dependency maps are always sorted alphabetically, even if `packageJsonOrder` is set to `false`, because `npm install` rewrites them in alphabetical order every time
289
- - String arrays are handled by field: `keywords`, `files`, `activationEvents` are deduplicated preserving original order; `bundledDependencies`, `bundleDependencies`, `extensionPack`, `extensionDependencies` are deduplicated then sorted; `workspaces` arrays are left untouched; all other string arrays are sorted alphabetically
290
- - Use `packageJsonOrderExcludeKeys` to opt specific top-level keys out of sorting entirely
291
-
292
- ## Options
207
+ ## TypeScript configuration
293
208
 
294
- Prettier plugin options are flat, so these options are prefixed with `importOrder`, `exportOrder`, or `packageJsonOrder`.
295
-
296
- | Option | Description | Default |
297
- | ----------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
298
- | `importOrder` | Sort `import` declarations in JS / TS | `true` |
299
- | `importOrderGroups` | Group order. Valid values: `builtin`, `external`, `internal`, `parent`, `sibling`, `index` | `["builtin", "external", "parent", "sibling", "index"]` |
300
- | `importOrderSeparation` | Insert a blank line between adjacent groups | `true` |
301
- | `importOrderTypeImports` | How to place `type` imports: `separate`, `inline-first`, `inline-last`, `mixed` | `"separate"` |
302
- | `importOrderMergeDuplicates` | Merge multiple `import` statements from the same source (except side-effect imports) | `true` |
303
- | `exportOrder` | Sort named specifiers inside `export { … }` alphabetically | `true` |
304
- | `packageJsonOrder` | Sort top-level keys, scripts/exports subkeys, and string arrays in `package.json` | `true` |
305
- | `packageJsonOrderExcludeKeys` | Top-level `package.json` keys to leave untouched | `[]` |
306
-
307
- ## Example
308
-
309
- ```json
310
- {
311
- "plugins": ["prettier-plugin-sort"],
312
- "importOrderGroups": [
313
- "builtin",
314
- "external",
315
- "internal",
316
- "parent",
317
- "sibling",
318
- "index"
319
- ],
320
- "importOrderTypeImports": "inline-last",
321
- "packageJsonOrderExcludeKeys": ["contributes"]
322
- }
323
- ```
324
-
325
- ## Type hints
326
-
327
- If you write your Prettier config in a `.ts` or `.js` file, you can reuse the `SortOptions` type exported by the plugin to get completion and validation.
328
-
329
- ### In a `.ts` file
209
+ The package also exports `SortOptions`, `ImportGroup`, and `TypeImportStyle` for use in TypeScript configuration files:
330
210
 
331
211
  ```typescript
332
212
  import { type Config } from 'prettier';
@@ -334,7 +214,7 @@ import { type SortOptions } from 'prettier-plugin-sort';
334
214
 
335
215
  export default {
336
216
  plugins: ['prettier-plugin-sort'],
337
- importOrderGroups: [
217
+ esmImportGroups: [
338
218
  'builtin',
339
219
  'external',
340
220
  'internal',
@@ -342,46 +222,6 @@ export default {
342
222
  'sibling',
343
223
  'index',
344
224
  ],
345
- importOrderTypeImports: 'inline-last',
346
- packageJsonOrderExcludeKeys: ['contributes'],
225
+ esmImportTypeStyle: 'inline-last',
347
226
  } satisfies Config & SortOptions;
348
227
  ```
349
-
350
- ### In a `.js` file
351
-
352
- ```js
353
- /** @type {import('prettier').Config & import('prettier-plugin-sort').SortOptions} */
354
- const config = {
355
- plugins: ['prettier-plugin-sort'],
356
- importOrderGroups: [
357
- 'builtin',
358
- 'external',
359
- 'internal',
360
- 'parent',
361
- 'sibling',
362
- 'index',
363
- ],
364
- importOrderTypeImports: 'inline-last',
365
- packageJsonOrderExcludeKeys: ['contributes'],
366
- };
367
-
368
- export default config;
369
- ```
370
-
371
- The `ImportGroup` and `TypeImportsStyle` literal types are also exported if you only need those.
372
-
373
- ## Motivation
374
-
375
- Before adopting Prettier, I relied on IDE-native sorting features to keep my code organized. As I started switching between different IDEs, I wanted a portable, unified configuration, so I brought ESLint and Prettier into my projects.
376
-
377
- Prettier doesn't provide sorting out of the box. To sort imports I had to install `prettier-plugin-organize-imports`. To sort `package.json` I had to install `prettier-plugin-packagejson`. The fragmented experience bothered me.
378
-
379
- I ignored this for a long time while focusing on actual development. Recently, though, I needed to control how `import type` was inlined and found that `prettier-plugin-organize-imports` didn't support it. On top of that, `prettier-plugin-packagejson`, built on `sort-package-json`, carries many dependencies that are redundant for a plugin. That's when I decided to build my own.
380
-
381
- `prettier-plugin-sort` isn't meant to replace anything. It's about giving developers more options. Prettier is used almost entirely for JS/TS code, and every JS project has a `package.json`, so the plugin covers these two fundamental sorting tasks. The goal is to let JS developers work out of the box with minimal mental overhead (support for `tsconfig.json` sorting may be added in a future release). If you have other sorting needs, you can still install something like `prettier-plugin-css-order` alongside it. There's no conflict.
382
-
383
- ## Credits
384
-
385
- - `eslint-plugin-import`: https://github.com/import-js/eslint-plugin-import
386
- - `typescript-eslint`: https://github.com/typescript-eslint/typescript-eslint
387
- - `sort-package-json`: https://github.com/keithamus/sort-package-json