简单易用的自动修复导入排序。
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 个内部数组)– 这会在每个之间造成一个空行。)
AI小说写作助手,一站式润色、改写、扩写
蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。
字节跳动发布的AI编程神器IDE
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能 ,是提升开发效率的理想工具。
全能AI智能助手,随时解答生活与工作的多样问题
问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在 日常生活中提高效率,轻松管理个人事务。
实时语音翻译/同声传译工具
Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满 足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程 解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文 档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号