
深入剖析 emotion/hashEmotion 的 MurmurHash2 哈希实现与 className 生成原理【免费下载链接】emotion CSS-in-JS library designed for high performance style composition项目地址: https://gitcode.com/gh_mirrors/em/emotionemotion/hash是 Emotion 系列包中一个极小却至关重要的基础模块它以 TypeScript 实现了经典的MurmurHash2非加密哈希算法。本文将以该包的 README 为骨架结合其 源码、测试 以及在serialize、babel-plugin、cache等包中的真实调用完整讲解它的 API 用法、算法逐段原理、base36 输出的设计考量以及它如何参与生成 Emotion 的css-*类名与 SSR 水合hydration过程。读完本文你将能独立读懂这段不到 70 行的哈希实现并理解为什么它是 Emotion 高性能样式组合体系的基石之一。一、emotion/hash 是什么根据 README 的定义emotion/hash就是A MurmurHash2 implementation——一个 MurmurHash2 算法的实现。它的源码源自经典的 murmurhash-js 项目garycourt/murmurhash-js 中的murmurhash2_gc.js并进一步对照 Austin Appleby 的原始smhasher仓库中MurmurHash2.cpp的参考实现移植而来这一移植关系在 packages/hash/src/index.ts 的源码注释中有明确记载。MurmurHash 是业界广泛使用的非加密哈希non-cryptographic hash算法族由 Austin Appleby 于 2008 年设计以雪崩效应好、分布均匀、速度极快著称特别适合字符串/键值哈希、哈希表、布隆过滤器以及为内容生成短标识符等场景。Emotion 之所以选中它正是因为样式字符串需要在每次渲染与构建时被快速哈希为稳定的短标识符而这恰好是 MurmurHash2 的强项。该包当前版本为 0.9.2见 packages/hash/package.json自 0.9.2 起源码已迁移为 TypeScript 编写见 packages/hash/CHANGELOG.md 中的迁移记录。二、快速上手API 与基本用法emotion/hash暴露的 API 极其简洁——一个默认导出函数入参为字符串返回该字符串的 MurmurHash2 哈希以 base36 字符串表示import hash from emotion/hash hash(some-string) // 12fj1d这一点从类型声明也能确认packages/hash/types/index.d.ts 只有一行将默认导出转发给包入口而 packages/hash/types/tests.ts 则对类型做了 dtslint 校验对应 package.json 中的test:typescript脚本。实际执行效果可以通过包自带的单元测试 packages/hash/tests/index.js 得到可复现的验证import hash from emotion/hash test(accepts a string and returns a string as a hash, () { expect(hash(something)).toBe(crsxd7) })也就是说输入字符串输出some-string12fj1dsomethingcrsxd7使用前提说明该函数只接受字符串输入返回固定长度通常 6 位左右取决于值的大小的 base36 字符串。它是同步、无状态的纯函数——相同的输入永远得到相同的输出这保证了 Emotion 生成的类名在不同环境浏览器、Node 服务端、构建期之间完全一致。三、MurmurHash2 算法原理源码逐段精读完整的算法实现位于 packages/hash/src/index.ts共 63 行函数签名与整体流程如下export default function murmur2(str: string): string { var h 0 // 初始化哈希值 var k, i 0, len str.length // ... 主循环每 4 字节一块混入哈希 // ... 尾部字节处理 // ... 最终混淆 return ((h ^ (h 15)) 0).toString(36) }3.1 常量与初始化算法注释中给出了两个混合常量mixing constants它们是通过离线调参得到的并非随手取的魔法数字注释原文为 Theyre not really magic, they just happen to work well// const m 0x5bd1e995; // const r 24; var h 0m 0x5bd1e995乘法混合常量r 24右移混合位数h初始为 0随后逐块累加混入。3.2 主循环每 4 字节一块for (; len 4; i, len - 4) { k (str.charCodeAt(i) 0xff) | ((str.charCodeAt(i) 0xff) 8) | ((str.charCodeAt(i) 0xff) 16) | ((str.charCodeAt(i) 0xff) 24) k (k 0xffff) * 0x5bd1e995 (((k 16) * 0xe995) 16) k ^ k 24 h ((k 0xffff) * 0x5bd1e995 (((k 16) * 0xe995) 16)) ^ ((h 0xffff) * 0x5bd1e995 (((h 16) * 0xe995) 16)) }这一步完成了三件事组块block用charCodeAt逐字符取出字节按小端序低字节在前拼成一个 32 位整数k。每个块为 4 字节。k 混合k乘以常量m然后异或k 24即乘以2^r再右移r位的复合效果。h 混合把混合后的k与当前的h各自乘m后异或得到新的h。值得注意的是由于 JavaScript 中的位运算会先把操作数转为 32 位整数而0x5bd1e995 * 0x5bd1e995这类乘法会超出 32 位安全范围因此源码采用了一个经典技巧用Math.imul的等价手写展开——即(k 0xffff) * m (((k 16) * (m 16)) 16)把 32 位乘法拆成高 16 位与低 16 位的组合乘法避免精度丢失。源码中的* 0x5bd1e995展开式中0xe995正是0x5bd1e995 16的十六进制表示。3.3 尾部字节处理当输入长度不足 4 的倍数时剩余 13 个字节进入switch分支注意 fall-through 的写法switch (len) { case 3: h ^ (str.charCodeAt(i 2) 0xff) 16 case 2: h ^ (str.charCodeAt(i 1) 0xff) 8 case 1: h ^ str.charCodeAt(i) 0xff h (h 0xffff) * 0x5bd1e995 (((h 16) * 0xe995) 16) }末尾字节同样按小端序放入h的高位case 3移 16 位、case 2移 8 位并在case 1处对h再做一次乘m的混合。3.4 最终混淆与输出h ^ h 13 h (h 0xffff) * 0x5bd1e995 (((h 16) * 0xe995) 16) return ((h ^ (h 15)) 0).toString(36)最后三段式混淆h ^ h 13→ 乘m→h ^ h 15用于确保末尾几个字节也被充分扩散源码注释 Do a few final mixes of the hash to ensure the last few bytes are well-incorporated。最终通过 0把结果强制转换为无符号 32 位整数再用.toString(36)转成base36 字符串返回。3.5 为什么输出用 base36toString(36)使用 0-9 与 a-z 共 36 个字符来编码一个 32 位无符号整数。这意味着更短32 位整数用十进制最多 10 位用 base36 通常只需 6 位如12fj1d、crsxd7可以直接拼接进css-xxx这样的类名而不显著增加 DOM 体积可读性全部为小写字母与数字避免大小写敏感问题也便于作为data-emotion属性值确定性同一字符串始终映射到同一短标识这是 Emotion 类名在客户端与服务端保持一致、从而支持 SSR 水合的前提。四、在 Emotion 生态中的实际应用emotion/hash的价值不在于它自身而在于它是 Emotion 类名生成链路的关键一环。仓库中有三个典型的使用点可以交叉印证。4.1 serializeStyles样式哈希生成类名在 packages/serialize/src/index.ts 中serializeStyles将插值处理后的完整样式字符串交给hashStringlet name hashString(styles) identifierName这里styles是经过插值、扁平化后的最终 CSS 文本identifierName则是从样式中的label:注释/label:\s*([^\s;{])\s*(;|$)/g提取出的可读标签。于是最终类名 样式内容的 MurmurHash2 base36 哈希 可读 label例如css-1rn8i3s或带 label 的css-1rn8i3s-Button。这正是 Emotion 中css与styled生成类名的底层机制。4.2 babel-plugin编译期生成稳定的目标类名在 Babel 插件的工具函数 packages/babel-plugin/src/utils/get-target-class-name.js 中emotion/hash被用来生成与文件路径无关、可预测的类名let hashArray (arr) hashString(arr.join()) // ... const stuffToHash [moduleName] if (finalPath) { stuffToHash.push(normalizePath(finalPath)) } else { stuffToHash.push(state.file.code) } const stableClassName e${hashArray(stuffToHash)}${positionInFile}实现会把包名 归一化后的相对文件路径拼成数组后交给hashString从而让同一源码文件在任意机器、任意绝对路径下都能编译出完全一致的类名这对于构建缓存与热更新非常重要。4.3 cache 测试SSR 水合中手动复算哈希cache 包的测试 展示了该哈希的可复现性如何被用于验证 SSR 水合测试直接用hashString(css)复算类名再断言插入的style标签内容与预期一致let hash hashString(css) expect(...).toEqual( style contenteditable="false">【免费下载链接】emotion CSS-in-JS library designed for high performance style composition项目地址: https://gitcode.com/gh_mirrors/em/emotion创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考