功能说明
该示例把标题转换成适合放进 URL 的 slug。它先做 NFKD 分解,把带音标的拉丁字符拆成基础字母与组合符号,再删除组合符号,最后只保留小写字母和数字并用分隔符连接。生成结果可直接用于文章路径、标签页或静态站点文件名。
关键代码
normalize 使用 NFKD,随后用正则匹配 Unicode 组合附加符号区间并删除。文本转小写后按非字母数字字符切分,过滤空片段再 join,避免出现连续分隔符。超过 maxLength 时先截断,再回退到最后一个分隔符,保证不会把单词切成半截。结果为空时返回 fallback,默认是 untitled,防止生成空路径。
使用注意
该实现只做拉丁字母的变音符号折叠,不做中文、日文或西里尔字母的音译,这些字符会被整体丢弃并落到 fallback。如果需要多语言 slug,应接入音译表或转写库。分隔符不能为空字符串,maxLength 必须为正整数。截断可能丢掉尾部词,重要信息应放在标题前部。生成后仍应检查是否与已有 slug 冲突。
验证方式
断言 Héllo, Wörld! 2026 生成 hello-world-2026,Café au Lait 生成 cafe-au-lait。中文输入应返回 untitled。用 maxLength 为 18 的标题验证截断发生在分隔符处而不是单词中间。再测试自定义分隔符、空分隔符抛 TypeError、非字符串输入抛 TypeError。
代码示例
const DIACRITICS = /[\u0300-\u036f]/g;
export function slugify(input, options = {}) {
if (typeof input !== "string") throw new TypeError("input must be a string");
const { maxLength = 80, separator = "-", fallback = "untitled" } = options;
if (typeof separator !== "string" || separator.length === 0) {
throw new TypeError("separator must be a non-empty string");
}
if (!Number.isInteger(maxLength) || maxLength < 1) {
throw new RangeError("maxLength must be a positive integer");
}
const normalized = input.normalize("NFKD").replace(DIACRITICS, "").toLowerCase();
const parts = normalized.split(/[^a-z0-9]+/).filter(Boolean);
let slug = parts.join(separator);
if (slug.length > maxLength) {
slug = slug.slice(0, maxLength);
const cut = slug.lastIndexOf(separator);
if (cut > 0) slug = slug.slice(0, cut);
}
return slug || fallback;
}
console.log(slugify("Héllo, Wörld! 2026"));
console.log(slugify(" Café au Lait "));
console.log(slugify("你好 世界"));
console.log(slugify("Express + TypeScript Guide", { maxLength: 18 }));