ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

just-kebab-case 深度解析:将任意字符串转换为 kebab-case 的零依赖工具

just-kebab-case 深度解析:将任意字符串转换为 kebab-case 的零依赖工具
  • 开发工具

【免费下载链接】just

A library of dependency-free JavaScript utilities that do just one thing.

项目地址:https://gitcode.com/gh_mirrors/jus/just
点击查看免费下载

just-kebab-case是 Just 库中负责字符串 kebab-case 转换的独立 npm 模块,它只做一件事:把任意大小写、空格、标点混杂的字符串规范化为lower-case-with-hyphens形式。阅读本文后,你将掌握该工具的完整 API、安装与双模块(ESM/CJS)引入方式、源码级转换原理(含连续大写、Unicode 字符等边界规则)、测试用例验证方法,以及它相对 Lodash 同类工具的取舍依据。

一、工具定位:一个只做一件事的零依赖模块

just-kebab-case隶属于 Just 库(官方包名just-kebab-case,仓库内路径为 packages/string-kebab-case),是项目 Strings 字符串工具族中的一员,与just-camel-case、just-snake-case、just-pascal-case并列。

它的核心承诺来自 Just 库的整体理念:零依赖(zero-dependency)、只做一件事(do just one thing)。该模块不依赖任何第三方包,package.json 的keywords中明确标注了no-dependencies。在仓库根目录的 TRADEOFFS.md 中,作者给出了一个量化的取舍依据:just-kebab-case压缩后约149b,而 Lodash 的kebabCase约为1720b——如果你关心 JavaScript 体积(尤其是移动端与 PWA 场景),且不依赖 Lodash 的参数强转能力,那么 Just 是更轻量的选择。

二、安装与快速上手

官方推荐通过 npm 或 yarn 安装(详见 packages/string-kebab-case/README.md):

npm install just-kebab-case
yarn add just-kebab-case

安装后即可在项目中引入并使用:

import kebabCase from 'just-kebab-case'; kebabCase('the quick brown fox'); // 'the-quick-brown-fox' kebabCase('the-quick-brown-fox'); // 'the-quick-brown-fox' kebabCase('the_quick_brown_fox'); // 'the-quick-brown-fox' kebabCase('theQuickBrownFox'); // 'the-quick-brown-fox' kebabCase('theQuickBrown Fox'); // 'the-quick-brown-fox' kebabCase('thequickbrownfox'); // 'thequickbrownfox' kebabCase('the - quick * brown# fox'); // 'the-quick-brown-fox' kebabCase('theQUICKBrownFox'); // 'the-q-u-i-c-k-brown-fox'

注意最后一个示例:theQUICKBrownFox转换后是the-q-u-i-c-k-brown-fox——连续大写字母会被逐字拆开,这正是 kebab-case 工具区别于简单toLowerCase().replace(/\s+/g, '-')的关键行为,后文会结合源码详细解释。

三、API 与类型定义

该模块对外只暴露一个默认导出函数,签名如下(见 index.d.ts):

declare function kebabCase(value: string): string; export default kebabCase;
  • 入参:一个字符串value;
  • 返回值:kebab-case 规范化后的字符串。

类型层面做了严格约束:模块自带 TypeScript 声明与类型测试 index.tests.ts,其中通过// @ts-expect-error标注了非法调用,例如:

import kebabCase from './index' // OK kebabCase('myString'); // Not OK —— 传入非字符串会触发类型错误 // @ts-expect-error kebabCase(); // @ts-expect-error kebabCase(0); // @ts-expect-error kebabCase([]); // @ts-expect-error kebabCase({}); // @ts-expect-error kebabCase(/nope/); // @ts-expect-error kebabCase(false);

从源码结构看,kebabCase对非法参数没有任何运行时保护(会直接调用str.replace而抛错),这与 TRADEOFFS.md 中 "a. Just expects correct argument type, Lodash coerces arguments to expected type" 的描述一致:Just 期望调用者传入正确类型,不做参数强转。这一点既是设计选择,也是与 Lodash 的关键差异。

四、转换原理:源码逐段拆解

实现位于 index.cjs(ESM 版本 index.mjs 内容与之完全一致),全部逻辑只有约 20 行、三个正则、一个函数,无任何依赖。

4.1 三个核心正则

// 任意组合的空格与标点字符 var wordSeparators = /[\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]+/; var capital_plus_lower = /[A-ZÀ-Ý\u00C0-\u00D6\u00D9-\u00DD][a-zà-ÿ]/g; var capitals = /[A-ZÀ-Ý\u00C0-\u00D6\u00D9-\u00DD]+/g;
  • wordSeparators:定义"词分隔符"。除了常见空白符\s和标点,还覆盖了两个 Unicode 区块:\u2000-\u206F(一般标点区,General Punctuation)与\u2E00-\u2E7F(补充标点区,Supplemental Punctuation),确保西文标点体系中的破折号、引号等也能被正确识别。该正则在代码注释中注明参考自 Stack Overflow(a/25575009),并被just-camel-case、just-snake-case、just-pascal-case等兄弟模块复用。
  • capital_plus_lower:匹配"一个大写字母紧跟一个小写字母",用于识别camelCase 的词边界(例如Quick中的Q),同时支持带变音符的拉丁大写字母(À-Ý、\u00C0-\u00D6、\u00D9-\u00DD)与对应小写(a-zà-ÿ)。
  • capitals:匹配连续大写字母串(如QUICK、FOX),用于识别"全大写词"或缩写。

4.2 两阶段分词

function kebabCase(str) { // 阶段1:把"大写+小写"的词首替换为"空格 + 小写形式" str = str.replace(capital_plus_lower, function(match) { return ' ' + (match[0].toLowerCase() || match[0]) + match[1]; }); // 阶段2:把剩余的连续大写整体替换为"空格 + 小写形式" str = str.replace(capitals, function(match) { return ' ' + match.toLowerCase(); }); return str .trim() .split(wordSeparators) .join('-') .replace(/^-/, '') .replace(/-\s*$/, ''); }

处理流程如下:

  1. 大写边界分词:第一步把theQuickBrownFox变为the Quick Brown Fox(每个大写词首前插入空格并转小写);第二步把仍残留的大写串(如FOX、QUICK)整体拆分并转小写,theQUICKBrownFox因此变成the QUICK Brown Fox,随后逐字母小写化得到the q u i c k brown fox——这就是"连续大写逐字拆开"现象的来源。
  2. 规范化拼接:trim()去掉首尾空白后,用split(wordSeparators)按任意长度组合的分隔符切词,再用join('-')以连字符连接。
  3. 首尾连字符清理:最后两步replace(/^-/, '')与replace(/-\s*$/, '')分别删除开头的连字符和结尾的连字符(及其前的空白),保证输出不以-开头或结尾。这正是 CHANGELOG 中4.0.2 "No leading or trailing hyphens"修复所对应的实现(详见第六节)。

五、边界行为验证:测试用例怎么说

仓库为每个包维护独立的 tape 测试(运行框架封装见 test/util/test.js,为每个模块名自动加[module]前缀)。just-kebab-case的测试位于 test/string-kebab-case/index.cjs,覆盖了五类典型场景:

1. 空格分隔(string with spaces):

t.equal(kebabCase('the quick brown fox'), 'the-quick-brown-fox'); t.equal(kebabCase('the quick brownfox'), 'the-quick-brownfox');

多个连续空格会被压缩为一个连字符;无大小写信息的纯小写串brownfox不会被强行拆分。

2. 标点分隔(string with punctuation):

t.equal(kebabCase('the_quick_brown_fox'), 'the-quick-brown-fox'); t.equal(kebabCase('the*quick-brown_fox'), 'the-quick-brown-fox'); t.equal(kebabCase('the****quick-_-brown_:fox'), 'the-quick-brown-fox');

下划线、星号、连字符混用时,任意组合的标点都被视为同一分隔符,输出完全一致。

3. 空格与标点混合(string with mixed spaces and punctuation):

t.equal(kebabCase('the_quick_brown_ fox'), 'the-quick-brown-fox'); t.equal(kebabCase('the** **quick-_-brown_:fox'), 'the-quick-brown-fox');

4. 首尾标点(string with leading or trailing punctuation)——对应 4.0.2 修复:

t.equal(kebabCase('*Rank Ordinal*'), 'rank-ordinal'); t.equal(kebabCase('Rank (Ordinal)'), 'rank-ordinal');

首尾的*、括号等标点会被完整剔除,不会在输出两端留下裸连字符。

5. 大小写组合与 Unicode(string with capitalization):

t.equal(kebabCase('theQuickBrownFox'), 'the-quick-brown-fox'); t.equal(kebabCase('The quick brown FOX'), 'the-quick-brown-fox'); t.equal(kebabCase('MapGLBeta'), 'map-gl-beta'); t.equal(kebabCase('MapGL-_-Beta'), 'map-gl-beta'); t.equal(kebabCase('ABCHaček Kroužek'), 'abc-haček-kroužek'); t.equal(kebabCase('Bonjour XYZÉtienne-Louis Boullée'), 'bonjour-xyz-étienne-louis-boullée');

最后两个用例验证了带变音符的拉丁字符处理:Haček(č)、Kroužek(ž)、Étienne(É)、Boullée(é)都能被正确识别并保留变音;MapGLBeta正确产出map-gl-beta(GL连写缩写被整体小写后作为一个词)。这正是capital_plus_lower与capitals两个正则协同工作的结果。

六、版本演进:从 4.0.2 到 4.2.0 的工程化历程

关联文档 packages/string-kebab-case/CHANGELOG.md 记录了该包近期的全部发布历史,这些变更揭示了一个"函数逻辑稳定、工程形态持续演进"的过程:

版本变更类型内容
4.2.0Minor ChangesRename node module.js->.cjs
4.1.1Patch Changesfix: reorder exports to set default last #488
4.1.0Minor Changespackage.json updates to fix #467 and #483
4.0.3Patch ChangesKeep ESMs in sync with commonJS modules
4.0.2Patch ChangesNo leading or trailing hyphens

逐条解读其工程含义:

  • 4.0.2(功能修复):修复了首尾出现多余连字符的问题,对应 index.cjs 中最后的两个replace(/^-/, '')与replace(/-\s*$/, ''),以及测试中*Rank Ordinal*、Rank (Ordinal)两个用例。
  • 4.0.3(双模块同步):保证 ESM 与 CJS 两份产物逻辑一致。仓库通过 Rollup 构建生成 ESM:每个包目录下的 rollup.config.js 调用共享配置 config/createRollupConfig.js,后者以index.cjs为输入、@rollup/plugin-commonjs为插件、输出index.mjs(ESM 格式)。因此 index.mjs 与 index.cjs 内容严格一致——"Keep ESMs in sync with commonJS modules"。
  • 4.1.0(package.json 元数据修复):修复 issue #467、#483 相关的包元数据问题(exports 字段、types 指向等)。当前 package.json 中可见最终形态:"type": "module",exports按types→require(index.cjs)→import(index.mjs)的顺序声明,同时保留main/types顶层字段以兼容旧工具链。
  • 4.1.1(导出顺序修复,issue #488):调整 exports 顺序,把default导出放到最后。结合 4.1.0 的 exports 声明顺序,最终require与import都能正确命中各自格式的入口文件。
  • 4.2.0(CJS 扩展名规范化):将 CommonJS 入口从.js重命名为.cjs。在此之前仓库根目录 package.json 的"main": "index.cjs"说明这是全局一致的规范动作——所有 Just 包统一采用index.cjs(CJS)+index.mjs(ESM)+index.d.ts(类型声明)三件套,从而无需转译即可同时支持 Node 的require与浏览器/打包器的import(参见根 README.md-的说明)。

值得说明的是,所有变更日志由 Changesets 工作流驱动(配置见 .changeset/config.json,仓库采用 lerna.json 中的independent独立版本模式),因此每个包的版本各自演进,CHANGELOG 按 minor/patch 分类记录。

七、与同族字符串工具的关系

just-kebab-case不是孤立的:Just 库的 Strings 家族包含 string-camel-case、string-snake-case、string-pascal-case 等模块,它们共享同一份wordSeparators分隔符正则,但词边界策略各有差异:

  • camelCase(string-camel-case/index.mjs):按分隔符切词后,从第二个词开始首字母大写,并内置basicCamelRegEx、fourOrMoreConsecutiveCapsRegEx、allCapsRegEx三个正则处理已有 camelCase 输入与连续大写;
  • snakeCase(string-snake-case/index.mjs):仅用一个capitals正则把每个大写字母(含变音区\u00C0-\u00D6、\u00D9-\u00DD)替换为"空格+小写",再以_连接;
  • pascalCase(string-pascal-case/index.mjs):直接按分隔符切词并逐词首字母大写;
  • kebabCase(本文主角):采用"capital_plus_lower先处理词首、capitals再处理连续大写"的两阶段策略,最后用-连接。

对于同一输入theQUICKBrownFox,四者的产出分别是theQUICKBrownFox(camelCase 会尽量保持原有 camel 形态以外的处理逻辑不同)、the_q_u_i_c_k_brown_fox(snakeCase 逐字母拆)、theQUICKBrownFox(pascalCase 不拆连续大写)与the-q-u-i-c-k-brown-fox(kebabCase 逐字母拆)。如果你需要_连接的风格,直接选用 string-snake-case 即可,二者体积相当(TRADEOFFS.md 显示均约 149b)。

八、总结

just-kebab-case以约 20 行零依赖代码,实现了对空格、标点、camelCase 边界、连续大写缩写与 Unicode 变音字符的完整 kebab-case 规范化,并通过独立的 tape 测试(test/string-kebab-case/index.cjs)与 TypeScript 类型测试(index.tests.ts)双重保障质量。其 CHANGELOG(packages/string-kebab-case/CHANGELOG.md)记录了一次功能修复(4.0.2 首尾连字符)与多次工程形态演进(4.0.3 ESM 同步、4.1.x 包元数据与导出顺序、4.2.0 CJS 扩展名规范化),体现了"函数稳定、工程打磨"的发布哲学。

适用建议:当你的项目对包体积敏感(如 PWA、移动端)、需要 ESM/CJS 双格式免转译引入、且接受"调用者自行保证传入字符串"的类型约定时,just-kebab-case是理想的轻量选择;若需要 Lodash 式的参数强转或更多内置边界处理,则可按 TRADEOFFS.md 的对照表自行权衡。

  • 开发工具

【免费下载链接】just

A library of dependency-free JavaScript utilities that do just one thing.

项目地址:https://gitcode.com/gh_mirrors/jus/just
点击查看免费下载
上一篇:Simplefolio无障碍:屏幕阅读器兼容性终极指南
下一篇:深入理解 nvim-treesitter-context 插件:代码上下文可视化利器

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进