简单易用的自动修复导入排序。
eslint --fix][eslint-fix] 运行 – 无需新工具git diff 友好require][no-require]这适用于经常使用 [eslint --fix][eslint-fix](自动修复)并想完全忘记导入排序的人!
import React from "react"; import Button from "../Button"; import styles from "./styles.css"; import type { User } from "../../types"; import { getUser } from "../../api"; import PropTypes from "prop-types"; import classnames from "classnames"; import { truncate, formatNumber } from "../../utils";
⬇️
import classnames from "classnames"; import PropTypes from "prop-types"; import React from "react"; import { getUser } from "../../api"; import type { User } from "../../types"; import { formatNumber, truncate } from "../../utils"; import Button from "../Button"; import styles from "./styles.css";
npm install --save-dev eslint-plugin-simple-import-sort
ℹ️ 这是一个 ESLint 插件。👉 ESLint 入门指南
eslintrc: 在 .eslintrc.* 文件的 "plugins" 数组中添加 "simple-import-sort",并添加用于排序导入和导出的规则。默认情况下,ESLint 不解析 import 语法 – "parserOptions" 是启用它的示例。
{ "plugins": ["simple-import-sort"], "rules": { "simple-import-sort/imports": "error", "simple-import-sort/exports": "error" }, "parserOptions": { "sourceType": "module", "ecmaVersion": "latest" } }
[eslint.config.js (平铺配置)]: 导入 eslint-plugin-simple-import-sort,将其放入 plugins 对象中,并添加用于排序导入和导出的规则。使用平铺配置时,默认启用 import 语法。
import simpleImportSort from "eslint-plugin-simple-import-sort"; export default [ { plugins: { "simple-import-sort": simpleImportSort, }, rules: { "simple-import-sort/imports": "error", "simple-import-sort/exports": "error", }, }, ];
确保不要同时使用其他排序规则:
ℹ️ 注意:曾经有一个名为
"simple-import-sort/sort"的规则。从 6.0.0 版本开始,它被称为"simple-import-sort/imports"。
这个示例使用了 [eslint-plugin-import],这是可选的。
建议同时设置 [Prettier],以帮助格式化你的导入(以及所有其他代码)。
{ "parserOptions": { "sourceType": "module", "ecmaVersion": "latest" }, "plugins": ["simple-import-sort", "import"], "rules": { "simple-import-sort/imports": "error", "simple-import-sort/exports": "error", "import/first": "error", "import/newline-after-import": "error", "import/no-duplicates": "error" } }
"sourceType": "module" 和 "ecmaVersion": "latest" 是必需的,这样 ESLint 就不会将 import 和 export 报告为语法错误。simple-import-sort/imports 和 simple-import-sort/exports。这个插件并不适合所有人。让我解释一下。
长期以来,这个插件没有任何选项,这有助于保持它的简单性。
虽然人工字母排序和注释处理似乎适用于很多人,但导入分组更加困难。项目之间的差异太大,无法有一个通用的分组方式。
我决定只提供这一个选项,不再增加其他。以下是一些你无法配置的内容:
如果你想要更多选项,我建议使用 import/order 规则(来自 [eslint-plugin-import])。它有很多选项,维护者似乎有兴趣在合理的情况下扩展功能。
那么为什么这个插件存在呢?参见这个规则与 import/order 有何不同?。
如果我们开始为这个插件添加更多选项,它就不再是 eslint-plugin-<strong>simple</strong>-import-sort 了。最终它将没有存在的理由 – 最好将精力用于为 import/order 贡献。
我为自己制作了这个插件。我在许多小项目中使用它,我喜欢它。如果你也喜欢 – 我很高兴听到!但并非<strong>每个人</strong>都会喜欢它。这没关系。
这个插件应该与自动修复一起使用,最好是直接在你的编辑器中通过 ESLint 扩展使用,或者使用 [eslint --fix][eslint-fix]。
本节是为了了解排序的工作原理,而不是如何手动修复错误。请使用自动修复!
总结: 先分组,然后按字母顺序排序。
首先,插件查找所有导入的<strong>块</strong>。一个"块"是一系列导入语句,之间只有注释和空白。每个块单独排序。如果你想确保所有导入都在同一个块中,可以使用 import/first。
然后,每个块被<strong>分组</strong>成几个部分,每个部分之间有一个空行。
import "./setup": 副作用导入。(这些内部不排序。)import * as fs from "node:fs": 带有 node: 前缀的 Node.js 内置模块。import react from "react": 包(npm 包和<strong>不带</strong> node: 的 Node.js 内置模块)。import a from "/a": 绝对导入和其他导入,如 Vue 风格的 @/foo。import a from "./a": 相对导入。注意:上述分组的定义非常宽松。更多信息请参见[自定义分组]。
重新导出(带有 from 的导出)序列会被排序。其他类型的导出不会重新排序。
与导入不同,导出没有自动分组。相反,单独一行的注释会开始一个新组。这将分组留给你手动完成。
以下示例有 3 个组(一个包含 "x" 和 "y",一个包含 "a" 和 "b",一个包含 "./"):
export * from "x"; export * from "y"; // 这个注释开始一个新组。 /* 这个不会。 */ export * from "a"; // 这个也不会。 /* 这个 也不会 */ export * from "b"; /* 但这个会。 */ export * from "./";
每个组单独排序,组本身不排序 – 它们 保持在你写它们的位置。
没有分组注释的话,上面的示例最终会变成这样:
export * from "./"; /* 这个不会。 */ export * from "a"; // 这个也不会。 /* 这个 也不会 */ export * from "b"; export * from "x"; export * from "y";
在每个部分内,导入/导出按 from 字符串的字母顺序排序(另见"为什么按 from 排序?")。保持简单!看看这里的代码会有帮助:
const collator = new Intl.Collator("en", { sensitivity: "base", numeric: true, }); function compare(a, b) { return collator.compare(a, b) || (a < b ? -1 : a > b ? 1 : 0); }
换句话说,组内的导入/导出按字母顺序排序,不区分大小写,并像人类那样处理数字,在出现平局的情况下回退到传统的字符代码排序。更多信息请参阅Intl.Collator。注意:Intl.Collator以某种定义的顺序对标点符号进行排序。我不知道标点符号的排序顺序是什么,也不在乎。据我所知,标点符号没有有序的"字母表"。
对字母顺序规则有一个补充:目录结构。目录结构中较高层级文件的相对导入/导出排在较近层级之前——"../../utils"排在"../utils"之前,后者又排在"."之前。(简而言之,.和/排在任何其他(非空白、非控制)字符之前。".."和类似的排序如同"../,"(以避免"较短前缀排在前面"的排序概念)。)
如果对同一来源同时使用了import type和常规导入,类型导入排在前面。export type也是如此。(你可以将类型导入移到它们自己的组,如[自定义分组]中所述。)
// 副作用导入。(这些内部不进行排序。) import "./setup"; import "some-polyfill"; import "./global.css"; // 带有`node:`前缀的Node.js内置模块。 import * as fs from "node:fs"; // 包。 import type A from "an-npm-package"; import a from "an-npm-package"; import fs2 from "fs"; import b from "https://example.com/script.js"; // 绝对导入和其他导入。 import c from "/"; import d from "/home/user/foo"; import Error from "@/components/error.vue"; // 相对导入。 import e from "../.."; import type { B } from "../types"; import f from "../Utils"; // 不区分大小写。 import g from "."; import h from "./constants"; import i from "./styles"; // 不同类型的导出: export { a } from "../.."; export { b } from "/"; export { Error } from "@/components/error.vue"; export * from "an-npm-package"; export { readFile } from "fs"; export * as ns from "https://example.com/script.js"; // 这个注释分组了一些更多的导出: export { e } from "../.."; export { f } from "../Utils"; export { g } from "."; export { h } from "./constants"; export { i } from "./styles"; // 其他导出 – 插件不会触及这些,除了对大括号内的命名导出进行排序。 export var one = 1; export let two = 2; export const three = 3; export function func() {} export class Class {} export type Type = string; export { named, other as renamed }; export type { T, U as V }; export default whatever;
无论在哪个组中,导入的项目都按以下方式排序:
import { // 数字按其数值排序: img1, img2, img10, // 然后是其他所有内容,按字母顺序: k, L, // 不区分大小写。 m as anotherName, // 按"外部接口"名称"m"排序,而不是"anotherName"。 m as tie, // 但在出现平局时使用文件本地名称。 // 类型的排序就像`type`关键字不存在一样。 type x, y, } from "./x";
即使对于没有from的导出,导出项也会被排序(尽管导出语句本身不会相对于其他导出进行排序):
export { k, L, // 不区分大小写。 anotherName as m, // 按"外部接口"名称"m"排序,而不是"anotherName"。 // tie as m, // 对于导出,不可能有平局 – 所有导出必须是唯一的。 // 类型的排序就像`type`关键字不存在一样。 type x, y, }; export type { A, B, A as C };
乍一听,导入时a as b按a排序,而导出时按b排序可能听起来有悖常理。这样做的原因是选择最"稳定"的名称。在import { a as b } from "./some-file.js"中,as b部分是为了避免文件中的名称冲突,而不必更改some-file.js。在export { b as a }中,b as部分是为了避免文件中的名称冲突,而不必更改文件的导出接口。
有一个选项(参见[不适合所有人])称为groups,它对许多不同的用例都很有用。
groups是一个字符串数组的数组:
type Options = { groups: Array<Array<string>>; };
每个字符串都是一个正则表达式(带有[u标志])。这些正则表达式决定哪些导入去往何处。(记得转义反斜杠 – 是"\\w",而不是"\w",例如。)
内部数组用一个换行符连接;外部数组用两个换行符连接 – 创建一个空行。这就是为什么有两级数组 – 它让你选择在哪里有空行。
以下是一些你可以做的事情:
src/Button和@company/Button从(第三方)"包"组移出,放入它们自己的组。react移到最前面。./和../导入。如果你在考虑自定义分组是因为想移动非标准导入路径,如
src/Button(没有前导的./或../)和@company/Button– 考虑使用不像npm包的名称,如@/Button和~company/Button。这样你就不需要自定义分组,而且作为额外好处,对其他在代码库上工作的人来说可能会不那么混淮。如果你有非常复杂的要求,请参见issue #31获取一些提示。
注意:对于导出,分组是通过注释手动完成的 – 参见[导出]。
每个import都会根据from字符串与所有正则表达式进行匹配。导入最终会出现在匹配最长的正则表达式处。在平局的情况下,第一个匹配的正则表达式胜出。
如果一个导入最终出现在错误的位置 – 尝试让所需的正则表达式匹配
from字符串的更多部分,或使用否定前瞻((?!x))来排除其他组中的内容。
不匹配任何正则表达式的导入会被放在最后。
副作用导入的from字符串前面会添加\u0000(以\u0000开头)。你可以用"^\\u0000"来匹配它们。
类型导入的from字符串后面会添加\u0000(以\u0000结尾)。你可以用"\\u0000$"来匹配它们 – 但你可能需要更多内容来避免它们也被其他正则表达式匹配。
匹配同一正则表达式的所有导入会按照[排序顺序]中提到的方式内部排序。
这是groups选项的默认值:
[ // 副作用导入。 ["^\\u0000"], // 带有`node:`前缀的Node.js内置模块。 ["^node:"], // 包。 // 以字母(或数字或下划线)开头的内容,或者`@`后跟一个字母。 ["^@?\\w"], // 绝对导入和其他导入,如Vue风格的`@/foo`。 // 任何未在其他组中匹配的内容。 ["^"], // 相对导入。 // 任何以点开头的内容。 ["^\\."], ];
细心的读者可能会注意到,上述正则表达式匹配的内容比它们的注释所说的要多。例如,"@config"和"_internal"被匹配为包,但它们都不是有效的npm包名。".foo"被匹配为相对导入,但".foo"到底是什么意思?不过,使用更具体的规则并没有太多好处。所以保持简单!
参见[示例]以获取灵感。
当通过排序移动导入/导出时,它们的注释也会随之移动。注释可以放在导入/导出的上方(除了第一个 – 稍后会详细说明),或者在其行的开头或结尾。
示例:
// 导入块之前的注释 /* c1 */ import c from "c"; // c2 // b1 import b from "b"; // b2 // a1 /* a2 */ import a /* a3 */ from "a"; /* a4 */ /* 非a */ // 导入块之后的注释
⬇️
// 导入块之前的注释 // a1 /* a2 */ import a /* a3 */ from "a"; /* a4 */ // b1 import b from "b"; // b2 /* c1 */ import c from "c"; // c2 /* 非a */ // 导入块之后的注释
现在比较这两个例子:
// @flow import b from "b"; // a import a from "a";
// eslint-disable-next-line import/no-extraneous-dependencies import b from "b"; // a import a from "a";
// @flow 注释应该位于文件顶部(它为该文件启用 Flow 类型检查),与 "b" 导入无关。另一方面,// eslint-disable-next-line 注释却与 "b" 导入相关。即使是文档注释也可能是针对整个文件或第一个导入。因此,这个插件无法确定是否应该将注释移到第一个导入之上(但它知道 //a 注释属于 "a" 导入)。
基于这个原因,导入/导出块上下的注释永远不会被移动。如有需要,你需要自己手动移动。
围绕导入/导出项的注释遵循类似的规则 - 它们可以放在项目上方,或者在其行的开头或结尾。第一个项目或换行符之前的注释保留在开头,最后一个项目之后的注释保留在结尾。
<!-- prettier-ignore -->import { // 开头的注释 /* c1 */ c /* c2 */, // c3 // b1 b as /* b2 */ renamed , /* b3 */ /* a1 */ a /* not-a */ // 结尾的注释 } from "wherever"; import { e, d, /* d */ /* not-d */ // 尾随逗号后的结尾注释 } from "wherever2"; import {/* 开头的注释 */ g, /* g */ f /* f */} from "wherever3";
⬇️
<!-- prettier-ignore -->import { // 开头的注释 /* a1 */ a, // b1 b as /* b2 */ renamed , /* b3 */ /* c1 */ c /* c2 */// c3 /* not-a */ // 结尾的注释 } from "wherever"; import { d, /* d */ e, /* not-d */ // 尾随逗号后的结尾注释 } from "wherever2"; import {/* 开头的注释 */ f, /* f */g/* g */ } from "wherever3";
如果你对奇怪的空白感到疑惑 - 请参阅 "排序自动修复导致了一些奇怪的空白!"
说到空白 - 空行怎么处理?就像注释一样,很难知道排序后空行应该放在哪里。这个插件采用了一种简单的方法 - 导入/导出块中的所有空行都被移除,除了 /**/ 注释中的空行和在 [排序顺序] 中提到的组之间添加的空行。(注意:对于导出,组之间的空行完全由你决定 - 如果你在分组注释周围有空行,它们会被保留。)
(由于空行被移除,你可能会遇到与 lines-around-comment 和 padding-line-between-statements 规则略有不兼容的情况 - 我自己不使用这些规则,但我认为应该有解决方法。)
最后一条空白规则是,这个插件每行只放一个导入/导出。我从未见过有意将多个导入/导出放在同一行的真实项目。
require 吗?不支持。这是有意为之,以保持简单。对于 require 的排序,请使用其他排序规则,比如 import/order。或者考虑将使用 require 的代码迁移到 import。现在 import 已经得到很好的支持。
from 排序?一些其他的导入排序规则是根据 import 后的第一个名称排序,而不是 from 后的字符串。本插件有意按 from 字符串排序,以便于 git diff。
看看这个例子:
import { productType } from "./constants"; import { truncate } from "./utils";
现在假设你还需要 arraySplit 工具:
import { productType } from "./constants"; import { arraySplit, truncate } from "./utils";
如果按 import 后的第一个名称排序(在这种情况下是 "productType" 和 "arraySplit"),这两个导入现在会交换顺序:
import { arraySplit, truncate } from "./utils"; import { productType } from "./constants";
另一方面,如果按 from 字符串排序(就像本插件所做的那样),导入会保持相同的顺序。这可以防止导入在你添加和删除内容时跳来跳去,保持你的 git 历史清晰,并减少合并冲突的风险。
大部分情况下是安全的。
在 JavaScript 中,导入和重新导出可能会有副作用,因此改变它们的顺序可能会改变这些副作用执行的顺序。最佳实践是要么导入一个模块以获得其副作用,要么导入它导出的内容(并且绝不依赖重新导出的副作用)。
// 运行副作用的 `import`: import "some-polyfill"; // 获取 `someUtil` 的 `import`: import { someUtil } from "some-library";
仅用于副作用的导入会保持输入顺序。这些不会被排序:
import "b"; import "a";
既导出内容又运行副作用的导入很少见。如果你遇到这种情况 - 试着修复它,因为它会让所有使用这段代码的人感到困惑。如果无法修复,可以**忽略(部分)排序。**
另一个小问题是你有时需要手动移动注释 - 请参阅 注释和空白处理。
为了完整起见,对导入的导入/导出项进行排序始终是安全的:
import { c, b, a } from "wherever"; // 等同于: import { a, b, c } from "wherever";
注意:import {} from "wherever" 不被视为副作用导入。
最后,关于导出还有一点需要知道。考虑这种情况:
one.js:
export const title = "One"; export const one = 1;
two.js:
export const title = "Two"; export const two = 2;
reexport.js:
export * from "./one.js"; export * from "./two.js";
main.js:
import * as reexport from "./rexport.js"; console.log(reexport);
如果你运行 main.js 会发生什么?在 Node.js 和浏览器中,结果是:
{ one: 1, two: 2, }
注意 title 甚至不在对象中!这对排序来说是好事,因为这意味着重新排序 reexport.js 中的两个 export * from 导出是安全的 - 并不是最后一个导入"胜出",你也不会因为排序意外改变 title 的值。
然而,根据你使用的打包工具,这可能仍然会导致问题。以下是一些打包工具在编写时处理重复名称 title 的方式:
你可能会遇到一些奇怪的间距,例如逗号后缺少空格:
<!-- prettier-ignore -->import {bar, baz,foo} from "example";
排序是这个插件中简单的部分。处理空白和注释是困难的部分。自动修复有时可能会在导入/导出周围产生一些奇怪的间距。我建议使用 [Prettier] 或启用其他可自动修复的 ESLint 空白规则,而不是手动修复这些空格。更多信息请参见 [示例]。
空白可能会变得奇怪的原因是,这个插件重用并移动已存在的空白,而不是删除和添加新的空白。这是为了与其他处理空 白的 ESLint 规则保持兼容。
不太可能。这个规则的错误消息就是 "运行自动修复来排序这些导入!"为什么?为了积极鼓励你使用 [eslint --fix][eslint-fix](自动修复),而不是浪费时间手动做计算机能更好完成的事情。我见过有人痛苦地一个个修复其他规则产生的晦涩(且烦人的!)排序错误,却没意识到这些错误可以被自动修复。最后,不试图制作更详细的消息使得这个插件的代码更容易处理。
寻找这个规则的 /* eslint-disable */?请阅读所有关于**忽略(部分)排序的内容。**
import/order 有何不同?import/order 规则以前不支持字母顺序排序,但现在支持了。那么 eslint-plugin-simple-import-sort 还能带来什么呢?
import { a, b, c } from "."):eslint-plugin-import#1787"./img10.jpg" 排在 "./img2.jpg" 之后,而不是之前)import/order 问题:import/export ordering一些其他区别:
import/order 可能会提供多个(详见我可以在不使用自动修复的情况下使用吗?)。换句话说,本插件在编辑器中显示的下划线更多,而 import/order 在错误数量上更多。import/order 有多个不同的选项。目前还不清楚哪个更容易配置。但 eslint-plugin-simple-import-sort 尝试开箱即用地实现最大功能。dprint 一起使用?[dprint] 也会对导入和导出进行排序,但不会对它们进行分组。相反,它会保留你自己的分组方式。
首先要问自己的是 dprint 是否足够好。如果是,那你就少了一个需要担心的工具!
但是,如果你想强制分组,你仍然可以使用 eslint-plugin-simple-import-sort。然而,这两者在某些排序边缘情况下可能会略有分歧。因此,最好在你的 dprint 配置文件中关闭排序:
{ "typescript": { "module.sortImportDeclarations": "maintain" } }
来源:https://dprint.dev/plugins/typescript/config/
使用[自定义分组],将 groups 选项设置为只有一个内部数组。
例如,这是默认值但改为单个内部数组:
[["^\\u0000", "^node:", "^@?\\w", "^", "^\\."]];
(默认情况下,每个字符串都在自己的数组中(总共 5 个内部数组)– 这会在每个之间造成一个空行。)


免费创建高清无水印Sora视频
Vora是一个免费创建高清无水印Sora视频的AI工具


最适合小白的AI自动化工作流平台
无需编码,轻松生成可复用、可变现的AI自动化工作流

大模型驱动的Excel数据处理工具
基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公 式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。


AI辅助编程,代码自动修复
Trae是一种自适应的 集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。


AI论文写作指导平台
AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。


AI一键生成PPT,就用博思AIPPT!
博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。


AI赋能电商视觉革命,一站式智能商拍平台
潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。


企业专属的AI法律顾问
iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。


稳定高效的流量提升解决方案,助力品牌曝光
稳定高效的流量提升解决方案,助力品牌曝光


最新版Sora2模型免费使用,一键生成无水印视频
最新版Sora2模型免费使用,一键生成无水印视频
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号