@1adybug/prettier-plugin-sort-imports
v0.0.30
Published
一个 Prettier 插件,用于对 JavaScript/TypeScript 文件的导入语句进行分组和排序
Maintainers
Readme
Prettier Plugin Sort Imports
一个功能强大的 Prettier 插件,用于对 JavaScript/TypeScript 文件的导入语句进行智能分组和排序。
特性
- ✅ 智能排序:支持对导入模块和导入内容进行排序
- ✅ 灵活分组:自定义分组规则,支持按模块类型、路径等分组
- ✅ TypeScript 支持:完整支持 TypeScript 的
type导入 - ✅ 注释保留:注释会跟随对应的导入语句移动
- ✅ 副作用处理:可配置副作用导入的排序行为
- ✅ 未使用导入删除:可选的自动删除未使用的导入功能
- ✅ 工厂函数模式:支持在配置文件中使用自定义函数
快速开始
安装
npm install @1adybug/prettier-plugin-sort-imports --save-dev基础配置
在 prettier.config.mjs 中添加插件:
export default {
plugins: ["@1adybug/prettier-plugin-sort-imports"],
}运行
npx prettier --write "src/**/*.{js,ts,jsx,tsx}"使用示例
基本排序
import "./styles.css"自定义分组和排序
// prettier.config.mjs
import { createPlugin } from "@1adybug/prettier-plugin-sort-imports"
export default {
plugins: [
createPlugin({
// 自定义分组:按模块类型分组
getGroup: statement => {
if (statement.path.startsWith("react")) return "react"
if (!statement.path.startsWith(".")) return "external"
return "local"
},
// 指定分组顺序
sortGroup: (a, b) => {
const order = ["react", "external", "local"]
return order.indexOf(a.name) - order.indexOf(b.name)
},
// 在分组之间添加空行
groupSeparator: "",
}),
],
}结果:
import "./styles.css"API 文档
类型定义
ImportContent
导入内容的定义:
interface ImportContent {
/** 导入的内容的名称 */
name: string
/** 导入的内容的别名 */
alias?: string
/** 导入的内容的类型,只有明确在导入前加入了 type 标记的才属于 type 类型 */
type: "type" | "variable"
}ImportStatement
导入语句的定义:
interface ImportStatement {
/** 导入的模块路径,可以是相对路径或绝对路径 */
path: string
/** 是否是导出语句,默认为 false */
isExport: boolean
/** 是否是副作用导入,默认为 false */
isSideEffect: boolean
/** 导入的内容 */
importContents: ImportContent[]
}Group
分组定义:
interface Group {
/** 分组名称,默认为 default */
name: string
/** 是否是副作用分组,默认为 false */
isSideEffect: boolean
/** 分组对应的导入语句列表 */
importStatements: ImportStatement[]
}PluginConfig
插件配置:
interface PluginConfig {
/** 自定义分组函数 */
getGroup?: (importStatement: ImportStatement) => string
/** 自定义分组排序函数 */
sortGroup?: (a: Group, b: Group) => number
/** 自定义导入语句排序函数 */
sortImportStatement?: (a: ImportStatement, b: ImportStatement) => number
/** 自定义导入内容排序函数 */
sortImportContent?: (a: ImportContent, b: ImportContent) => number
/** 分组之间的分隔符 */
groupSeparator?: string | ((group: Group, index: number) => string | undefined)
/** 是否对副作用导入进行排序,默认为 false */
sortSideEffect?: boolean
/** 是否删除未使用的导入,默认为 false */
removeUnusedImports?: boolean
/** 是否为 Node.js 内置模块自动添加/移除 node: 前缀 */
nodeProtocol?: "add" | "remove"
}配置选项
方式 1:简单配置
通过 Prettier 配置文件配置基本选项:
export default {
plugins: ["@1adybug/prettier-plugin-sort-imports"],
sortSideEffect: false, // 是否对副作用导入排序
groupSeparator: "", // 分组分隔符
removeUnusedImports: false, // 是否删除未使用的导入
nodeProtocol: "add", // "add" 为添加 node: 前缀("remove" 为移除)
}方式 2:高级配置(工厂函数)
使用 createPlugin 函数可以传递自定义函数:
import { createPlugin } from "@1adybug/prettier-plugin-sort-imports"
export default {
plugins: [
createPlugin({
getGroup: statement => {
/* 自定义分组逻辑 */
},
sortGroup: (a, b) => {
/* 自定义排序 */
},
sortImportStatement: (a, b) => {
/* 自定义排序 */
},
sortImportContent: (a, b) => {
/* 自定义排序 */
},
groupSeparator: "",
sortSideEffect: true,
removeUnusedImports: false,
nodeProtocol: "add",
}),
],
}方式 3:自定义插件模块
创建自定义插件模块以获得更好的组织性和可复用性:
步骤 1:创建自定义插件文件 prettier-plugin-sort-imports.mjs:
// prettier-plugin-sort-imports.mjs
import { createPlugin } from "@1adybug/prettier-plugin-sort-imports"
export default createPlugin({
// 自定义分组逻辑
getGroup: statement => {
const path = statement.path
// React 及相关库
if (path.startsWith("react") || path.startsWith("@react")) return "react"
// UI 库
if (path.includes("antd") || path.includes("@mui") || path.includes("chakra")) return "ui"
// 工具库
if (path.includes("lodash") || path.includes("ramda") || path.includes("date-fns")) return "utils"
// 外部包 (node_modules)
if (!path.startsWith(".") && !path.startsWith("@/")) return "external"
// 内部别名 (@/)
if (path.startsWith("@/")) return "internal"
// 相对导入
return "relative"
},
// 定义分组顺序
sortGroup: (a, b) => {
const order = ["react", "external", "ui", "utils", "internal", "relative"]
return order.indexOf(a.name) - order.indexOf(b.name)
},
// 自定义导入内容排序
sortImportContent: (a, b) => {
// 类型在前,变量在后
if (a.type !== b.type) return a.type === "type" ? -1 : 1
// 同类型内按字母顺序
const aName = a.alias ?? a.name
const bName = b.alias ?? b.name
return aName.localeCompare(bName)
},
// 在分组间添加空行
groupSeparator: "\n",
// 排序副作用导入
sortSideEffect: true,
})步骤 2:在 prettier.config.mjs 中使用自定义插件:
// prettier.config.mjs
export default {
plugins: ["./prettier-plugin-sort-imports.mjs"],
// 其他 prettier 选项...
semi: false,
tabWidth: 4,
}此方法的优点:
- ✅ 可复用:在多个项目间共享相同配置
- ✅ 版本控制:在 git 中跟踪你的导入排序规则
- ✅ 易维护:将复杂逻辑从 prettier 配置中分离
- ✅ 团队协作:团队成员间保持一致的导入排序规则
方式 4:插件兼容性
使用 createPlugin 的 otherPlugins 参数与其他 Prettier 插件合并,避免冲突:
import { createPlugin } from "@1adybug/prettier-plugin-sort-imports"
import * as tailwindPlugin from "prettier-plugin-tailwindcss"
export default {
plugins: [
createPlugin({
// 你的导入排序配置
getGroup: statement => {
if (statement.path.startsWith("react")) return "react"
if (!statement.path.startsWith(".")) return "external"
return "local"
},
groupSeparator: "\n",
// 要合并的其他 Prettier 插件(仅支持 Plugin 对象)
otherPlugins: [
tailwindPlugin, // 直接导入插件
// 根据需要添加更多插件...
],
// 其他插件的配置选项
prettierOptions: {
// TailwindCSS 插件选项
tailwindConfig: "./tailwind.config.js",
tailwindFunctions: ["clsx", "cn", "cva"],
tailwindAttributes: ["class", "className", "ngClass", ":class"],
// 其他插件选项可以在这里配置...
},
}),
],
}重要说明:
otherPlugins只接受导入的 Plugin 对象,不支持字符串插件名称- 你必须自己导入插件以确保正确的模块解析
- 这种方法避免了复杂的模块加载问题,给你完全的控制权
插件执行顺序:
- 其他插件按照在
otherPlugins数组中出现的顺序执行 - 导入排序始终最后执行以确保兼容性
配置传递:
prettierOptions中的选项会传递给所有其他插件- 这允许其他插件即使在合并时也能接收到它们的配置
removeUnusedImports
是否删除未使用的导入,默认为 false。
默认行为(false):保留所有导入。
开启后(true):自动分析代码并删除未使用的导入。
// 排序前
// 排序后(开启 removeUnusedImports)
import React, { useState } from "react"
import { Button } from "antd"
function MyComponent() {
const [count, setCount] = useState(0)
return <Button>Click me</Button>
}
function MyComponent() {
const [count, setCount] = useState(0)
return <Button>Click me</Button>
}注意事项:
- 副作用导入(如
import "./styles.css")不会被删除 - 导出语句(如
export { x } from "module")不会被删除 - 分析基于 AST,会识别代码中实际使用的标识符
- 支持识别 JSX 组件、TypeScript 类型引用等
nodeProtocol
是否为 Node.js 内置模块自动添加/移除 node: 前缀,默认为 undefined(不处理)。
"add":自动添加node:前缀"remove":自动移除node:前缀
// 排序前
// nodeProtocol: "remove"
import fs from "fs"
// nodeProtocol: "add"
import fs from "node:fs"
import path from "node:path"
import path from "path"sortSideEffect
是否对副作用导入进行排序,默认为 false。
默认行为(false):副作用导入作为分隔符,分隔符之间的导入独立排序。
import "f-side-effect"
import "f-side-effect"开启后(true):副作用导入也会参与排序。
import "f-side-effect"
import "f-side-effect"groupSeparator
分组之间的分隔符,默认为 undefined(无分隔符)。
可以是字符串或函数:
// 字符串:在所有分组间添加空行
groupSeparator: ""
// 函数:灵活控制
groupSeparator: (group, index) => {
// 第一个分组不添加分隔符
if (index === 0) return undefined
// 其他分组添加空行
return ""
}默认排序规则
导入内容排序
默认行为(未提供自定义 sortImportContent 时):
- 默认导入始终在最前面
- 命名空间导入(
import * as)在默认导入之后 - 命名导入按照
type类型优先,然后按最终导入名称字母顺序排序
自定义行为:
如果提供了自定义的 sortImportContent 函数,插件会完全遵循你的排序逻辑:
createPlugin({
// 完全按字母顺序,不区分 type 和 variable
sortImportContent: (a, b) => {
const aName = a.alias ?? a.name
const bName = b.alias ?? b.name
return aName.localeCompare(bName)
},
})导入语句排序
导入语句按模块路径的字母顺序排序:
注释处理
注释会跟随它们所附加的导入语句一起移动:
实现细节
核心模块
1. 类型定义 (src/types.ts)
定义所有接口类型:ImportContent、ImportStatement、Group、PluginConfig 和各种函数类型。
2. 解析器 (src/parser.ts)
使用 @babel/parser 解析源代码,提取导入/导出语句:
- 解析源代码为 AST
- 遍历 AST 找到所有 import 和 export 语句
- 识别导入类型:默认导入、命名导入、命名空间导入、副作用导入
- 识别 TypeScript 的
type导入标记 - 提取并保留导入语句上方的注释
- 记录导入语句的位置信息
3. 排序器 (src/sorter.ts)
实现分组和排序逻辑:
- 根据
getGroup函数对导入语句进行分组 - 如果
sortSideEffect为 false,将副作用导入作为分隔符处理 - 使用各种排序函数对分组、导入语句、导入内容进行排序
- 支持完全自定义的排序逻辑
4. 格式化器 (src/formatter.ts)
将排序后的导入语句转换回代码字符串:
- 根据
ImportStatement生成对应的 import/export 代码 - 处理默认导入、命名导入、命名空间导入的格式
- 处理
type导入的格式 - 根据
groupSeparator配置在分组之间插入分隔符 - 保持注释关联
5. 插件入口 (src/index.ts)
实现 Prettier 插件标准接口:
- 扩展现有的 babel/typescript 解析器
- 支持工厂函数模式
- 集成解析器、排序器、格式化器
- 只处理文件开头的连续导入语句块
6. 分析器 (src/analyzer.ts)
分析代码中使用的标识符并过滤未使用的导入:
- 使用
@babel/traverse遍历 AST - 收集代码中使用的所有标识符(变量、函数、JSX 组件、类型引用等)
- 过滤导入语句,只保留在代码中使用的导入内容
- 支持识别别名、默认导入、命名空间导入等
技术栈
- 构建工具:rslib
- 解析器:@babel/parser
- AST 遍历:@babel/traverse
- AST 类型:@babel/types
- 插件系统:Prettier 3.x
工厂函数模式的优势
Prettier 原生无法接受函数作为配置参数(因为配置需要序列化)。本插件通过工厂函数模式巧妙地解决了这个问题:
// 工厂函数在配置文件中被调用,返回一个插件实例
import { createPlugin } from "@1adybug/prettier-plugin-sort-imports"
export default {
plugins: [
createPlugin({
// 可以传递函数!
getGroup: statement => {
/* ... */
},
}),
],
}这样既保持了配置的灵活性,又不违反 Prettier 的配置系统限制。
注意事项
只处理文件开头的连续导入/导出语句块
- 遇到非导入/导出语句后,后续的导入不会被处理
支持的文件类型
- JavaScript:
.js,.jsx,.mjs,.cjs,.mjsx,.cjsx - TypeScript:
.ts,.tsx,.mts,.cts,.mtsx,.ctsx
- JavaScript:
不支持 CommonJS 的
require语句- 只支持 ES6 模块语法(import/export)
自定义排序函数
- 提供自定义
sortImportContent时,插件会完全遵循你的逻辑 - 不会强制默认导入在前或 type 在前等规则
- 提供自定义
项目状态
✅ 完成并可用
所有核心功能已实现并通过测试,插件可以正常工作并集成到任何使用 Prettier 的项目中。
已验证场景
- ✅ 基本导入排序(按字母顺序)
- ✅ 副作用导入作为分隔符
- ✅ 副作用导入排序(开启选项)
- ✅ 注释跟随导入语句
- ✅ TypeScript type 导入优先
- ✅ 默认导入和命名空间导入位置
- ✅ 混合导入(默认 + 命名)
- ✅ 导入内容按 alias 排序
- ✅ 自定义排序逻辑
下一步(可选)
- 添加单元测试(使用 Jest 或 Vitest)
- 添加 CI/CD 配置
- 发布到 npm
- 添加更多示例
- 支持更多配置选项(如忽略特定导入)
License
MIT
