溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

什么是sourcemap

發(fā)布時(shí)間:2021-06-15 16:20:40 來源:億速云 閱讀:176 作者:chen 欄目:web開發(fā)

這篇文章主要講解了“什么是sourcemap”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“什么是sourcemap”吧!

 前言

而今,只要是工程化的項(xiàng)目,大多離不開 sourcemap  的身影,一言蔽之:構(gòu)建處理前的代碼和處理后的代碼之間的橋梁。但卻很少有同學(xué)真的去深入了解它的運(yùn)作原理,真問起來也就停留在“啊,有個(gè).map  文件,可以通過它定位到源碼信息”,來,我們?nèi)コ虺?,源碼是一句簡(jiǎn)單的`console.log('好好學(xué)習(xí),天天向上'`)的`.map`文件

什么是sourcemap

如果我告訴你,位置信息就在mapping對(duì)應(yīng)的這堆字母里

sourcemap成為了房間里的大象,一旦出現(xiàn)諸如“無法映射到源文件”“只能映射到 loader 處理后的文件”等問題,多數(shù)人是毫無頭緒的;而就像 TJ  大神說的: "不要直接 copy 解決方案,要理解后自己去實(shí)現(xiàn)";

閑言少敘,書歸正傳(不好意思,最近愛看評(píng)書),通過本文你將收獲什么呢?

本文目標(biāo)

sourcemap配置項(xiàng)給你安排的明明白白,順帶送上生產(chǎn)環(huán)境、開發(fā)環(huán)境最佳實(shí)踐

sourcemap定位原理給你安排的明明白白,Base64-VQL是怎么做到生成mapping記錄源碼和處理后代碼間的映射

感恩大奉送,編碼給你安排的明明白白,base64 編碼、VLQ 編碼、base64-vlq 編碼的三世孽緣

讀至此處,您還要跑?

冰冰動(dòng)圖大合集云盤,en,是不可能給的,就給一張你們瞅瞅好了

sourcemap:devTools 配置項(xiàng)二三事

對(duì)于sourcemap而言,我們最常見的,莫過于在 webpack  的配置項(xiàng)devTools中進(jìn)行使用,而有多少種供我們選擇的配置呢?

什么是sourcemap

也不多,二十種的樣子,好了,官網(wǎng)鏈接在此,大家去背吧,背完記得喊一聲,本文完。

抱歉我皮了,所謂變中取定,這么多種配置項(xiàng)其實(shí)只是五個(gè)關(guān)鍵字 eval、source-map、cheap、module 和 inline  的組合罷了,請(qǐng)牢記這張表,破陣心法,忽悠時(shí)方可娓娓道來。

什么是sourcemap

怎么理解呢?實(shí)戰(zhàn)見真知。

舉例詳解

文件源碼如下

let a = 1,b; b = a;

source-map 處理后輸出結(jié)果

//# sourceMappingURL=bundle.js.map

 什么是sourcemap

eval 處理后輸出結(jié)果

eval("var a = 1,\n    b;\nb = a;\n\n//////////////////\n// WEBPACK FOOTER\n// ./src/index.js\n// module id = 0\n// module chunks = 0\n\n//# sourceURL=webpack:///./src/index.js?");

 什么是sourcemap

解決問題:原作者解釋whyeval,關(guān)鍵在于下面兩句話

  • devtool: "source-map" cannot cache SourceMaps for modules and need to  regenerate complete SourceMap for the chunk. It's something for production.

  • devtool: "eval-source-map" is really as good as devtool: "source-map", but  can cache SourceMaps for modules. It's much faster for rebuilds.

翻譯來說劃重點(diǎn):加 eval 和不加是一樣的?,但加了eval后可以緩存,于是更?。

Inline-source-map處理后輸出結(jié)果

//# sourceMappingURL=data: ...(base64 字符串)

 什么是sourcemap

cheap-source-map處理后輸出結(jié)果

//# sourceMappingURL=bundle.js.map

 什么是sourcemap

對(duì)于cheap-source-map而言,只會(huì)定義到出錯(cuò)的這一行

什么是sourcemap

而對(duì)于source-map而言,則會(huì)精準(zhǔn)到列

什么是sourcemap

存在的問題

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 錯(cuò)誤信息只會(huì)定義到行,而不會(huì)定義到列

  3. 對(duì)于經(jīng)由 babel 之類工具轉(zhuǎn)義的代碼,只能定位到轉(zhuǎn)換后的代碼

這就引出了我們最后的一個(gè)關(guān)鍵字

cheap-module-source-map處理后輸出結(jié)果

//# sourceMappingURL=bundle.js.map

 什么是sourcemap

測(cè)試代碼

# sum let sum =  (a, b) => {     return a + b } debugger export default sum; # index.js import sum from './sum'; console.log(sum);

對(duì)于cheap-source-map而言,此時(shí)頁面 debugger 展示源碼是 es5 的代碼,因?yàn)橐呀?jīng)被 babal 轉(zhuǎn)義了

什么是sourcemap

而對(duì)于source-map而言,則會(huì)精準(zhǔn)到原始代碼

什么是sourcemap

配置項(xiàng)關(guān)鍵字小結(jié)

至此,我們`source-map`的五個(gè)關(guān)鍵詞的學(xué)習(xí)也就告一段落了,而最開始提到官網(wǎng)給出的二十幾種配置無非是選詞組合而已,再附送下一些常見配置項(xiàng)的關(guān)鍵參數(shù)對(duì)比吧。

什么是sourcemap

配置項(xiàng)最佳實(shí)踐

開發(fā)環(huán)境

  • 我們?cè)陂_發(fā)環(huán)境對(duì) sourceMap 的要求是:快(eval),信息全(module),

  • 且由于此時(shí)代碼未壓縮,我們并不那么在意代碼列信息(cheap),

所以開發(fā)環(huán)境比較推薦配置:devtool: cheap-module-eval-source-map

生產(chǎn)環(huán)境

  • 一般情況下,我們并不希望任何人都可以在瀏覽器直接看到我們未編譯的源碼,

  • 所以我們不應(yīng)該直接提供 sourceMap 給瀏覽器。但我們又需要 sourceMap 來定位我們的錯(cuò)誤信息,

  • 一方面 webpack 會(huì)生成 sourcemap 文件以提供給錯(cuò)誤收集工具比如 sentry,另一方面又不會(huì)為 bundle  添加引用注釋,以避免瀏覽器使用。

這時(shí)我們可以設(shè)置devtool: hidden-source-map

至此,關(guān)于sourcemap在 webpack 中的應(yīng)用層面我們就算是了解個(gè)七七八八了。但其實(shí),這只是一個(gè)開頭小菜

本文最大目標(biāo)來啦:sourcemap 到底怎么做到源文件和處理后文件映射的?

輸出內(nèi)容分析:map 文件詳解

要分析實(shí)現(xiàn),還是得先從現(xiàn)象下手,假定源文件script.js內(nèi)容為

let a=1; let b=2; let c=3;

其輸出內(nèi)容為

script-min.js

var a=1,b=2,c=3;

script-min.js.map

{"version":3,"file":"script-min.js","lineCount":1,"mappings":"AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE;","sources":["script.js"],"names":["a","b","c"]}

文件字段具體含義分析

 什么是sourcemap

可以看到,既然我們要定位,自然最關(guān)心的是具有【記錄位置信息】功能的 mapping 屬性,接下來詳細(xì)講解如何分析mapping。

mapping 屬性值含義

什么是sourcemap

  • 【行對(duì)應(yīng)】很好理解,即一個(gè)分號(hào)為一行,因?yàn)閴嚎s后基本上都是一行了,所以這個(gè)沒啥有用信息;

  • 【位置對(duì)應(yīng)】可以理解為分詞,每個(gè)逗號(hào)對(duì)應(yīng)轉(zhuǎn)換后源碼的一個(gè)位置;

  • 【分詞信息】是關(guān)鍵,如AAAA代表該位置轉(zhuǎn)換前的源碼位置,以VLQ編碼表示;

其中【分詞信息】每組最多五位(如果不是變量,只會(huì)有四位),分別是:

  • 第一位,表示這個(gè)位置在【轉(zhuǎn)換后代碼】的第幾列。

  • 第二位,表示這個(gè)位置屬于【sources 屬性】中的哪一個(gè)文件。

  • 第三位,表示這個(gè)位置屬于【轉(zhuǎn)換前代碼】的第幾行。

  • 第四位,表示這個(gè)位置屬于【轉(zhuǎn)換前代碼】的第幾列。

  • 第五位,表示這個(gè)位置屬于【names 屬性】的哪一個(gè)變量。

到此,我們也算是知道 map 文件到底是怎么組成的了。

小思考

Q:但為什么這么設(shè)定呢?理解絕對(duì)比死記更有效,其他都好理解,但談到分詞信息中的位置對(duì)應(yīng),我們下意識(shí)應(yīng)該都會(huì)想到坐標(biāo),記錄組成元素在編譯后文件和源文件的坐標(biāo),就形成了映射;但我們看到的`mapping`卻是字符串,為什么?A:因?yàn)轶w積,如果直接坐標(biāo)記錄信息,至少存在兩點(diǎn)空間損耗:編譯后文件的縱坐標(biāo)大的驚人;因?yàn)樽鴺?biāo)信息是數(shù)字,如果采用數(shù)組存儲(chǔ),將有大量存儲(chǔ)空間浪費(fèi)。注意上面的`version`字段,象征著版本,而對(duì)于現(xiàn)在的默認(rèn)也是最新版`Source  Map Revision 3.0`(V3)而言,通過使用 Base64 VLQ 編碼,大大縮小了.map  文件的體積,而這也是本文最有價(jià)值的思考點(diǎn):`Base64 VLQ`是啥?為什么能做到縮減體積。

此處附送base64vlq  在線轉(zhuǎn)換地址,將上面的mappings對(duì)應(yīng)的字符串輸入,將會(huì)得到對(duì)應(yīng)的數(shù)字信息,如AAAA對(duì)應(yīng)的是0000,這兩者之間的映射規(guī)則就是base64vlq編碼。

整理目標(biāo)

到此,我們整理下接下來要做的事情,抬頭看天,低頭走路。

我們希望解決坐標(biāo)信息占用空間過大的問題,主要在于兩點(diǎn)

  • 編譯后文件列號(hào)過大問題:因?yàn)闀?huì)編譯成一行,可以想象靠后的元素縱坐標(biāo)是很大的

  • 數(shù)據(jù)結(jié)構(gòu)占據(jù)空間問題:數(shù)組自然比字符串更耗費(fèi)空間

隨著對(duì)這兩個(gè)問題的思考,我們將會(huì)徹底理解,為啥我們用于記錄位置信息的mapping會(huì)是這個(gè)鬼樣子

AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE

打起精神,繼續(xù)學(xué)!冰冰續(xù)命

相對(duì)位置解決列號(hào)過大問題

對(duì)于第一點(diǎn)輸出后的位置元素的列號(hào)特別大的問題,可以采用相對(duì)位置的方案進(jìn)行解決,具體規(guī)則如下

  • 第一次記錄的輸入位置和輸出位置是絕對(duì)的,往后的輸入位置和輸出位置都是相對(duì)上一次的位置移動(dòng)了多少

舉例而言,假設(shè) a.js 內(nèi)容為feel the force,處理后輸出the force feel ,其 names  為:['feel','the','force'], source: ['a.js']

什么是sourcemap

則其按照相對(duì)位置輸出的關(guān)系如下

| 字符組合 | 位置類別 | 輸出位置 | 輸入位置 | 映射(輸出 x | 屬于文件在 source 的索引 | 輸入 x | 輸入 y | 變量在  names 的索引) | | --- | --- | --- | --- | --- | | feel | 絕對(duì) | 10, 0 | 0, 0 | 10 | 0  | 0 | 0 | 0 | | the | 相對(duì) | -10, 0 | 5, 0 | -10 | 0 | 5 | 0 | 1 | | force | 相對(duì) |  4, 0 | 4, 0 | 4 | 0 | 4 | 0 | 2 |

其中【位置類別】的相對(duì)代表相對(duì)上一個(gè)元素坐標(biāo)的偏移,比如`the`的輸出位置相對(duì)于`feel`就是 x 軸左移 10,y  軸不變,所以其輸出位置為(-10, 0);其輸入位置是 x 軸右移 5,y 軸不變,所以其輸入位置為(5  ,0),`force`對(duì)比`the`類推即可。有心的小伙伴會(huì)發(fā)現(xiàn)【輸出 y 坐標(biāo)】在映射中沒有記錄,其原因也很簡(jiǎn)單,因?yàn)樘幚砗蟮妮敵龃a都是一行的,固定為  0,所以也就沒必要記錄了現(xiàn)在,我們就來到了第二點(diǎn)了,如何壓縮`mapping`?涉及到壓縮體積,便逃不掉編碼

就像上面說的,sourcemap 通過`Base64 VLQ`編碼進(jìn)行了縮小.map 文件的體積的處理。那就開始琢磨下關(guān)鍵而神奇的`Base64  VLQ`吧

base64VLQ 解決數(shù)據(jù)結(jié)構(gòu)占據(jù)空間問題

以坐標(biāo)信息`[0,0,0,0],  [4,0,0,4,0]`為例首先,我們得明確,對(duì)于源、目標(biāo)文件的元素坐標(biāo)映射關(guān)系,數(shù)組是不可能用數(shù)組的,這輩子是不可能用數(shù)組的,用字符串不香嗎?(原因就不解釋了)


既然是字符串,原例就變成了`0,0,0,0 | 4,0,0,4,0`  ,有沒有感覺這個(gè)`,`有點(diǎn)不順眼?本來它們就都是描述同一個(gè)映射關(guān)系,干嘛還浪費(fèi)這空間,想想我們編譯后的那一大串,如果保留這個(gè)分隔符,別扭的很,那如何去掉呢?主角`Base64-VLQ`登場(chǎng)。Base64-VLQ  編碼見名知意,其實(shí)就是 VLQ 編碼方式和 base64 編碼的“一套組合拳”,它能去除分隔符主要在于 VLQ  編碼方式【變長(zhǎng)】的特性,關(guān)鍵點(diǎn)就一句:用二進(jìn)制表示,進(jìn)行分組后每組最高位表示連續(xù)性,如果是 1,代表這組字節(jié)后面的一組字節(jié)也屬于同一個(gè)數(shù);如果是  0,表示該數(shù)值到這就結(jié)束了。不明白?十臉懵逼?沒關(guān)系,咱一步步來。柿子挑軟的捏,要了解`Base64  VLQ`,咱就先查漏補(bǔ)缺下最熟悉的陌生人:`base64`編碼。


BASE64 編碼

我們開發(fā)同學(xué)最初了解到base64大概是在小icon圖標(biāo)的處理上,當(dāng)時(shí)了解到的是可以將圖片的二進(jìn)制轉(zhuǎn)為文本,從而減少 http  請(qǐng)求,但只適用于小圖標(biāo)等體積小的內(nèi)容,因?yàn)槭褂胋ase64編碼處理過會(huì)導(dǎo)致被處理對(duì)象體積增加  33%;那么base64到底是什么?它出現(xiàn)就是為了處理小圖標(biāo)嗎?了解事物的經(jīng)典三問

是什么?

在 MDN 中的定義:

  • 是一組相似的二進(jìn)制到文本(binary-to-text)的編碼規(guī)則,使得二進(jìn)制數(shù)據(jù)在解釋成 radix-64 的表現(xiàn)形式后能夠用 ASCII  字符串的格式表示出來。

為什么出現(xiàn)?

回想一下,有沒有遭遇過用記事本打開exe、jpg、pdf這些文件時(shí),看到一大堆亂碼?很簡(jiǎn)單,在 ASCII 碼中規(guī)定,031、127 這 33  個(gè)字符屬于控制字符,32126 這 95 個(gè)字符屬于可打印字符(來源于Unicode 官網(wǎng)),也就是說網(wǎng)絡(luò)傳輸、文本處理只能使用這 95  個(gè)字符,不在這個(gè)范圍內(nèi)的字符無法使用。那么該怎么才能傳輸其他字符呢?這就就需要一個(gè)二進(jìn)制到字符串的轉(zhuǎn)換方法。

怎么做到的?

編碼本身并不復(fù)雜,對(duì)使用者而言按圖索驥而已,關(guān)鍵是它規(guī)則為什么這么設(shè)定,以及修補(bǔ)規(guī)則出現(xiàn)的原因。

既然 ASCII 碼表中存在不可打印字符,那我們就定義一個(gè)新碼表,其范圍固定在可打印字符內(nèi)。(這就意味著要多個(gè)新碼表字符表示一個(gè) ASCII  碼表字符,原因很簡(jiǎn)單,你要用蘋果、梨表示所有水果,那只好定義兩個(gè)蘋果是西瓜、兩個(gè)梨是番茄、一個(gè)蘋果一個(gè)梨是。。。排列組合)

新碼表字符的組成單元占幾個(gè)字節(jié)?

我們知道基礎(chǔ) ASCII 碼,使用 7 位二進(jìn)制數(shù)表示組成單元,新碼表的表示范圍小于 ASCII  碼表(因?yàn)橐_保新碼表中都是可打印字符),這也就意味著,新碼表使用的二進(jìn)制數(shù)必須少于七位,而二進(jìn)制數(shù)越少代表其能表示的字符越少,那就需要更多個(gè)二進(jìn)制數(shù)來表示一個(gè)字符,而這個(gè)位數(shù)應(yīng)該是越多越好的,因?yàn)檫@樣我們所有的組成元素(新碼表)就多了,于是定  6 位吧。2^6 是 64,于是新碼表叫做 base64(這塊純屬于個(gè)人理解,僅供推理記憶,如有錯(cuò)誤請(qǐng)不吝賜教)

如何 ASCII 碼進(jìn)行 Base64 編解碼

ASCII 碼字符占 8 位二進(jìn)制,而 Base64 占 6 位,取最小公倍數(shù)即為 24,即可以用 4 個(gè) base64 字符去表示 3 個(gè) ASCII  碼字符。遂有如下轉(zhuǎn)換規(guī)定

1.ASCII 碼字符串根據(jù) ASCII 碼對(duì)照表轉(zhuǎn)換為二進(jìn)制數(shù)值;

2.把二進(jìn)制數(shù)值按每 6 位進(jìn)行劃分;

  • 假設(shè)字符數(shù)是 3 的倍數(shù),比如三個(gè) ASCII 碼字符,就可以三個(gè)為一組,用四個(gè) base64 字符來表示(3 * 8 == 4 *  6,enen,應(yīng)該好理解的)

  • 如果待編碼字符串的長(zhǎng)度不是 3 的倍數(shù),則用 0 補(bǔ)位. 如果有連續(xù) 6 位都是 0 的話, 就用=來表示。

3.然后 6 位二進(jìn)制轉(zhuǎn)化為十進(jìn)制根據(jù) Base64 對(duì)照表找到編碼字符.

對(duì)照表

什么是sourcemap

擴(kuò)展

在JavaScript中,原生提供了 base64 和 ASCII 碼之間的轉(zhuǎn)換 API

什么是sourcemap

舉例而言

以 ASCII 的 A 字符為例,A 轉(zhuǎn)為二進(jìn)制如上010000001,不足三位,所以補(bǔ) 0,從而補(bǔ)齊 24 位

什么是sourcemap

再 6 位為一組,對(duì)照Base64 編碼表,全為 0 的話用=號(hào)代替

什么是sourcemap

所以,得出結(jié)果,A 字符對(duì)應(yīng)的 Base64 編碼是QQ==

什么是sourcemap

至此,我們就算是對(duì) base64 這位“最熟悉的陌生人”至少能答出個(gè)來龍去脈了

擴(kuò)展-修補(bǔ)規(guī)則:URL 安全的 Base64 編碼

修補(bǔ)規(guī)則出現(xiàn)背景

Base64 編碼可用于在[HTTP](http://zh.wikipedia.org/wiki/HTTP)環(huán)境下傳遞較長(zhǎng)的標(biāo)識(shí)信息,然而,標(biāo)準(zhǔn)的  Base64 并不適合直接放在 URL 里傳輸,因?yàn)?URL 編碼器會(huì)把標(biāo)準(zhǔn) Base64  中的「/」和「+」字符變?yōu)樾稳纭?XX」的形式,而這些「%」號(hào)在存入數(shù)據(jù)庫時(shí)還需要再進(jìn)行轉(zhuǎn)換,因?yàn)閇ANSI](http://zh.wikipedia.org/wiki/ANSI)  [SQL](http://zh.wikipedia.org/wiki/SQL)中已將「%」號(hào)用作通配符。咱證明下權(quán)威性,來看看`rfc`中的定義

  • For base 64, the non-alphanumeric characters (inparticular, "/") may be  problematic in file names and URLs.

翻譯過來即:

  • 對(duì)于 base 64 編碼, 非字母表中的字符 (特別是 "/") 可能會(huì)在文件名和 URL 中出現(xiàn)問題

修補(bǔ)規(guī)則

為解決此問題,可采用一種**用于 URL 的改進(jìn) Base64**編碼,具體也很簡(jiǎn)單,加密時(shí)先執(zhí)行三條規(guī)則再轉(zhuǎn)`base64`即可(解密反之)

  • 不在末尾填充=號(hào)

  • + 用 *替換

  • / 用 -替換

擴(kuò)展-實(shí)現(xiàn):在JavaScript中如何實(shí)現(xiàn)base64url的編碼解碼?

編碼

function urlsafe_b64encode(str) {       base64Str = base64_encode(str);       base64UrlStr = base64Str.replaceaLL('+','*').replaceaLL('/','-').replaceaLL('=','');       return base64UrlStr;    }

解碼

function urlsafe_b64decode(base64UrlStr) {       data = base64Str.replaceaLL('*','+').replaceaLL('-','/');        let mod4 = strlen(data) % 4;        if (mod4) {            data = data.substr('====', mod4);        }        return base64_decode(data);     }

有興趣的小伙伴可以使用處理 base64url 的 npm 包地址自行去驗(yàn)證下哦

# npm i base64url  const base64url = require('base64url'); console.log('字符串     base64     base64URL'); console.log(' A     ', encode('A'),'     ',base64url("A")); //  A      QQ==       QQ

什么是sourcemap

VLQ 編碼

這是一個(gè)很陌生的詞匯,但卻是sourcemap實(shí)現(xiàn)的核心工具,還是老樣子,了解事物的經(jīng)典三問

是什么?

  • VLQ 是 Variable-length quantity 的縮寫,變長(zhǎng)編碼,用以通過任意位二進(jìn)制精簡(jiǎn)地表示很大的數(shù)值。

為什么出現(xiàn)?

縮減多數(shù)字組成的元素所占據(jù)的空間(比如按上文所生成的mapping,就是通過|進(jìn)行區(qū)分不同數(shù)字的,這個(gè)|其實(shí)最好可以省掉,因?yàn)橹皇菫榱碎喿x者好理解而已)

怎么做到的?

將數(shù)字轉(zhuǎn)化為二進(jìn)制,然后規(guī)定通過二進(jìn)制始末位具有標(biāo)識(shí)數(shù)字的起始的特殊含義,從而節(jié)省分隔符所占的空間,設(shè)定如下:

一個(gè)二進(jìn)制字節(jié)有 8 個(gè)位,在 VLQ 編碼中設(shè)定最高位為是否連續(xù)的標(biāo)識(shí),除了最高位,如果不足 7 個(gè)位的倍數(shù)則高位補(bǔ) 0;

對(duì)于大數(shù)字而言,需要多個(gè)單元進(jìn)行表示,那么如果得知這幾個(gè)單元屬于同一個(gè)數(shù)字,編碼規(guī)則設(shè)定最高位表示連續(xù)性,如果是  1,代表這組字節(jié)后面的一組字節(jié)也屬于同一個(gè)數(shù);如果是 0,表示該數(shù)值到這就結(jié)束了;這就避免了分隔符,如|,的出現(xiàn)

舉例而言

對(duì)于數(shù)字 7 和 1200 以及-7,如果用 VLQ 分別該怎么進(jìn)行表示呢?

什么是sourcemap

對(duì) 7 而言

  • 先判斷是不是七的倍數(shù):不是,只有三位,所以前置位補(bǔ) 4 個(gè) 0;

  • 只有一個(gè)單元,自然最高位是 0(標(biāo)識(shí)該數(shù)的結(jié)束);

得出的 VLQ 編碼就是0000111;

對(duì)于 1200 而言

  • 先判斷是不是七的倍數(shù):不是,有 11 位,所以前置位補(bǔ) 3 個(gè) 0,處理對(duì)象變?yōu)?0010010110000

  • 只有兩個(gè)單元,所以第一個(gè)單元最高位是 1(標(biāo)識(shí)下一個(gè)單元還是表示該數(shù)),第二個(gè)單元是 0;

最后得到的結(jié)果即1000100100110000

到此,VLQ的編碼也算是告一段落了,其實(shí)就是一個(gè)規(guī)定,規(guī)定二進(jìn)制某些位具有特定含義,從而節(jié)省空間,反正處理時(shí)按規(guī)定解碼就好了,也不需要人去看,再怎么難理解也是計(jì)算機(jī)的事兒,懶人改變世界嘛。

擴(kuò)展一:VLQ 偏移自然數(shù):Git 底層格式

在了解 VLQ 編碼的過程中,意外的了解到了很多冰山之下的知識(shí),比如【VLQ 與自然數(shù)的相互轉(zhuǎn)換】在 Git  中居然有專門的實(shí)現(xiàn)的一個(gè)算法:雙射計(jì)數(shù)法(bijective numeration),主要是如何做到一一映射從而避免多字節(jié) VLQ  的冗余現(xiàn)象,這個(gè)與本文主題相差有點(diǎn)大,不做過分拓展,推薦一篇文章《深扒 Git 底層格式:VLQ 偏移自然數(shù)》

擴(kuò)展二:在JavaScript中如何實(shí)現(xiàn) VLQ 的編碼?

/**如何對(duì)數(shù)值進(jìn)行 VLQ 編碼 * 1. 將數(shù)值改寫成二進(jìn)制形式 10001001 * 2. 七位一組做分組,不足的補(bǔ) 0 * 3. 最后一組開頭補(bǔ) 0,其余補(bǔ) 1 * 4. 拼接 得出編碼 * @param {*} num  */function encodeForVLQ(num) {    let binary = num.toString(2);    let padded = binary.padStart(Math.ceil(binary.length / 7) * 7, '0');    let groups = padded.match(/\d{7}/g);     groups = groups.map((group,index)=>{          let pre = (index==0 && groups.length > 1?'1':'0')          return pre + group;      });    let vlqCode = groups.join('');    return vlqCode}console.log(encodeForVLQ2(7));console.log(encodeForVLQ2(1200));

至此,我們就完成了對(duì) VLQ 編碼處理的了解,但我們可以發(fā)現(xiàn)輸出的是二進(jìn)制數(shù),而如果我們希望獲得的是字符串,就需要使用到BASE64  vlq編碼處理了

Base-VLQ 編碼

見名知意,其實(shí)就是 VLQ 編碼方式和 base64 編碼的結(jié)合。不過有幾點(diǎn)與 VLQ 的區(qū)別也需要注意一下

  • Base64 VLQ 需要能夠表示負(fù)數(shù),于是用第一個(gè)單元的最后一位來作為符號(hào)標(biāo)志位

  • 在 Base64 VLQ 中,因?yàn)橐?base64 相對(duì)應(yīng),所以修改vlq7 位一組的設(shè)定,改為 5 位一組,加上設(shè)定為最高位的連續(xù)位正好六位。

是什么?

對(duì)數(shù)字進(jìn)行 VLQ 編碼處理后,使用 base64 字符表示 VLQ 編碼的結(jié)果。

為什么出現(xiàn)?

考慮到 mapping 文件的可閱讀和文本軟件處理的問題,VLQ 轉(zhuǎn)化后的二進(jìn)制應(yīng)該通過可打印字符去表示。

怎么做到的?

大致與 VLQ  編碼的處理邏輯相似,都是分組,然后用最高位表示連續(xù),最關(guān)鍵的不同在于base64-vlq需要表示負(fù)數(shù),于是用第一個(gè)單元的最后一位來作為符號(hào)標(biāo)志位。

1.在 base64 中,將 VLQ 存儲(chǔ)單元是 8 個(gè) btye 的設(shè)定改為 6 個(gè),這樣就對(duì)應(yīng)上了 base64 的存儲(chǔ)格式。

2.對(duì)于正負(fù)而言,編碼規(guī)則設(shè)定第一個(gè)單元的最后一位用于表示正負(fù)數(shù),零正一負(fù);

  • 這里需要注意,為什么說是【第一個(gè)單元】,因?yàn)橐还擦鶄€(gè)位,去掉一個(gè)表示連續(xù),一個(gè)表示正負(fù),那能表示的范圍是[-15,15],如果數(shù)字過大,就會(huì)需要多個(gè)單元去描述(這也是說其變長(zhǎng)的原因)非第一個(gè)單元是不需要表示正負(fù)的,所以只需要最高位表示是否終止即可。

3.將 VLQ 每個(gè)單元對(duì)應(yīng)的 base64 字符存儲(chǔ)下來即可

舉例而言

對(duì)于數(shù)字 7 和 1200 以及-7,如果用base64-VLQ分別該怎么進(jìn)行表示呢?

什么是sourcemap

首先,對(duì) 7 而言

  • 只有一個(gè)單元,自然最高位是 0;

  • 正數(shù),所以最后一位是 0;

  • 最后只有三位,所以前置位補(bǔ) 0;

得出的 VLQ 編碼就是001110;

什么是sourcemap

其次,對(duì)于 1200 而言

先完成第一個(gè)單元

  • 多個(gè)單元,所以連續(xù),最高位是 1;

  • 正數(shù),所以最后一位是 0;

  • 超過一個(gè)單元,所以對(duì)于第一個(gè)單元在二進(jìn)制中從后取出四個(gè)數(shù)出來填充四位即可

那得到的第一個(gè)單元的組成就是100000

再完成第二個(gè)單元

  • 還是填不滿,所以聯(lián)系,最高位是 1

  • 非第一個(gè)單元,所以不管正負(fù)了,取出五個(gè)數(shù)填充五位即可

得到的第二個(gè)單元的組成就是101011

例推下去,最后得到的結(jié)果即100000101011000010

 什么是sourcemap

根據(jù)mapping獲取信息

知道了 mapping  文件時(shí)如何來的,關(guān)鍵在于我們常見的場(chǎng)景是有類似AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE的 mapping  文件,那我們?cè)趺磸闹蝎@取信息呢?很簡(jiǎn)單,按照編碼方式反著來就好了,具體可見【擴(kuò)展-解碼】的實(shí)現(xiàn)。

擴(kuò)展:在JavaScript中如何實(shí)現(xiàn)base64-VLQ的編碼解碼?

在此實(shí)現(xiàn)一個(gè)簡(jiǎn)易版本,其實(shí)目前有base64-vql的處理包,處理 vql 的 npm 包地址,其內(nèi)部實(shí)現(xiàn)大多用到的是諸如>>>  |之類的位運(yùn)算,在此就不過分展開了,有機(jī)會(huì)單獨(dú)出一篇文章,思路是相似的。

編碼

let base64 = [    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',    'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/']; /**如何對(duì)數(shù)值進(jìn)行 bae64-VLQ 編碼 * 1. 將數(shù)值改寫成二進(jìn)制形式  * 2. 五位一組做分組,不足的補(bǔ) 0,并將組倒序排序 * 3. 最后一組開頭補(bǔ) 0,其余補(bǔ) 1 * 4. 轉(zhuǎn) bae64 進(jìn)制,即通過對(duì)應(yīng)索引在 base64 碼表中取值  * @param {*} num  */function encode(num) {    //1. 改寫成二進(jìn)制形式,如果是負(fù)數(shù)的話是絕對(duì)值轉(zhuǎn)二進(jìn)制    let binary = (Math.abs(num)).toString(2);    //2.正數(shù)最后邊補(bǔ) 0,負(fù)數(shù)最右邊補(bǔ) 1,127 是正數(shù),末位補(bǔ) 0     binary = num >= 0 ? binary + '0' : binary + '1';    //3.五位一組做分組,不足的補(bǔ) 0     let zero = 5 - (binary.length % 5);    if (zero > 0) {        binary = binary.padStart(Math.ceil(binary.length / 5) * 5, '0');    }    let parts = [];    for (let i = 0; i < binary.length; i += 5) {        parts.push(binary.slice(i, i + 5));    }    //4. 將組倒序排序     parts.reverse();    //5. 最后一組開頭補(bǔ) 0,其余補(bǔ) 1    for (let i = 0; i < parts.length; i++) {        if (i === parts.length - 1) {            parts[i] = '0' + parts[i];        } else {            parts[i] = '1' + parts[i];        }    }    //6. 轉(zhuǎn) bae64 進(jìn)制,即通過對(duì)應(yīng)索引在 base64 碼表中取值     let chars = [];    for (let i = 0; i < parts.length; i++) {        chars.push(base64[parseInt(parts[i], 2)]);    }    return chars.join('')}let result = encode(137);console.log(result);

解碼

let base64 = [    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',    'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'];function getValue(char) {    let index = base64.findIndex(item => item == char);//先找這個(gè)字符的索引    let str = (index).toString(2);//索引轉(zhuǎn)成 2 進(jìn)制    str = str.padStart(6, '0');//在前面補(bǔ) 0 補(bǔ)到 6 位    //最后一位是符號(hào)位,正數(shù)最后一位是 0,負(fù)數(shù)最后一位為 1    let sign = str.slice(-1)=='0'?1:-1;    //最后一組第一位為 0,其它的第一位為 1    str = str.slice(1, -1);    return parseInt(str, 2)*sign;}function decode(values) {    let parts = values.split(',');//分開每一個(gè)位置    let positions = [];    for(let i=0;i<parts.length;i++){        let part = parts[i];        let chars = part.split('');//得到每一個(gè)字符        let position = [];        for (let i = 0; i < chars.length; i++) {            position.push(getValue(chars[i]));//獲取此編寫對(duì)應(yīng)的值        }        positions.push(position);    }    return positions;}let positions = decode('AAAA,IAAIA,EAAE,CAAN,CACIC,EAAE,CADN,CAEIC,EAAE');//后列,哪個(gè)源文件,前行,前列,變量 console.log('positions',positions);let offsets = positions.map(item=>[item[2],item[3],0,item[0],]);console.log('offsets',offsets);let origin = {x:0,y:0};let target = {x:0,y:0};let mapping=[];for(let i=0;i<offsets.length;i++){    let [originX,originY,targetX,targetY] = offsets[i];    origin.x += originX;    origin.y += originY;    target.x += targetX;    target.y += targetY;    mapping.push(`[${origin.x},${origin.y}]=>[${target.x},${target.y}]`);}console.log('mapping',mapping);

有興趣的小伙伴可以使用處理 vql 的 npm 包地址自行去驗(yàn)證下哦

# npm i vlq const vlq = require('vlq');console.log(vlq.encode( 137 )); // yIconsole.log(vlq.decode( 'yI' )); // 137

什么是sourcemap

關(guān)于base64-vlq的尾聲

到此,base64-VLQ的編碼也算是告一段落了,其實(shí)就是一個(gè)規(guī)定,規(guī)定二進(jìn)制某些位具有特定含義,從而節(jié)省空間,反正處理時(shí)按規(guī)定解碼就好了,也不需要人去看,再怎么難理解也是計(jì)算機(jī)的事兒,懶人改變世界嘛。

尾聲

很喜歡《看見》,看來很多遍,其中一篇是《真實(shí)自有萬鈞之力》,大意講的是拍攝時(shí)要“去雕飾,去匠氣”,真正有價(jià)值的東西會(huì)自然而然的流露出來;研究  sourceMap  原理的過程中很“突?!钡南氲搅诉@句話,很多時(shí)候,我們對(duì)高大上的名詞趨之若鶩,卻很少思考一些像`sourcemap`這類實(shí)際解決痛點(diǎn)的“硬核”知識(shí)點(diǎn)置若罔聞,看到編碼就“累覺不愛”,老實(shí)講,要不是因?yàn)樵O(shè)定了交稿日期,我起碼還得拖個(gè)一兩周,因?yàn)榭淳幋a實(shí)在是太太太煩了,又不常用,但堅(jiān)持下來后才發(fā)現(xiàn)很多諸如`base64`、`ASCII`之類的知識(shí)點(diǎn)串聯(lián)成線,“通了”,大概很多小伙伴都或多或少有過這種類似頓悟的感覺,其實(shí)就算前端出了名的新知識(shí)點(diǎn)層出不窮,我們還是可以看到很多通用的、沉在冰山之下的“道”,而這,則需要靜下來去思考。

涉及到的工具

  • 處理 vql 的 npm 包地址

  • base64vlq 在線轉(zhuǎn)換

感謝各位的閱讀,以上就是“什么是sourcemap”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)什么是sourcemap這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)
推薦閱讀:
  1. 什么是PHP
  2. 什么是python

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI