現(xiàn)與 className 生成原理)
深入剖析 emotion/hashEmotion 的 MurmurHash2 哈希實(shí)現(xiàn)與 className 生成原理【免費(fèi)下載鏈接】emotion? CSS-in-JS library designed for high performance style composition項(xiàng)目地址: https://gitcode.com/gh_mirrors/em/emotionemotion/hash是 Emotion 系列包中一個(gè)極小卻至關(guān)重要的基礎(chǔ)模塊它以 TypeScript 實(shí)現(xiàn)了經(jīng)典的MurmurHash2非加密哈希算法。本文將以該包的 README 為骨架結(jié)合其 源碼、測(cè)試 以及在serialize、babel-plugin、cache等包中的真實(shí)調(diào)用完整講解它的 API 用法、算法逐段原理、base36 輸出的設(shè)計(jì)考量以及它如何參與生成 Emotion 的css-*類(lèi)名與 SSR 水合hydration過(guò)程。讀完本文你將能獨(dú)立讀懂這段不到 70 行的哈希實(shí)現(xiàn)并理解為什么它是 Emotion 高性能樣式組合體系的基石之一。一、emotion/hash 是什么根據(jù) README 的定義emotion/hash就是A MurmurHash2 implementation——一個(gè) MurmurHash2 算法的實(shí)現(xiàn)。它的源碼源自經(jīng)典的 murmurhash-js 項(xiàng)目garycourt/murmurhash-js 中的murmurhash2_gc.js并進(jìn)一步對(duì)照 Austin Appleby 的原始smhasher倉(cāng)庫(kù)中MurmurHash2.cpp的參考實(shí)現(xiàn)移植而來(lái)這一移植關(guān)系在 packages/hash/src/index.ts 的源碼注釋中有明確記載。MurmurHash 是業(yè)界廣泛使用的非加密哈希non-cryptographic hash算法族由 Austin Appleby 于 2008 年設(shè)計(jì)以雪崩效應(yīng)好、分布均勻、速度極快著稱(chēng)特別適合字符串/鍵值哈希、哈希表、布隆過(guò)濾器以及為內(nèi)容生成短標(biāo)識(shí)符等場(chǎng)景。Emotion 之所以選中它正是因?yàn)闃邮阶址枰诿看武秩九c構(gòu)建時(shí)被快速哈希為穩(wěn)定的短標(biāo)識(shí)符而這恰好是 MurmurHash2 的強(qiáng)項(xiàng)。該包當(dāng)前版本為 0.9.2見(jiàn) packages/hash/package.json自 0.9.2 起源碼已遷移為 TypeScript 編寫(xiě)見(jiàn) packages/hash/CHANGELOG.md 中的遷移記錄。二、快速上手API 與基本用法emotion/hash暴露的 API 極其簡(jiǎn)潔——一個(gè)默認(rèn)導(dǎo)出函數(shù)入?yún)樽址祷卦撟址?MurmurHash2 哈希以 base36 字符串表示import hash from emotion/hash hash(some-string) // 12fj1d這一點(diǎn)從類(lèi)型聲明也能確認(rèn)packages/hash/types/index.d.ts 只有一行將默認(rèn)導(dǎo)出轉(zhuǎn)發(fā)給包入口而 packages/hash/types/tests.ts 則對(duì)類(lèi)型做了 dtslint 校驗(yàn)對(duì)應(yīng) package.json 中的test:typescript腳本。實(shí)際執(zhí)行效果可以通過(guò)包自帶的單元測(cè)試 packages/hash/tests/index.js 得到可復(fù)現(xiàn)的驗(yàn)證import hash from emotion/hash test(accepts a string and returns a string as a hash, () { expect(hash(something)).toBe(crsxd7) })也就是說(shuō)輸入字符串輸出some-string12fj1dsomethingcrsxd7使用前提說(shuō)明該函數(shù)只接受字符串輸入返回固定長(zhǎng)度通常 6 位左右取決于值的大小的 base36 字符串。它是同步、無(wú)狀態(tài)的純函數(shù)——相同的輸入永遠(yuǎn)得到相同的輸出這保證了 Emotion 生成的類(lèi)名在不同環(huán)境瀏覽器、Node 服務(wù)端、構(gòu)建期之間完全一致。三、MurmurHash2 算法原理源碼逐段精讀完整的算法實(shí)現(xiàn)位于 packages/hash/src/index.ts共 63 行函數(shù)簽名與整體流程如下export default function murmur2(str: string): string { var h 0 // 初始化哈希值 var k, i 0, len str.length // ... 主循環(huán)每 4 字節(jié)一塊混入哈希 // ... 尾部字節(jié)處理 // ... 最終混淆 return ((h ^ (h 15)) 0).toString(36) }3.1 常量與初始化算法注釋中給出了兩個(gè)混合常量mixing constants它們是通過(guò)離線調(diào)參得到的并非隨手取的魔法數(shù)字注釋原文為 Theyre not really magic, they just happen to work well// const m 0x5bd1e995; // const r 24; var h 0m 0x5bd1e995乘法混合常量r 24右移混合位數(shù)h初始為 0隨后逐塊累加混入。3.2 主循環(huán)每 4 字節(jié)一塊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逐字符取出字節(jié)按小端序低字節(jié)在前拼成一個(gè) 32 位整數(shù)k。每個(gè)塊為 4 字節(jié)。k 混合k乘以常量m然后異或k 24即乘以2^r再右移r位的復(fù)合效果。h 混合把混合后的k與當(dāng)前的h各自乘m后異或得到新的h。值得注意的是由于 JavaScript 中的位運(yùn)算會(huì)先把操作數(shù)轉(zhuǎn)為 32 位整數(shù)而0x5bd1e995 * 0x5bd1e995這類(lèi)乘法會(huì)超出 32 位安全范圍因此源碼采用了一個(gè)經(jīng)典技巧用Math.imul的等價(jià)手寫(xiě)展開(kāi)——即(k 0xffff) * m (((k 16) * (m 16)) 16)把 32 位乘法拆成高 16 位與低 16 位的組合乘法避免精度丟失。源碼中的* 0x5bd1e995展開(kāi)式中0xe995正是0x5bd1e995 16的十六進(jìn)制表示。3.3 尾部字節(jié)處理當(dāng)輸入長(zhǎng)度不足 4 的倍數(shù)時(shí)剩余 13 個(gè)字節(jié)進(jìn)入switch分支注意 fall-through 的寫(xiě)法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) }末尾字節(jié)同樣按小端序放入h的高位case 3移 16 位、case 2移 8 位并在case 1處對(duì)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用于確保末尾幾個(gè)字節(jié)也被充分?jǐn)U散源碼注釋 Do a few final mixes of the hash to ensure the last few bytes are well-incorporated。最終通過(guò) 0把結(jié)果強(qiáng)制轉(zhuǎn)換為無(wú)符號(hào) 32 位整數(shù)再用.toString(36)轉(zhuǎn)成base36 字符串返回。3.5 為什么輸出用 base36toString(36)使用 0-9 與 a-z 共 36 個(gè)字符來(lái)編碼一個(gè) 32 位無(wú)符號(hào)整數(shù)。這意味著更短32 位整數(shù)用十進(jìn)制最多 10 位用 base36 通常只需 6 位如12fj1d、crsxd7可以直接拼接進(jìn)css-xxx這樣的類(lèi)名而不顯著增加 DOM 體積可讀性全部為小寫(xiě)字母與數(shù)字避免大小寫(xiě)敏感問(wèn)題也便于作為data-emotion屬性值確定性同一字符串始終映射到同一短標(biāo)識(shí)這是 Emotion 類(lèi)名在客戶端與服務(wù)端保持一致、從而支持 SSR 水合的前提。四、在 Emotion 生態(tài)中的實(shí)際應(yīng)用emotion/hash的價(jià)值不在于它自身而在于它是 Emotion 類(lèi)名生成鏈路的關(guān)鍵一環(huán)。倉(cāng)庫(kù)中有三個(gè)典型的使用點(diǎn)可以交叉印證。4.1 serializeStyles樣式哈希生成類(lèi)名在 packages/serialize/src/index.ts 中serializeStyles將插值處理后的完整樣式字符串交給hashStringlet name hashString(styles) identifierName這里styles是經(jīng)過(guò)插值、扁平化后的最終 CSS 文本identifierName則是從樣式中的label:注釋/label:\s*([^\s;{])\s*(;|$)/g提取出的可讀標(biāo)簽。于是最終類(lèi)名 樣式內(nèi)容的 MurmurHash2 base36 哈希 可讀 label例如css-1rn8i3s或帶 label 的css-1rn8i3s-Button。這正是 Emotion 中css與styled生成類(lèi)名的底層機(jī)制。4.2 babel-plugin編譯期生成穩(wěn)定的目標(biāo)類(lèi)名在 Babel 插件的工具函數(shù) packages/babel-plugin/src/utils/get-target-class-name.js 中emotion/hash被用來(lái)生成與文件路徑無(wú)關(guān)、可預(yù)測(cè)的類(lèi)名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}實(shí)現(xiàn)會(huì)把包名 歸一化后的相對(duì)文件路徑拼成數(shù)組后交給hashString從而讓同一源碼文件在任意機(jī)器、任意絕對(duì)路徑下都能編譯出完全一致的類(lèi)名這對(duì)于構(gòu)建緩存與熱更新非常重要。4.3 cache 測(cè)試SSR 水合中手動(dòng)復(fù)算哈希cache 包的測(cè)試 展示了該哈希的可復(fù)現(xiàn)性如何被用于驗(yàn)證 SSR 水合測(cè)試直接用hashString(css)復(fù)算類(lèi)名再斷言插入的style標(biāo)簽內(nèi)容與預(yù)期一致let hash hashString(css) expect(...).toEqual( style contenteditable="false">【免費(fèi)下載鏈接】emotion? CSS-in-JS library designed for high performance style composition項(xiàng)目地址: https://gitcode.com/gh_mirrors/em/emotion創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考