- 开发工具
【免费下载链接】nanoid
A tiny (118 bytes), secure, URL-friendly, unique string ID generator for JavaScript
导读:本文以 Nano ID 项目 CHANGELOG.md 为骨架,逐版梳理从 1.0 到 6.0 的关键变更,并结合仓库内 index.js、index.browser.js、non-secure/index.js 等源码与 test/index.test.js 测试用例,解释每一项变更背后的原因、影响与当下实现。读完你将理解:默认 21 位 ID 为什么能对标 UUID v4 的碰撞概率;customAlphabet、customRandom的熵均匀性算法如何工作;ESM/CommonJS、Web Crypto、CDN、CLI 等演进路线的取舍依据,以及各版本的 Node.js 支持边界。
Nano ID 的 CHANGELOG.md 是一份罕见的"浓缩版架构文档":它记录了该项目从 v0.1 首次发布到 v6.0.1 的每一次功能性转折——默认字母表、异步 API、CommonJS 支持、Web Crypto 迁移、字符串池优化等。逐条解读这些条目,能最直接地看清这个"118 字节"库的演进逻辑。
一、版本总览与演进主线
当前仓库 package.json 中记录的版本为6.0.1,engines.node要求^22 || ^24 || >=26,且包为纯 ESM("type": "module")。整个变更历史可归纳为四条主线:
- 体积控制:从 v0.1 起几乎每个版本都在"Reduce size",配合 package.json 中的
size-limit配置(nanoid118 B、customAlphabet207 B、urlAlphabet47 B、non-secure 版 93 B/55 B)持续压测; - 安全性增强:从
Math.random()演进到 Node.jscrypto,再到 v5.0 全面迁移 Web Crypto API,并持续修复熵均匀性、随机池损坏等隐患; - 模块生态演进:CommonJS → ESM 优先、命名导出、
package.exports、浏览器/React Native 映射、JSR 发布、CDN 单文件、CLI 工具; - 边界健壮性:修复大量"非整数/负数/超大 size 导致死循环或池污染"的边界问题。
二、v6.0:核心 API 提速 4 倍与 Node.js 版本收窄
2.1 "4 倍提速"的源码依据
v6.0.0 的关键条目是"Madenanoid()andcustomAlphabet()4 times faster",其实现就藏在当前 index.js 的**字符串池(string pool)**机制中,源码注释明确说明该优化移植自 nope-id 项目:
POOL_MAX = GET_RANDOM_LIMIT / 2(即 32768),池的最大预生成长度被限制在 Web Crypto 单次getRandomValues可接受范围内;- 首次调用时池从请求的 size 起步,之后按 16 倍几何增长(
poolNext = Math.min(target * 16, POOL_MAX)),短生命周期生成器不会为完整池买单; - 每次 refill 只做一次
Buffer#toString('latin1'),之后每个 ID 只是对池字符串的一次substring,避免了逐字符拼接的分配开销。
// index.js 中字符串池的核心逻辑(节选) let pool = '' let poolOffset = 0 let poolNext = 0 return (size = defaultSize) => { size |= 0 if (size < 0) throw new RangeError('Wrong ID size') if (size === 0) return '' if (poolOffset + size > pool.length) { let target = Math.max(poolNext, size) poolNext = Math.min(target * 16, POOL_MAX) let buffer = Buffer.allocUnsafe(target) // ... 拒绝采样 / 掩码映射写入字节缓冲 pool = buffer.toString('latin1') poolOffset = 0 } poolOffset += size return pool.substring(poolOffset - size, poolOffset) }这正是 test/index.test.js 中"avoids pool pollution, infinite loop"测试存在的意义:传入nanoid(2.1)这类非整数 size 时,若不做size |= 0的强转,会污染poolOffset导致后续 ID 相互重复或死循环。
2.2 移除 Node.js 18/20 支持
v6.0.0 同步"Removed Node.js 18 and 20 support",package.json 中的engines.node已收紧为^22 || ^24 || >=26。如果你仍运行在 Node.js 18/20 上,应锁定 5.x 分支;这是安全支持窗口与语言特性(如现代crypto、V8 优化)之间的权衡,并非 Bug 修复。
三、v5.0 系列:Web Crypto 迁移、TypeScript 与发布链路升级
3.1 全面迁移 Web Crypto(v5.0)
v5.0 是 API 层面的分水岭:"Moved Node.js version to Web Crypto API"、"Removed async API since Web Crypto API has only sync version"、"Removed Node.js 14 and 16 support"。
这意味着 Node 端与浏览器端共享同一条熵来源。当前实现中:
- Node 版 index.js 通过
crypto.getRandomValues填充Buffer,并因getRandomValues拒绝超过 65536 字节的请求,用fillRandom()分块填充; - 浏览器版 index.browser.js 直接
crypto.getRandomValues(new Uint8Array(bytes))。
建议迁移路径:5.x 用户如仍使用import { nanoid } from 'nanoid/async',需改为同步nanoid();Node 14/16 用户请升级运行时。
3.2 不透明类型(opaque types)与 JSR 支持(5.1.x)
- v5.1.0加入不透明类型支持,index.d.ts 中
nanoid<Type extends string>(size?: number): Type允许把生成的字符串铸造为带 brand 的专有类型,避免把普通字符串误当作UserId等强类型使用(详见 README.md 的示例); - v5.1.1为 non-secure 生成器补齐了同样的不透明类型支持,并加入 JSR 发布(jsr.json);
- v5.1.7为 CLI 增加
--version,并更新了 nanoid.js 单文件供 CDN 直接使用;customRandom的类型签名也被修复。
3.3 边界修复的完整清单(5.1.x)
| 版本 | 修复内容 | 相关测试/源码依据 |
|---|---|---|
| 5.1.16 | 负数 size 的死循环 | non-secure/index.js 用i-- > 0收窄循环;random()对负值抛RangeError |
| 5.1.15 | 大 ID 尺寸下随机池损坏 | 字符串池的poolOffset越界问题,见 index.js |
| 5.1.14/5.1.9 | npm 包体积回归 | size-limit配置持续把关 |
| 5.1.12/5.1.11/5.1.10 | 请求超大 ID 时破坏 Nano ID | test/index.test.js 覆盖 70000 字节 ID 的生成 |
| 5.1.8 | customAlphabet提速 75% | 字符串池与掩码映射路径,见 index.js |
| 5.1.6 | customAlphabet在 0 size 下死循环 | 三处生成器均有if (!size) return ''早退 |
| 5.1.3/5.1.6 | React Native 支持修复 | package.json 的react-native字段映射到浏览器实现 |
其中 v5.1.16 的负 size 修复尤其值得注意:旧实现中while (i--)在i为负数时会把-1当作真值继续循环,non-secure/index.js 改为while (i-- > 0)后,负数立即终止;同时 Node 端 index.js 对负 size 直接抛出RangeError('Wrong ID size')。
四、v4.0:告别 CommonJS,拥抱纯 ESM
v4.0 是生态决策的关键节点:"Removed CommonJS support. Nano ID 4 will work only with ESM applications",同时移除 Node.js 10/12 支持并进一步瘦身。
当前仓库完全继承了这一决策:package.json 声明"type": "module",package.json 的exports只暴露"."、"./non-secure"与"./package.json"。若你的项目仍在使用require('nanoid'),需要:
- 升级到支持 ESM 的 Node.js(仓库要求
^22 || ^24 || >=26); - 把
require改为import,或将入口转为 ESM(.mjs/"type": "module"); - 使用
import { nanoid } from 'nanoid'命名导入。
五、v3.x:模块格式修补、CLI 与customAlphabet的 size 参数
5.1 模块解析兼容性(3.1.8–3.1.25)
v3.x 中期密集修复了各类打包器兼容:package.exports(3.1.18/3.1.22)、ES modules(3.1.10/3.1.20)、esbuild(3.1.23)、browserify(3.1.24/3.1.25)、enhanced-resolve(3.3.2)、node16TypeScript(3.3.7),以及package.types路径(3.1.14/3.1.15)。这些条目最终沉淀为 package.json 中今天看到的exports/browser/react-native/types完整映射。
5.2 CLI 诞生(3.1 → 3.2 → 3.3)
- v3.1首次加入
npx nanoidCLI(入口为 package.json 的"bin": "./bin/nanoid.js"); - v3.2增加
--size和--alphabet参数; - v3.3为
customAlphabet生成的函数增加调用时指定 size 的能力。
对应 README 中的用法为:
$ npx nanoid LZfXLfzPPR4NNrgjlWDxn $ npx nanoid --size 10 L3til0JS4z $ npx nanoid --alphabet abc --size 15 bccbcabaabaccab5.3customAlphabet的 size 参数与边界修复
v3.3 的"Addedsizeargument to function fromcustomAlphabet"允许既设置默认 size、又在调用时覆盖:
import { customAlphabet } from 'nanoid' const nanoid = customAlphabet('1234567890abcdef', 10) model.id = nanoid(5) //=> "f01a2"同时 v3.x 修复了一批重要缺陷:v3.1.31 修复了size传入对象时的碰撞漏洞(现在 index.js 用size |= 0防御valueOf滥用);v3.3.16/v3.3.17 修复负/零 size 死循环;v3.3.14 修复大 ID 随机池损坏。
六、v2.x–v1.x:字母表、非安全生成器与异步 API 的引入与告别
6.1 默认字母表的定型(v1.0 → v2.0)
- v1.0默认 ID 定为 21 符号(21 symbols),保证与 UUID v4 相近的碰撞概率;
- v2.0将默认字母表中的
~换成-,使 ID 对文件名安全(file name safe)。
这就是今天 url-alphabet/index.js 中useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict的由来:64 个A-Za-z0-9_-字符,且顺序经过优化以获得更好的 gzip/brotli 压缩率(源码注释提到与 brotli 默认字典的引用关系)。
6.2 非安全生成器(v1.1 → v2.0)
- v1.1引入 non-secure ID 生成器,并建议 React Native 开发者使用;
- v2.0增加
nanoid/non-secure/generate。
当前 non-secure/index.js 基于Math.random()实现,体积更小(size-limit 配置为 93 B),但不保证不可预测性,且 README 明确指出 non-secure 版本比安全版本更慢。仅应在无硬件随机源的环境中按需使用:
import { nanoid } from 'nanoid/non-secure' const id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqLJ"6.3 异步 API 的引入与移除
- v1.2加入
nanoid/async; - v3.0移除
async/format等异步能力; - v5.0因 Web Crypto 只有同步 API 而彻底移除异步 API。
这条曲线的本质是:熵源从旧 Node.js 异步随机接口迁移到同步 Web Crypto 后,异步包装不再有必要。
七、v3.0:命名导出与 API 重命名的分水岭
v3.0 的迁移指南定义了至今仍在使用的大部分 API 形状:
- 命名导出:
import { nanoid } from 'nanoid'; import url from 'nanoid/url'→import { urlAlphabet } from 'nanoid';format()→customRandom();generate()→customAlphabet();- 移除
async/format; - 增加 nanoid.js 单文件用于 CDN 直引;
- 增加 TypeScript 类型定义(即 index.d.ts);
- 为打包器、Node.js、React Native 增加 ESM 支持。
也正是从这一版开始,字母表必须不超过 256 个符号成为 API 契约(index.d.ts 明确:超出则不保证内部生成器算法的安全性)。
八、安全与熵均匀性:变更日志之外的核心实现
虽然变更日志反复提到"Reduce size"和性能优化,但安全属性才是 Nano ID 的立身之本:
- 不可预测性:安全版使用硬件随机源(Node 端 index.js 的
crypto.getRandomValues、浏览器端 index.browser.js 的 Web Crypto),而非Math.random(); - 均匀性:
random % alphabet.length是常见错误——当字母表长度不能整除 256 时会产生模偏差(modulo bias),让部分符号出现频率更高、削弱暴力破解难度。当前 index.js 的customRandom采用拒绝采样:计算safeByteCutoff = 256 - (256 % alphabet.length),只接受小于该阈值的字节;若字母表长度为 2 的幂则退化为更快的& mask位掩码路径。
源码注释给出了直观例子:17 个符号时safeByteCutoff为 255,字节 0–254 让每个符号获得 15 个源字节,字节 255 会再次映射到符号 0 造成偏差,因此被拒绝。test/index.test.js 的"has flat distribution"测试对 10 万个 ID 统计每个字符出现频率,要求理论分布偏差不超过 0.05,从测试层面验证了均匀性。
下图为该均匀性结论的可视化:上方为 Nano ID 的 a–z 频率分布(色块明暗高度均匀),下方为常规取模实现的分布(w–z 明显偏亮、频率偏高):
而 test/index.test.js 的 5 万次无碰撞测试、test/index.test.js 的负/超大 size 抛错测试、test/index.test.js 的 proxy 数字valueOf攻击测试,共同构成了边界健壮性的证据链。
九、变更日志时间线速查表
| 版本 | 里程碑 | 备注 |
|---|---|---|
| 0.1 – 0.2.2 | 初始发布、size参数、性能提升 | 默认 21 符号在 1.0 定型 |
| 1.0 – 1.3.4 | 默认 21 符号、non-secure、async API | 大量体积与性能优化 |
| 2.0 – 2.1.11 | -替换~、non-secure/generate | 文件名字母表定型 |
| 3.0 – 3.3.17 | 命名导出、customAlphabet/customRandom、CLI、TypeScript 定义 | API 形状定型;异步 API 移除 |
| 4.0 – 4.0.2 | 移除 CommonJS、纯 ESM | 淘汰 Node 10/12 |
| 5.0 – 5.1.16 | 迁移 Web Crypto、移除异步 API、不透明类型、JSR、CDN 单文件 | 淘汰 Node 14/16;边界修复高峰 |
| 6.0 – 6.0.1 | nanoid()/customAlphabet()提速 4 倍、移除 Node 18/20 | 字符串池机制落地 |
十、升级与迁移指引
结合以上版本脉络,面向不同场景给出迁移建议:
- 从 4.x/3.x 升级到 6.x:确认运行时为 Node.js
^22 || ^24 || >=26;使用 ESM 命名导入;将任何异步用法替换为同步nanoid();按 index.d.ts 的签名调整 TypeScript 调用; - 从 2.x/1.x 直接迁移:除上述改动外,还需把
generate()/format()替换为customAlphabet()/customRandom(),把nanoid/url导入替换为urlAlphabet命名导出; - React Native:按 package.json 的映射,bundler 会自动选择浏览器实现;若运行时缺少
crypto.getRandomValues,需按 README.md 先引入react-native-get-random-valuespolyfill; - 体积预算敏感项目:参考 package.json 的
size-limit预算(主包 118 B),通过customAlphabet或 non-secure 入口换取更小体积时,务必同步评估碰撞概率与安全性要求。
总结
从 CHANGELOG.md 的 575 行变更记录可以看出,Nano ID 的演进并非简单堆功能,而是围绕体积、安全、生态兼容三条约束反复打磨:v3.0 定下 API 形状,v5.0 统一熵源到 Web Crypto,v6.0 用字符串池兑现"4 倍提速"并收紧 Node 版本支持。对照源码与测试阅读这份变更日志,是理解这款 118 字节库设计取舍最直接的途径。
- 开发工具
【免费下载链接】nanoid
A tiny (118 bytes), secure, URL-friendly, unique string ID generator for JavaScript
相关推荐
Nano ID 完整指南:118 字节的安全 URL 友好唯一 ID 生成器
Nano ID 完整指南:118 字节的安全 URL 友好唯一 ID 生成器 导读 Nano ID 是一个体积仅 118 字节(minified + brotl
开发工具free-stockdb分钟数据跨日查询技巧:8位日期如何自动补全为14位时间戳
free stockdb分钟数据跨日查询技巧:8位日期如何自动补全为14位时间戳 free stockdb 是一款面向 A 股日K、分钟K与ETF分钟数据的本地
开发工具Nano ID 完全指南:118 字节的安全 URL 友好唯一 ID 生成器实战与源码解析
Nano ID 完全指南:118 字节的安全 URL 友好唯一 ID 生成器实战与源码解析 Nano ID 是一个小巧、安全、URL 友好且唯一的 JavaSc
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考