ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入解读 streamlit 前端的自定义 ESLint 插件:eslint-plugin-streamlit-custom 的设计与实战

深入解读 streamlit 前端的自定义 ESLint 插件:eslint-plugin-streamlit-custom 的设计与实战 深入解读 streamlit 前端的自定义 ESLint 插件eslint-plugin-streamlit-custom 的设计与实战【免费下载链接】streamlitStreamlit — A faster way to build and share data apps.项目地址: https://gitcode.com/gh_mirrors/st/streamlit导读在 Streamlit 开源仓库的前端React TypeScript中工程化质量保障除了依赖社区 ESLint 规则外还维护了一个专属于项目自身的自定义 ESLint 插件包 ——eslint-plugin-streamlit-custom。它以 TypeScript 编写、按项目实际痛点定制了 5 条 lint 规则覆盖了空值判断、主题值硬编码、React 组件 memo 化、强制回流 API 访问和无障碍aria-hidden五大类问题。本文以该插件包的 README.md 为主线结合仓库中的规则实现源码、测试用例与 eslint.config.mjs 配置完整介绍每条规则的行为、自动修复能力以及在实际前端代码中的接入方式帮助你理解如何为大型前端工程编写一套项目专属 ESLint 规则。一、插件定位与包结构eslint-plugin-streamlit-custom位于 frontend/eslint-plugin-streamlit-custom是一个TypeScript 编写的、面向 Streamlit 前端代码的 ESLint 插件包。它解决的问题是社区规则无法覆盖的、只有项目自身才懂的技术约束例如主题值必须来自 theme 对象组件必须被 React.memo 包裹等团队约定。从 package.json 可以看到它的工程形态包名为eslint-plugin-streamlit-custommain指向./src/index.ts即直接以 TypeScript 源码作为入口peerDependencies要求eslint ^10.0.0与typescript-eslint/utils ^8.70.0开发依赖包含typescript-eslint/rule-tester规则测试器、vitest测试运行器与typescript脚本提供了yarn test运行测试、yarn testWatch监听模式、yarn typecheck类型检查、yarn lint、yarn format等常用命令。插件入口 src/index.ts 导出了全部 5 条规则export default { rules: { enforce-memo: enforceMemo, no-force-reflow-access: noForceReflowAccess, no-aria-hidden-with-focusable-children: noAriaHiddenWithFocusableChildren, no-hardcoded-theme-values: noHardcodedThemeValues, use-strict-null-equality-checks: useStrictNullEqualityChecks, }, }也就是说所有规则统一以streamlit-custom/rule-name的形式注册供上层 ESLint 配置引用。二、开发工作流改动后必须重启 ESLint ServerREADME 的 Development 章节强调了一个关键实操细节ESLint 会缓存插件改动插件代码后不会自动生效。因此在开发插件时修改完代码后需要重启任何使用该插件的包中的 ESLint Server修改插件代码重启使用该插件的包的 ESLint ServerVS Code 中通过命令面板CmdShiftP/CtrlShiftP执行ESLint: Restart ESLint Server或者直接重启 IDE / 编辑器。这一步骤是必需的因为 ESLint 会对插件做缓存在服务重启前不会拾取变更。插件的常用命令如下命令作用yarn test运行测试内部为vitest runyarn testWatch运行测试并监听文件变化yarn typecheck运行 TypeScript 类型检查--noEmityarn lint对src运行 ESLint--cache --max-warnings 0yarn format/yarn formatCheck通过 oxfmt 格式化 / 校验src下源码测试基础设施规则的质量由typescript-eslint/rule-tester保障。src/utils/ruleTester.ts 把 RuleTester 与 Vitest 桥接起来RuleTester.afterAll afterAll、RuleTester.describe describe等并配置了支持 JSX 的解析器选项export const ruleTester new RuleTester({ languageOptions: { ecmaVersion: 2018, sourceType: module, parserOptions: { ecmaFeatures: { jsx: true } }, }, })src/utils/createRule.ts 则通过ESLintUtils.RuleCreator为每条规则创建统一入口并将规则文档链接指向仓库内对应源码。每个规则文件都配有一个同名*.test.ts测试文件例如 use-strict-null-equality-checks.test.ts 中同时给出了valid与invalid两组用例后者还校验自动修复后的output。测试配置见 vitest.config.ts它只收集src/**/*.test.ts在 Node 环境下运行。三、在包中使用jiti 导入 TypeScript 插件README 给出了在某个包package的 ESLint 配置中接入该插件的最核心代码。由于规则是用 TypeScript 编写的但 ESLint 需要以 JS 方式导入因此仓库采用jiti在运行时进行转译import { createJiti } from jiti // 这是为了支持我们自定义的规则规则以 TypeScript 编写 // 但必须作为 JS 导入才能在 ESLint 中工作。 const jiti createJiti(import.meta.url) const streamlitCustom await jiti.import(eslint-plugin-streamlit-custom, { default: true, }) export default [ { plugins: { streamlit-custom: streamlitCustom, }, rules: { streamlit-custom/use-strict-null-equality-checks: error, streamlit-custom/no-hardcoded-theme-values: error, streamlit-custom/enforce-memo: error, }, }, ]这段代码说明了三个要点createJiti(import.meta.url)创建 jiti 实例jiti.import(...)直接加载 TypeScript 插件包并取默认导出通过plugins字段注册命名空间streamlit-custom通过rules字段按需开启规则值为error/warn/off。仓库中的真实接入方式这正是 Streamlit 前端 frontend/eslint.config.mjs 实际采用的做法第 43-46 行使用完全相同的 jiti 模式加载插件随后在各规则块中注册// eslint.config.mjs 中的规则启用情况部分 streamlit-custom/no-hardcoded-theme-values: error, streamlit-custom/use-strict-null-equality-checks: error, // 该规则仅针对特定目录开启 streamlit-custom/enforce-memo: off, streamlit-custom/no-force-reflow-access: error, streamlit-custom/no-aria-hidden-with-focusable-children: error,值得注意的工程化细节enforce-memo在全局配置中是off注释说明我们只对特定目录开启这条规则在另一处配置块中则被设为error见 eslint.config.mjs 第 602 行附近。这体现了通过配置文件按目录差异化启用规则的做法。四、五条自定义规则逐条详解以下结合各规则的源码实现逐条说明其检查逻辑、报告信息与自动修复能力。4.1 use-strict-null-equality-checks禁止宽松空值比较规则名streamlit-custom/use-strict-null-equality-checks元信息类型为suggestionfixable: code无 schema 选项实现见 src/use-strict-null-equality-checks.ts。它会在BinaryExpression中捕获 null、! null、 undefined、! undefined这类宽松比较会同时匹配null与undefined语义模糊并自动修复为项目内定义的显式工具函数原写法自动修复结果foo nullisNullOrUndefined(foo)foo ! nullnotNullOrUndefined(foo)foo undefinedisNullOrUndefined(foo)foo ! undefinednotNullOrUndefined(foo)报告消息为Use isNullOrUndefined or notNullOrUndefined instead of null or ! null。测试用例use-strict-null-equality-checks.test.ts中valid用例为isNullOrUndefined(foo)、notNullOrUndefined(foo)invalid用例则逐一验证上表四种写法的报错与output修复结果。4.2 no-hardcoded-theme-values禁止硬编码主题值规则名streamlit-custom/no-hardcoded-theme-values元信息类型为problemfixable: code无 schema 选项实现见 src/no-hardcoded-theme-values.ts。它的设计初衷是保证所有样式值都来自主题theme系统而非散落的魔法数字。规则覆盖三类写法源码注释中明确列出// 1) 内联 style 对象 div style{{ backgroundColor: red, width: 100px }} / // 2) styled-components 模板字符串 styled.divbackground-color: red; width: 100px; // 3) styled 对象函数形式 const foo styled.div(() { backgroundColor: red, width: 100px })底层判定逻辑非常值得细读cssPropertiesToCheck正则/^(.*color|width|height|margin.*|padding.*|lineHeight|line-height|border.*|.*radius|font.*|zIndex|z-index)$/i圈定需要检查的 CSS 属性集合allowedValuesRegex定义了允许的值不能包含theme字样以外的硬编码值但放行 CSS 内建值transparent、solid、initial、none、null、undefined、inherit、auto、unset、fit-content、collapse、separate等、纯0、相对单位%、em、vh、vw注意刻意不允许rem注释说明其粒度不如em精细且曾被滥用以及部分字体相关值small-caps、italic、normal、liga。对模板字符串的检查仅在styled.xxx组合出现时进行node.tag.object.name styled并对styleProperty.split(:)后的属性/值逐一匹配若模板内插入了函数如width: ${({ theme }) theme }px解析出的值为空字符串会被跳过以避免过度复杂化解析。报告消息区分两种场景noHardcodedThemeHardcoded theme values are not allowed. All values must start with theme or be a CSS built-in value such as none.noHardcodedThemeTemplate在模板字符串中触发额外提示通常请优先使用 styled-object 写法。仓库内也能看到这条规则的实际生效痕迹例如 frontend/app/src/components/Navigation/styled-components.ts 第 362-376 行用/* eslint-disable streamlint-custom/no-hardcoded-theme-values */与/* eslint-enable ... */局部豁免。4.3 enforce-memo强制导出组件使用 React.memo规则名streamlit-custom/enforce-memo元信息类型为problemfixable: code无 schema 选项实现见 src/enforce-memo.ts这是五条规则中逻辑最复杂的一条。它的目标是凡是导出的 React 组件都必须被memo包裹以通过浅比较避免不必要的重渲染。核心能力与实现要点组件识别通过isPascalCase首字母大写且长度大于 1识别组件名通过isLikelyReactComponent检查函数/箭头函数是否返回JSXElement或JSXFragment含对return语句的递归收集已包裹识别isMemoWrapped与isWrappedInExport通过正则匹配export default React.memo(X)、export default memo(X)等模式避免误报HOC 场景isComponentUsedInMemoizedHOC能识别const Enhanced someHOC(MyComponent)后被memo(Enhanced)包裹的间接形式并使用hocPatternCache缓存正则结果以避免重复编译自动修复ensureMemoImport会智能处理memo的导入——若react已有具名导入则追加, memo有默认导入则追加, { memo }否则在文件末尾或开头插入import { memo } from react;fixExportStatements则把所有export default MyComponent替换为export default memo(MyComponent)并采用倒序toReversed应用修复以避免位置偏移。在 eslint.config.mjs 中该规则全局为off、特定目录为error说明它被用于对性能敏感的组件目录做强制约束。4.4 no-force-reflow-access禁止访问触发强制回流的 DOM API规则名streamlit-custom/no-force-reflow-access元信息类型为problem不可自动修复fixable: undefined实现见 src/no-force-reflow-access.ts。它用于约束前端性能某些 DOM 属性/方法的读取会强制浏览器执行 layout/reflow导致性能问题源码注释引用了 Paul Irish 的经典 reflow 清单。规则维护了三张清单forceReflowPropertiesoffsetLeft/Top/Width/Height、offsetParent、clientLeft/Top/Width/Height、scrollWidth/Height/Left/Top、computedRole、computedName、innerText、scrollX/Y、innerHeight/Width、scrollingElement、layerX/Y、offsetX/Y、instanceRoot等forceReflowMethodsgetClientRects、getBoundingClientRect、getComputedStyle、elementFromPoint以及一系列 SVG 文本测量方法getBBox、getComputedTextLength、getNumberOfChars等visualViewportPropertiesheight、width、offsetTop、offsetLeft访问visualViewport.*时会给出更明确的提示建议改用ResizeObserver。规则贴心地做了降噪处理跳过对象字面量中的属性定义{ offsetWidth: 100 }与赋值表达式obj.offsetWidth 50跳过config、options、settings、props、state等明显非 DOM 对象的访问解构场景const { scrollWidth } element单独处理且跳过字面量、函数调用、new表达式等初始化来源源码注释还列出了一些有意不禁用的方法scrollBy、scrollTo、focus、select等因为它们在无替代方案的合法场景下使用。每条报告都会附带替代建议alternative例如访问offsetWidth时提示Consider using ResizeObserver for size tracking instead.调用innerText时建议改用不触发 reflow 的textContent。仓库内的真实豁免例子很能说明其使用方式frontend/lib/src/components/elements/Expander/useDetailsAnimation.ts 第 198-201 行、第 299-303 行用/* eslint-disable streamlit-custom/no-force-reflow-access -- Batched reads for animation */为动画场景的批量读取做了局部豁免frontend/component-lib/src/streamlit.ts 第 110 行也以行内注释豁免了单次访问。4.5 no-aria-hidden-with-focusable-children禁止在含可聚焦子元素的外层上使用 aria-hidden规则名streamlit-custom/no-aria-hidden-with-focusable-children元信息类型为problem不可自动修复支持选项additionalFocusableComponents字符串数组实现见 src/no-aria-hidden-with-focusable-children.ts。这是一条无障碍a11y规则在包含可聚焦子元素的容器上设置aria-hidden会把交互控件从辅助技术中隐藏破坏键盘与读屏用户的体验。规则建议只对具体的视觉文本节点如span应用aria-hidden。其判定逻辑isAriaHiddenTruthy识别div aria-hidden /、aria-hidden{true}、aria-hiddentrue等真值写法hasFocusableDescendant递归检查子节点命中以下任一情况即视为可聚焦明确的 HTML 交互标签button、a、input、select、textarea、summary源码注释强调这是刻意保守的清单tabIndex 0命中focusableComponents集合中的自定义组件。DEFAULT_FOCUSABLE_COMPONENTS内置了 Streamlit 自身会渲染可聚焦触发器的组件TooltipIcon、InlineTooltipIcon、WidgetLabelHelpIcon、WidgetLabelHelpIconInline、BaseButton。如果项目还有其他看起来像普通元素、实则内部可聚焦的组件可通过规则的additionalFocusableComponents选项追加rules: { streamlit-custom/no-aria-hidden-with-focusable-children: [ error, { additionalFocusableComponents: [MyIconWithTooltip] }, ], }报告消息会给出可操作的修复指引Do not set aria-hidden on a wrapper that contains focusable descendants. ... Apply aria-hidden only to the specific visual text node instead (e.g. a span).五、规则清单速览规则streamlit-custom/...问题类型可自动修复核心目标是否支持选项use-strict-null-equality-checkssuggestion是用isNullOrUndefined/notNullOrUndefined取代 null、! null宽松比较否no-hardcoded-theme-valuesproblem是样式值必须来自主题系统禁止硬编码颜色、尺寸等否enforce-memoproblem是导出的 React 组件必须由React.memo包裹含 HOC 场景否no-force-reflow-accessproblem否禁止访问/调用触发强制回流的 DOM 属性与方法否no-aria-hidden-with-focusable-childrenproblem否禁止在含可聚焦子元素的外层上设置aria-hidden是additionalFocusableComponents六、在 Streamlit 前端中的落地实践通过前文的 eslint.config.mjs 引用可以看到这套自定义规则已经深度融入 Streamlit 前端的日常开发jiti 运行时加载规则以 TypeScript 编写通过 jiti 在 ESLint 配置加载时转译为可执行代码eslint.config.mjs 第 43-46 行按严重度与目录差异化配置大部分规则全局为error而enforce-memo采取全局关闭、特定目录开启的策略避免一刀切造成大量无关报错必要时显式豁免对于确实需要访问 reflow API 的动画场景批量读取、DOM 变更后测量或历史遗留样式文件仓库通过eslint-disable注释做局部豁免并附上原因说明如-- Batched reads for animation。对于希望在自有项目中复刻这套机制的开发者建议按以下顺序落地用typescript-eslint/utils的ESLintUtils.RuleCreator与RuleTester搭建规则骨架与测试先用suggestion级别的低成本规则如use-strict-null-equality-checks验证工作流再逐步引入problem级规则对于需要自动修复的规则务必像enforce-memo一样为修复器编写针对导入语句、导出语句等边界场景的测试在顶层 ESLint 配置中通过 jiti 加载插件并按目录/严重度渐进式开启避免一次性全局开启引发大量存量报错。结语eslint-plugin-streamlit-custom是一个典型的工程规范代码化范例它把 Streamlit 前端团队对代码质量的具体要求显式空值判断、主题驱动样式、组件性能、DOM 性能、无障碍沉淀为可测试、可自动修复、可渐进式启用的 ESLint 规则并通过 jiti 打通了 TypeScript 规则与 ESLint 配置之间的加载链路。无论是想要深入理解 Streamlit 前端工程化实践还是希望在自研项目中建立类似的自定义 lint 体系这个插件包及其配套测试与配置都是可以直接研读的参考实现。【免费下载链接】streamlitStreamlit — A faster way to build and share data apps.项目地址: https://gitcode.com/gh_mirrors/st/streamlit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表