eslint-plugin-unicorn 的 no-exports-in-scripts 规则:禁止在脚本文件中使用 export
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
本篇文章围绕 eslint-plugin-unicorn 的no-exports-in-scripts规则展开,讲解它如何识别以 shebang(#!)开头的可执行脚本、为何要禁止脚本中出现 ESM 导出语句,以及它与普通模块、CommonJS 脚本之间的边界。读完本文,你将掌握该规则的判定原理、完整适用场景、配置方式与测试用例细节,能够直接在项目中启用并正确规避误报。
规则概述:脚本与模块的边界
no-exports-in-scripts是一条风格建议类(suggestion)规则,其核心主张是:脚本文件应当被直接执行,而不是被当作模块导入。
在 Node.js 生态中,一个以 shebang 开头的文件(如#!/usr/bin/env node)通常意味着它是一个 CLI 工具或可执行入口。如果在这样的文件里再出现export语句,就会同时混入"脚本边界"与"模块边界"两种语义,读者难以判断这个文件到底是用来直接运行的,还是设计为供其他模块导入的。这正是规则文档(docs/rules/no-exports-in-scripts.md)所强调的核心动机。
规则源码 rules/no-exports-in-scripts.js 中,规则的meta.docs.description为Disallow exports in scripts.,meta.type为suggestion,且未提供任何可配置选项(schema: [])——这意味着该规则开箱即用、无需调参。
判定原理:只看第一行是否为 shebang
该规则的实现非常精简,核心判定逻辑只有一步:检查文件的第一行是否以#!开头。
const create = context => { const {sourceCode} = context; if (!sourceCode.lines[0].startsWith('#!')) { return; } context.on(['ExportNamedDeclaration', 'ExportDefaultDeclaration', 'ExportAllDeclaration'], node => ({ node, messageId: MESSAGE_ID, })); };对应源码位置:rules/no-exports-in-scripts.js。关键点如下:
- 通过
sourceCode.lines[0].startsWith('#!')判断文件是否为脚本。只有第一行恰好以#!开头才触发检查,这是全部判定依据,没有任何其他启发式条件。 - 一旦确认是脚本,规则会监听三种导出节点类型:
ExportNamedDeclaration:export const foo = 1;、export {foo};、export {foo} from './foo.js';、export type Foo = string;(TypeScript 中导出类型同样走此节点);ExportDefaultDeclaration:export default foo;;ExportAllDeclaration:export * from './foo.js';、export * as foo from './foo.js';。
- 命中后报告统一的消息:
Do not use exports in scripts.(消息 ID 为no-exports-in-scripts,见 rules/no-exports-in-scripts.js)。
由于规则挂载的meta.languages为['js/js'],它默认只作用于 JavaScript 文件;TypeScript 场景需要通过languageOptions指定 TypeScript 解析器(测试用例中即为如此处理,见下文)。
示例:失败与通过的代码
规则文档给出了三组最直观的示例,本文将其完整保留并补充说明。
带 shebang 的脚本中出现导出 —— 失败:
#!/usr/bin/env node export const foo = 1;第一行以#!开头,文件被判定为脚本,随后出现的export const foo = 1;触发报告。
带 shebang 的脚本、无导出 —— 通过:
#!/usr/bin/env node const foo = 1; console.log(foo);脚本只包含普通声明与副作用调用,符合"脚本只执行、不导出"的预期。
普通模块中的导出 —— 通过:
export const foo = 1;文件第一行不是#!,规则直接返回,不产生任何报告。
边界情况:哪些写法会被放行、哪些会被拦截
测试文件 test/no-exports-in-scripts.js 通过 AVA 的test.snapshot()对大量边界场景做了验证,快照结果保存在 test/snapshots/no-exports-in-scripts.js.md。这些用例恰好能帮你理解规则的精确边界,避免在真实项目中产生意外误报。
不会被拦截(valid)的场景
- 无 shebang 的任何导出:
export const foo = 1;、export default foo;、export * from "./foo.js";、export {};,以及先声明后导出的const foo = 1; export {foo};; - TypeScript 中无 shebang 的类型导出:
export type Foo = string;、export interface Foo {}; - 带 shebang 但完全不导出:
#!/usr/bin/env node下仅使用import与console.log; - 带 shebang 但使用 CommonJS 导出:
#!/usr/bin/env node下的module.exports = foo;—— 规则只针对 ESM 的export关键字,CommonJS 的module.exports不在检查范围内,这也与"脚本可直接执行"的定位一致; - shebang 位于注释中:
// #!/usr/bin/env node export const foo = 1;第一行是注释而非真正的 shebang,规则不触发;
- shebang 只是字符串内容:
console.log('#!/usr/bin/env node'); export const foo = 1;第一行是普通代码,#!出现在字符串字面量内部,不影响判定。
从源码角度看,这些边界全部由sourceCode.lines[0].startsWith('#!')这一条判定自然覆盖:只有物理第一行、真实存在的 shebang才会进入导出检查。
会被拦截(invalid)的场景
以下所有写法都会报告Do not use exports in scripts.:
#!/usr/bin/env node+export const foo = 1;#!/usr/bin/env node+export default foo;#!/usr/bin/env node+export * from './foo.js';#!/usr/bin/env node+export * as foo from './foo.js';#!/usr/bin/env node+const foo = 1; export {foo};#!/usr/bin/env node+export {foo} from './foo.js';#!/usr/bin/env node+export {};(即使空的导出对象也会被拦截)#!/usr/bin/env node+ 多个导出语句(每条导出分别报告一次,快照invalid(8)显示一个文件内两处导出会产生 Error 1/2 与 Error 2/2)- TypeScript 场景:
#!/usr/bin/env node+export type Foo = string;、#!/usr/bin/env node+export interface Foo {}(需在测试中通过languageOptions: {parser: parsers.typescript}启用 TS 解析器,见 test/no-exports-in-scripts.js)
快照文件 test/snapshots/no-exports-in-scripts.js.md 中可以看到每条错误报告的输出格式:报告位置精确指向 export 语句本身,消息为Do not use exports in scripts.。
配置方式与推荐预设
该规则已在 eslint-plugin-unicorn 的推荐配置中启用。在 readme.md 的规则列表中,该规则对应行为:
| 规则 | 描述 | recommended | unopinionated |
|---|---|---|---|
| no-exports-in-scripts | Disallow exports in scripts. | ✅ | ☑️ |
- 启用
recommended(✅)的配置集合中,该规则直接生效; - 启用
unopinionated(☑️)的配置集合中,该规则同样开启(注:规则源码 rules/no-exports-in-scripts.js 中meta.docs.recommended标注为'unopinionated',两者并不冲突,unopinionated是"无争议、可放心启用"的定位)。
如果希望手动启用,可在 ESLint 配置中写入:
'unicorn/no-exports-in-scripts': 'error',或按项目需求设为'warn'。由于meta.schema为空数组,该规则不接受任何选项,无需也无法传参。
一个值得注意的细节是:仓库自身的 dogfooding 配置 eslint.dogfooding.config.js 中把'unicorn/no-exports-in-scripts': 'off'显式关闭,这说明对于"允许脚本附带导出"的项目,完全可以通过该选项按需放行,规则本身是可选加入的。
实践建议
- CLI 入口文件:凡是带 shebang 的 bin 脚本,保持"零导出"的纯净形态;若确有复用需求,将可复用逻辑抽取到独立模块文件(不带 shebang),再从脚本中
import使用。 - 不要用注释绕过规则:虽然
// #!或字符串中的#!不会触发检查,但这属于规则识别的边界,而非推荐写法;真正的 shebang 必须位于物理第一行才能被操作系统/Node.js 识别。 - CommonJS 脚本不受影响:如果你用
module.exports组织脚本,规则不会干预;它与 ESMexport关键字互不干扰。 - TypeScript 脚本同样适用:在配置了 TS 解析器时,
export type、export interface这类仅类型导出同样会被拦截,符合"脚本不做任何对外导出"的统一约定。
小结
no-exports-in-scripts用一条极简的判定(首行是否为#!)守护"脚本与模块分离"的代码组织原则:脚本只管执行,模块才负责导出。它的边界清晰、零配置、无参数,配合 rules/no-exports-in-scripts.js 的实现与 test/no-exports-in-scripts.js 的完整用例,可以放心在 recommended 配置中直接启用。
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考