eslint-plugin-light
v1.0.24
Published
ESLint 插件
Maintainers
Readme
ESLint Plugin Light
简单、易用的 ESLint 插件库。
1. 作者
novlan1
2. 安装
首先要安装 ESLint。
pnpm i eslint -D然后安装本插件 eslint-plugin-light。
npm i eslint-plugin-light -D3. 使用
在 .eslintrc 配置文件的 plugins 中增加本插件 eslint-plugin-light,或者省略 eslint-plugin- 前缀。
{
"plugins": [
"light"
]
}然后配置你想使用的规则。
{
"rules": {
"light/rule-name": 2
}
}也可以使用本工具提供的扩展。
{
"extends": ["plugin:light/recommended"],
}4. 规则
4.1. valid-vue-comp-import
禁止从 js 文件中加载 Vue 组件。
比如,
- 导入地址是
js/ts文件
import SomeComp from 'src/local-component/ui/pages/user/account-manage/index.js';
// 或者省略了js/ts文件后缀
import SomeComp from 'src/local-component/ui/pages/user/account-manage/index';如果加了 --fix,会被转换为:
import SomeComp from 'src/local-component/ui/pages/user/account-manage/xxx.vue';注意上面的xxx.vue是从index.js中分析得到的原始文件路径。
- 导入一个目录,但目录存在
index.js,这时候不管存不存在index.vue,uni-app转换都会失败
import SomeComp from 'src/local-component/ui/pages/user/account-manage';可转换为:
import SomeComp from 'src/local-component/ui/pages/user/account-manage/xxx.vue';- 具名导入
import {
AComp,
BComp,
CComp,
DComp,
} from './comp';可转换为:
import AComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-new/comp/a.vue';
import BComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-new/comp/b.vue';
import CComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-new/comp/c.vue';
import DComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-new/comp/d.vue';4.2. no-plus-turn-number
禁止在 vue 的 template 中用 + 号转换字符串为数字
比如:
<ScheduleItem
:child-id="+childId"
/>如果加了 --fix,会被转化成:
<ScheduleItem
:child-id="parseInt(childId, 10)"
/>4.3. no-complex-key
不要在vue模板中使用复杂的key。包括:
- 字符串拼接,如:
:key="`hold` + index"`- 模板字符串,如:
:key="`hold-${index}`"- 将
key提到一个函数中,如:
:key="getHoldKey(index)"
getHoldKey(index) {
return `hold${index}`
}最佳方式其实是提前处理好数据,这样性能会更好,也避免了key重复。
getData() {
items = items.map((item,index) => ({
...item,
key: `hold-${index}`
}))
}uni-app中,key重复的话,会造成挂载在组件上面的事件参数为undefined,从而不成功。
4.4. json-parse-try-catch
JSON.parse 应该加 try catch。
默认配置会排除下面情况:
JSON.parse(JSON.stringify(abc));可以配置 strict 参数为 true,开启检查。
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/json-parse-try-catch': [2, { strict: true }],
},
}4.5. no-import-all-in-one-api
使用 src/api 子仓库时, 不应该导入全部接口,而应该按需引入。
Bad case:
// bad
import { pubg_fateClient } from 'src/api/git.aow.com/itrpcprotocol/esport/esport_cgi/pubg_fate/pubg_fate.http';Good case:
// good
import { QueryGameListHomePageClient } from 'src/api/git.aow.com/itrpcprotocol/esport/esport_cgi/pubg_fate/pubg_fate/QueryGameListHomePage.http';Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-import-all-in-one-api': 2,
},
}还可以配置 excludes 数组,指定排除哪些接口,不推荐使用。
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-import-all-in-one-api': [2, {
excludes: [
'src/api/git.aow.com/itrpcprotocol/esport/esport_cgi/pubg_fate/pubg_fate.http',
]
}],
},
}下面是一个案例,根据这个规则,对一个线上项目进行改造。仅仅改动了几个文件的几行引入语句,就减少了主包 50KB,效果立竿见影。
之前:
之后:
改动的几个文件:
4.6. no-js-file
运行时文件不允许使用 js/jsx,只允许 ts/tsx。子工程的 config.js 会自动排除。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-js-file': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------- | ----------------------- | ------------------------------------------------------------- |
| include | 检查的列表,glob 模式 | ['src/{project,local-component,local-logic}/**/*.{js,jsx}'] |
| exclude | 排除的列表,glob 模式 | ['src/project/*/config.js'] |
4.7. valid-file-name
文件命名格式只允许使用 kebab-case。子工程的 App.vue 会自动排除。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/valid-file-name': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------- | ----------------------- | --------------------------- |
| include | 检查的列表,glob 模式 | ['src/**/*'] |
| exclude | 排除的列表,glob 模式 | ['src/project/*/App.vue'] |
4.8. no-multiple-script
禁止 Vue 文件中存在多个 <script>。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-multiple-script': 2,
},
}4.9. valid-spelling
校验正确的拼写,比如不能用"帐号",要使用"账号",不能使用"登陆",要使用"登录"。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/valid-spelling': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| -------- | ------------------------ | -------------------------------- |
| spelling | 错误拼写和正确拼写的映射 | { 帐号: '账号', 登陆: '登录' } |
4.10. valid-shaohou
校验正确的"稍候"和"稍后",稍候后面不加词,稍后后面需要加词,比如"请稍候/请稍后再试"。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/valid-shaohou': 2,
},
}4.11. classname-per-line
限制 Vue 中每行只有1个CSS类名,可以配置阈值。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/classname-per-line': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------ | -------------------------------- | ------ |
| counts | 检查阈值,小于阈值时,可以在一行 | 3 |
4.12. img-v-lazy
强制 img 标签使用 v-lazy 而不是 :src,可用于 Vue 项目中。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/img-v-lazy': 2,
},
}4.13. no-direct-img-src
在 jsx/tsx 中禁止直接使用 img src,必须使用封装的 CdnImage 组件,可用于 React 项目中。
与 img-v-lazy 规则 类似,都是为了防止直接使用 COS 图片,而不是 CDN 图片。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-direct-img-src': 2,
// 传入参数
'light/no-direct-img-src': [2, {
componentName: 'MyCdnImage',
}],
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------------- | ---------------------- | ---------- |
| componentName | 提示词中的自定义组件名 | CdnImage |
4.14. no-todo-comment
不允许存在 TODO。可防止调试代码被意外带到线上。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-todo-comment': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------- | ---------- | ------- |
| keyword | 检查关键词 | TODO: |
4.15. no-non-index-import
限制只能从 pmd-* 包的 lib 目录下的模块第一层 index 引入,禁止从非 index 文件引入。
目的是为了保证模块导入的规范性,避免直接导入包内的具体实现文件,便于包的维护和重构。
允许的导入格式:
// ✅ 直接导入模块(隐式index)
import Toast from 'pmd-widget/lib/toast';
import EventBus from 'pmd-tools/lib/e-bus';
// ✅ 显式导入index文件
import Toast from 'pmd-widget/lib/toast/index';
import EventBus from 'pmd-tools/lib/e-bus/index';
// ✅ 导入目录(隐式index)
import * as Utils from 'pmd-tools/lib/utils/';禁止的导入格式:
// ❌ 直接导入非index文件
import ToastComponent from 'pmd-widget/lib/toast/Toast.vue';
import helper from 'pmd-tools/lib/utils/helper.js';
// ❌ 多级目录的非index文件
import DeepComponent from 'pmd-widget/lib/toast/components/Modal.vue';Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-non-index-import': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------- | ------------------------------ | ------- |
| exclude | 排除的导入路径前缀数组 | [] |
例如,排除特定包的检查:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-non-index-import': [2, {
exclude: [
'pmd-special/lib',
]
}],
},
}4.16. no-decimal-in-brackets
禁止在方括号类名中使用小数点。背景:
小程序中不能使用
pr-[.28rem]这种,可以使用pr-1.12,同时在tailwind.config.js中配置下theme.extend.padding = {1.12: '.28rem'}。原因是
uni-app会把class中的[.解析成\[\放到wxss中,导致编译错误。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-decimal-in-brackets': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------ | -------- | ----------------------------------- |
| regexp | 匹配正则 | /\[[^\](]*\.\d+[a-zA-Z]+[^\]]*\]/ |
4.17. valid-pmd-import
禁止在 packages/xx 目录下引入对应的 pmd-xx 包,需要使用相对路径。
这个规则主要用于 monorepo 项目中,防止包内部引入自己的发布版本,而是使用相对路径引入源码,避免循环依赖和版本不一致问题。
比如,在 packages/components 目录下:
// ❌ 错误:引入自己的包名
import { Button } from 'pmd-components';
const utils = require('pmd-components/lib/utils');
// ✅ 正确:使用相对路径
import { Button } from '../src';
const utils = require('../../src/utils');如果加了 --fix,会被自动转换为相对路径。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/valid-pmd-import': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------- | ---------------- | ------- |
| baseDir | 包源码的基础目录 | 'src' |
例如,如果你的包源码不在 src 目录:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/valid-pmd-import': [2, {
baseDir: 'lib'
}],
},
}该规则会检查以下导入方式:
- ES6
import语句 - CommonJS
require()调用 - 动态
import()表达式 export ... from语句
4.18. no-src-imports-in-components
4.18.1. 规则说明
禁止在指定目录下使用特定前缀的导入路径,强制使用相对路径。这个规则主要用于确保某些目录(如 src/components)中的代码可以被独立发布为 npm 包,不依赖项目的绝对路径导入。
4.18.2. 为什么需要这个规则?
当你的项目中某些目录(如 src/components)需要被发布为独立的 npm 包时,这些目录中的代码不应该使用项目特定的绝对路径(如 src/...),而应该使用相对路径。这样可以确保:
- 代码的可移植性
- 包的独立性
- 避免发布后的路径解析问题
4.18.3. 配置选项
{
restrictedPaths: string[], // 需要限制的目录路径列表
bannedPrefixes: string[], // 禁止使用的导入路径前缀列表
message?: string // 自定义错误消息(可选)
}4.18.4. 使用示例
4.18.4.1. 基本用法(使用默认配置)
// .eslintrc.js
module.exports = {
rules: {
'light/no-src-imports-in-components': 'error',
},
};默认配置:
restrictedPaths:['src/components']bannedPrefixes:['src']
4.18.4.2. 自定义配置
// .eslintrc.js
module.exports = {
rules: {
'light/no-src-imports-in-components': ['error', {
// 指定多个需要限制的目录
restrictedPaths: ['src/components', 'src/lib', 'packages/ui'],
// 指定多个禁止的导入前缀
bannedPrefixes: ['src', '@/', '~/'],
// 可选:自定义错误消息
message: '该目录将被发布为独立包,请使用相对路径导入',
}],
},
};4.18.4.3. 只检查特定目录
// .eslintrc.js
module.exports = {
rules: {
'light/no-src-imports-in-components': ['error', {
restrictedPaths: ['src/components'],
bannedPrefixes: ['src'],
}],
},
};4.18.5. 错误示例
假设配置为:
{
restrictedPaths: ['src/components'],
bannedPrefixes: ['src']
}在 src/components/Button/Button.tsx 中:
// ❌ 错误:使用了 src 开头的导入
import { back } from 'src/app/route/route';
import { utils } from 'src/utils/helper';
// ✅ 正确:使用相对路径
import { back } from '../../app/route/route';
import { utils } from '../../utils/helper';
// ✅ 正确:导入 npm 包
import React from 'react';
import { Button } from 'antd';4.18.6. 配置参数详解
4.18.6.1. restrictedPaths
类型:string[]
默认值:['src/components']
指定需要应用此规则的目录列表。规则会检查文件路径是否包含这些目录。
示例:
restrictedPaths: ['src/components', 'src/lib', 'packages/ui']4.18.6.2. bannedPrefixes
类型:string[]
默认值:['src']
指定禁止使用的导入路径前缀列表。任何以这些前缀开头的导入都会被标记为错误。
示例:
bannedPrefixes: ['src', '@/', '~/']4.18.6.3. message
类型:string
默认值:无(使用默认错误消息)
自定义错误消息。如果不设置,将使用默认消息模板。
示例:
message: '该目录将被发布为独立的 npm 包,请使用相对路径导入'4.19. no-complex-style-class
禁止在 Vue 模板的 :style 或 :class 绑定中直接使用函数调用、模板字符串等复杂表达式。
背景:
uni-app Vue2 小程序中,
:style和:class的顶层表达式不支持函数调用和模板字符串等语法。例如:style="tool._style(xxx)"或:class="\${prefix}-item`"` 在小程序中会解析失败。应使用计算属性或字符串拼接
'' + xxx代替。注意:在数组元素、对象属性值、条件表达式分支中使用函数调用是允许的,例如
:class="[getClass(), 'base']"和:style="flag ? getStyle() : ''"在小程序中可以正常工作。
Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-complex-style-class': 2,
},
}错误示例:
<!-- ❌ 顶层函数调用 -->
<div :style="tool._style(xxx)" />
<div :class="getClass(item)" />
<!-- ❌ 顶层模板字符串 -->
<div :style="`color: ${color}`" />
<div :class="`${prefix}-item`" />正确示例:
<!-- ✅ 变量引用 -->
<div :style="myStyle" />
<div :class="item.className" />
<!-- ✅ 对象表达式(值为变量) -->
<div :style="{ color: myColor }" />
<div :class="{ active: isActive }" />
<!-- ✅ 字面量数组 -->
<div :class="['a', 'b']" />
<!-- ✅ 条件表达式(值为变量) -->
<div :style="flag ? styleA : styleB" />
<!-- ✅ 数组元素中的函数调用 -->
<div :class="[utils.getClass(classPrefix, 'medium'), tClassImage]" />
<!-- ✅ 条件表达式分支中的函数调用 -->
<div :style="inChat ? imageStyle(item) : ''" />
<!-- ✅ 数组 + 条件混合 -->
<div :class="[classPrefix, inChat ? classPrefix + '--chatting' : '', getFileTypeClass(inChat, files)]" />
<!-- ✅ 对象属性值中的函数调用 -->
<div :style="{ color: getColor() }" />4.20. no-barrel-import
禁止从指定包的入口(barrel / 桶文件)直接 import,必须按子路径引入。
背景:
从包入口直接
import时,webpack 解析阶段会把整个包(含file-loader引入的图片资源)全量打入产物,tree-shaking 也无法消除这部分体积。按子路径引入可以只打包真正用到的子模块,显著减小产物体积。
默认目标包是 press-pix,其目录结构为:包根目录下直接是 kebab-case 组件目录,每个组件目录里有同名入口文件,并且是 export default。
错误示例:
// ❌ 从 barrel 入口引入
import { CdnImage } from 'press-pix';正确示例:
// ✅ 按子路径引入
import CdnImage from 'press-pix/cdn-image/cdn-image';如果加了 --fix,命名导入会被自动转换为子路径的默认导入:
// 转换前
import { CdnImage, IconButton } from 'press-pix';
// 转换后
import CdnImage from 'press-pix/cdn-image/cdn-image';
import IconButton from 'press-pix/icon-button/icon-button';Usage:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-barrel-import': 2,
},
}配置选项:
| 字段 | 说明 | 默认值 |
| ------------- | -------- | --------- |
| packages | 受限的包名列表(barrel 入口) | ['press-pix'] |
| pathTemplate | 子路径模板,支持 {pkg} 和 {name} 占位符 | '{pkg}/{name}/{name}' |
| defaultExport | 子模块是否默认导出(true 时 --fix 会写成 default import)| true |
| mapping | 个别命名的硬编码覆盖映射({ 导入名: 完整子路径 } | {} |
自定义配置示例:
// .eslintrc.js
module.exports = {
plugins: [
'light',
],
rules: {
'light/no-barrel-import': [2, {
packages: ['press-pix', 'some-other-pkg'],
pathTemplate: '{pkg}/{name}/{name}',
defaultExport: true,
mapping: {
// 对于命名不规则的子模块,可以单独指定路径
SpecialIcon: 'press-pix/icons/special',
},
}],
},
}