說實話我剛開始看 webpack 文檔的時候,對這 3 個名詞云里霧里的,感覺他們都在說打包文件,但是一會兒 chunk 一會兒 bundle 的,逐漸就迷失在細節里了,所以我們要跳出來,從宏觀的角度來看這幾個名詞。
webpack 官網對 chunk 和 bundle 做出了解釋[3],說實話太抽象了,我這里舉個例子,給大家形象化地解釋一下。
首先我們在 src 目錄下寫我們的業務代碼,引入 index.js、utils.js、common.js 和 index.css 這 4 個文件,目錄結構如下:
src/
├── index.css
├── index.html # 這個是 HTML 模板代碼
├── index.js
├── common.js
└── utils.js
index.css 寫一點兒簡單的樣式:
body {
background-color: red;
}
utils.js 文件寫個求平方的工具函數:
export function square(x) {
return x * x;
}
common.js 文件寫個 log 工具函數:
module.exports = {
log: (msg) => {
console.log('hello ', msg)
}
}
index.js 文件做一些簡單的修改,引入 css 文件和 common.js:
import './index.css';
const { log } = require('./common');
log('webpack');
webpack 的配置如下:
{
entry: {
index: "../src/index.js",
utils: '../src/utils.js',
},
output: {
filename: "[name].bundle.js", // 輸出 index.js 和 utils.js
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // 創建一個 link 標簽
'css-loader', // css-loader 負責解析 CSS 代碼, 處理 CSS 中的依賴
],
},
]
}
plugins: [
// 用 MiniCssExtractPlugin 抽離出 css 文件,以 link 標簽的形式引入樣式文件
new MiniCssExtractPlugin({
filename: 'index.bundle.css' // 輸出的 css 文件名為 index.css
}),
]
}
我們運行一下 webpack,看一下打包的結果:
我們可以看出,index.css 和 common.js 在 index.js 中被引入,打包生成的 index.bundle.css 和 index.bundle.js 都屬于 chunk 0,utils.js 因為是獨立打包的,它生成的 utils.bundle.js 屬于 chunk 1。
感覺還有些繞?我做了一張圖,你肯定一看就懂:
看這個圖就很明白了:
一般來說一個 chunk 對應一個 bundle,比如上圖中的 utils.js -> chunks 1 -> utils.bundle.js;但也有例外,比如說上圖中,我就用 MiniCssExtractPlugin 從 chunks 0 中抽離出了 index.bundle.css 文件。
module,chunk 和 bundle 其實就是同一份邏輯代碼在不同轉換場景下的取了三個名字:
我們直接寫出來的是 module,webpack 處理時是 chunk,最后生成瀏覽器可以直接運行的 bundle。
filename 是一個很常見的配置,就是對應于 entry 里面的輸入文件,經過webpack 打包后輸出文件的文件名。比如說經過下面的配置,生成出來的文件名為 index.min.js。
{
entry: {
index: "../src/index.js"
},
output: {
filename: "[name].min.js", // index.min.js
}
}
chunkFilename 指未被列在 entry 中,卻又需要被打包出來的 chunk 文件的名稱。一般來說,這個 chunk 文件指的就是要懶加載的代碼。
比如說我們業務代碼中寫了一份懶加載 lodash 的代碼:
// 文件:index.js
// 創建一個 button
let btn = document.createElement("button");
btn.innerHTML = "click me";
document.body.appendChild(btn);
// 異步加載代碼
async function getAsyncComponent() {
var element = document.createElement('div');
const { default: _ } = await import('lodash');
element.innerHTML = _.join(['Hello!', 'dynamic', 'imports', 'async'], ' ');
return element;
}
// 點擊 button 時,懶加載 lodash,在網頁上顯示 Hello! dynamic imports async
btn.addEventListener('click', () => {
getAsyncComponent().then(component => {
document.body.appendChild(component);
})
})
我們的 webpack 不做任何配置,還是原來的配置代碼:
{
entry: {
index: "../src/index.js"
},
output: {
filename: "[name].min.js", // index.min.js
}
}
這時候的打包結果如下:
這個 1.min.js 就是異步加載的 chunk 文件。文檔[4]里這么解釋:
“
output.chunkFilename 默認使用 [id].js 或從 output.filename 中推斷出的值([name] 會被預先替換為 [id] 或 [id].)
文檔寫得太抽象,我們不如結合上面的例子來看:
output.filename 的輸出文件名是 [name].min.js,[name] 根據 entry 的配置推斷為 index,所以輸出為 index.min.js;
由于 output.chunkFilename 沒有顯示指定,就會把 [name] 替換為 chunk 文件的 id號,這里文件的 id 號是 1,所以文件名就是 1.min.js。
如果我們顯式配置 chunkFilename,就會按配置的名字生成文件:
{
entry: {
index: "../src/index.js"
},
output: {
filename: "[name].min.js", // index.min.js
chunkFilename: 'bundle.js', // bundle.js
}
}
filename 指列在 entry 中,打包后輸出的文件的名稱。
chunkFilename 指未列在 entry 中,卻又需要被打包出來的文件的名稱。
這幾個名詞其實都是 webpack 魔法注釋(magic comments)[5]里的,文檔中說了 6 個配置,配置都可以組合起來用。我們說說最常用的三個配置。
前面舉了個異步加載 lodash 的例子,我們最后把 output.chunkFilename 寫死成 bundle.js。在我們的業務代碼中,不可能只異步加載一個文件,所以寫死肯定是不行的,但是寫成 [name].bundle.js 時,打包的文件又是意義不明、辨識度不高的 chunk id。
{
entry: {
index: "../src/index.js"
},
output: {
filename: "[name].min.js", // index.min.js
chunkFilename: '[name].bundle.js', // 1.bundle.js,chunk id 為 1,辨識度不高
}
}
這時候 webpackChunkName 就可以派上用場了。我們可以在 import 文件時,在 import里以注釋的形式為 chunk 文件取別名:
async function getAsyncComponent() {
var element = document.createElement('div');
// 在 import 的括號里 加注釋 /* webpackChunkName: "lodash" */ ,為引入的文件取別名
const { default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash');
element.innerHTML = _.join(['Hello!', 'dynamic', 'imports', 'async'], ' ');
return element;
}
這時候打包生成的文件是這樣的:
現在問題來了,lodash 是我們取的名字,按道理來說應該生成 lodash.bundle.js 啊,前面的 vendors~ 是什么玩意?
其實 webpack 懶加載是用內置的一個插件 SplitChunksPlugin[6] 實現的,這個插件里面有些默認配置項[7],比如說 automaticNameDelimiter,默認的分割符就是 ~,所以最后的文件名才會出現這個符號,這塊兒內容我就不引申了,感興趣的同學可以自己研究一下。
這兩個配置一個叫預拉?。≒refetch),一個叫預加載(Preload),兩者有些細微的不同,我們先說說 webpackPreload。
在上面的懶加載代碼里,我們是點擊按鈕時,才會觸發異步加載 lodash 的動作,這時候會動態的生成一個 script 標簽,加載到 head 頭里:
如果我們 import 的時候添加 webpackPrefetch:
...
const { default: _ } = await import(/* webpackChunkName: "lodash" */ /* webpackPrefetch: true */ 'lodash');
...
就會以 <link rel="prefetch" as="script"> 的形式預拉取 lodash 代碼:
這個異步加載的代碼不需要手動點擊 button 觸發,webpack 會在父 chunk 完成加載后,閑時加載 lodash 文件。
webpackPreload 是預加載當前導航下可能需要資源,他和 webpackPrefetch 的主要區別是:
webpackChunkName 是為預加載的文件取別名,webpackPrefetch 會在瀏覽器閑置下載文件,webpackPreload 會在父 chunk 加載時并行下載文件。
首先來個背景介紹,哈希一般是結合 CDN 緩存來使用的。如果文件內容改變的話,那么對應文件哈希值也會改變,對應的 HTML 引用的 URL 地址也會改變,觸發 CDN 服務器從源服務器上拉取對應數據,進而更新本地緩存。
hash 計算是跟整個項目的構建相關,我們做一個簡單的 demo。
沿用案例 1 的 demo 代碼,文件目錄如下:
src/
├── index.css
├── index.html
├── index.js
└── utils.js
webpack 的核心配置如下(省略了一些 module 配置信息):
{
entry: {
index: "../src/index.js",
utils: '../src/utils.js',
},
output: {
filename: "[name].[hash].js", // 改為 hash
},
......
plugins: [
new MiniCssExtractPlugin({
filename: 'index.[hash].css' // 改為 hash
}),
]
}
生成的文件名如下:
我們可以發現,生成文件的 hash 和項目的構建 hash 都是一模一樣的。
因為 hash 是項目構建的哈希值,項目中如果有些變動,hash 一定會變,比如說我改動了 utils.js 的代碼,index.js 里的代碼雖然沒有改變,但是大家都是用的同一份 hash。hash 一變,緩存一定失效了,這樣子是沒辦法實現 CDN 和瀏覽器緩存的。
chunkhash 就是解決這個問題的,它根據不同的入口文件(Entry)進行依賴文件解析、構建對應的 chunk,生成對應的哈希值。
我們再舉個例子,我們對 utils.js 里文件進行改動:
export function square(x) {
return x * x;
}
// 增加 cube() 求立方函數
export function cube(x) {
return x * x * x;
}
然后把 webpack 里的所有 hash 改為 chunkhash:
{
entry: {
index: "../src/index.js",
utils: '../src/utils.js',
},
output: {
filename: "[name].[chunkhash].js", // 改為 chunkhash
},
......
plugins: [
new MiniCssExtractPlugin({
filename: 'index.[chunkhash].css' // // 改為 chunkhash
}),
]
}
構建結果如下:
我們可以看出,chunk 0 的 hash 都是一樣的,chunk 1 的 hash 和上面的不一樣。
假設我又把 utils.js 里的 cube() 函數去掉,再打包:
對比可以發現,只有 chunk 1 的 hash 發生變化,chunk 0 的 hash 還是原來的。
我們更近一步,index.js 和 index.css 同為一個 chunk,如果 index.js 內容發生變化,但是 index.css 沒有變化,打包后他們的 hash 都發生變化,這對 css 文件來說是一種浪費。如何解決這個問題呢?
contenthash 將根據資源內容創建出唯一 hash,也就是說文件內容不變,hash 就不變。
我們修改一下 webpack 的配置:
{
entry: {
index: "../src/index.js",
utils: '../src/utils.js',
},
output: {
filename: "[name].[chunkhash].js",
},
......
plugins: [
new MiniCssExtractPlugin({
filename: 'index.[contenthash].css' // 這里改為 contenthash
}),
]
}
我們對 index.js 文件做了 3 次修改(就是改了改 log 函數的輸出內容,過于簡單就先不寫了),然后分別構建,結果截圖如下:
我們可以發現,css 文件的 hash 都沒有發生改變。
hash 計算與整個項目的構建相關;
chunkhash 計算與同一 chunk 內容相關;
contenthash 計算與文件內容本身相關。
sourse-map ,里面都有個 map 了,肯定是映射的意思。sourse-map 就是一份源碼和轉換后代碼的映射文件。具體的原理內容較多,感興趣的同學可以自行搜索,我這里就不多言了。
我們先從官網上看看 sourse-map 有多少種類型:
emmmm,13 種,告辭。
如果再仔細看一下,就發現這 13 種大部分都是 eval、cheap、inline 和 module這 4 個詞排列組合的,我做了個簡單的表格,比官網上直白多了:
參數參數解釋eval打包后的模塊都使用 eval() 執行,行映射可能不準;不產生獨立的 map 文件cheapmap 映射只顯示行不顯示列,忽略源自 loader 的 source mapinline映射文件以 base64 格式編碼,加載 bundle 文件最后,不產生獨立的 map 文件module增加對 loader source map 和第三方模塊的映射
還不明白?可以看看 demo。
我們對 webpack 做一些配置,devtool 是專門配置 source-map 的。
......
{
devtool: 'source-map',
}
......
index.js 文件為了簡便,我們只寫一行代碼,為了得出報錯信息,我們故意拼錯:
console.lg('hello source-map !') // log 寫成 lg
下面我們試一試常見的幾個配置:
source-map 是最大而全的,會生成獨立 map 文件:
注意下圖光標的位置,source-map 會顯示報錯的行列信息:
cheap,就是廉價的意思,它不會產生列映射,相應的體積會小很多,我們和 sourse-map 的打包結果比一下,只有原來的 1/4 。
eval-source-map 會以 eval() 函數打包運行模塊,不產生獨立的 map 文件,會顯示報錯的行列信息:
// index.bundle.js 文件
!function(e) {
// ......
// 省略不重要的代碼
// ......
}([function(module, exports) {
eval("console.lg('hello source-map !');//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi4vc3JjL2luZGV4Mi5qcz9mNmJjIl0sIm5hbWVzIjpbImNvbnNvbGUiLCJsZyJdLCJtYXBwaW5ncyI6IkFBQUFBLE9BQU8sQ0FBQ0MsRUFBUixDQUFXLG9CQUFYIiwiZmlsZSI6IjAuanMiLCJzb3VyY2VzQ29udGVudCI6WyJjb25zb2xlLmxnKCdoZWxsbyBzb3VyY2UtbWFwICEnKSJdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///0\n")
}
]);
映射文件以 base64 格式編碼,加在 bundle 文件最后,不產生獨立的 map 文件。加入 map 文件后,我們可以明顯地看到包體積變大了;
// index.bundle.js 文件
!function(e) {
}([function(e, t) {
console.lg("hello source-map !")
}
]);
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4uL3NyYy9pbmRleDIuanMiXSwibmFtZXMiOlsiaW5zdGFsbGVkTW9kdWxlcyIsIl9fd2VicGFja19yZXF1aXJ......
// base64 太長了,我刪了一部分,領會精神
上面的幾個例子都是演示,結合[官網推薦](https://webpack.docschina.org/configuration/devtool/#%E5%AF%B9%E4%BA%8E%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83[8])和實際經驗,常用的配置其實是這幾個:
1.source-map
大而全,啥都有,就因為啥都有可能會讓 webpack 構建時間變長,看情況使用。
2.cheap-module-eval-source-map
這個一般是開發環境(dev)推薦使用,在構建速度報錯提醒上做了比較好的均衡。
3.cheap-module-source-map
一般來說,生產環境是不配 source-map 的,如果想捕捉線上的代碼報錯,我們可以用這個
般地,URL和URI比較難以區分。接下來,本文以區分URL和URI為引子,詳細介紹URL的用法,JavaScript重文·也許你該知道瀏覽器輸入 URL 后發生了什么?
URI是Uniform Resource Identifier的縮寫,稱為統一資源標識符。URI是一個通用的概念,由兩個主要的子集URL和URN構成,URL是通過描述資源的位置來標識資源的,而URN則是通過名字來識別資源的,與它們當前所處位置無關
URL是Uniform Resource Locator的縮寫,稱為統一資源定位符。URL正是使用web瀏覽器等訪問web頁面時需要輸入的網頁地址
URL是一種強有力的工具。但URL并不完美。它表示的是實際的地址,而不是準確的名字。這種方案的缺點在于如果資源被移走了,URL也就不再有效了。那時,它就無法對對象進行定位了
如果有了對象的準確名稱,則不論其位于何處都可以找到這個對象。就像人一樣,只要給定了資源的名稱和其他一些情況,無論資源移到何處,都能夠追蹤到它。為了應對這個問題,因特網工程任務組(Internet Engineering Task Force, IETF) 已經對URN的新標準做了一段時間的研究了。無論對象搬移到什么地方,URN都能為對象提供一個穩定的名稱
但是,從URL轉換成URN是一項巨大的工程,支持URN需要進行很多改動——標準主體的一致性,對各種HTTP應用程序的修改等。所以,還要等待更合適的時機才能進行這種轉換
URL語法建立在由下面9部分構成的通用格式上。其中,URL最重要的3個部分是方案(scheme)、主機(host)和路徑(path)
<scheme>://<user>:<password>@<host>:<port>/<path>:<params>?<query>#<frag>
【方案】
方案實際上是規定如何訪問指定資源的主要標識符,它會告訴負責解析URL的應用程序應該使用什么協議
方案組件必須以一個字母符號開始,由第一個“:”符號將其與URL的其余部分分隔開來。方案名是大小寫無關的,因此URL“http://www.hardware.com”和“HTTP://www.hardware.com” 是等價的
常見的方案如下
1、HTTP
HTTP是一種超文本傳輸協議方案,除了沒有用戶名和密碼之外,與通用的URL格式相符。如果省略了端口,就默認為80
基本格式:
http://<host>:<port>/<path>?<query>#<frag>
示例:
http://www.hardware.com/index.html http://www.hardware.com:80/index.html
2、https
方案https與方案http是一對。唯一的區別在于方案https使用了網景的SSL, SSL為HTTP連接提供了端到端的加密機制。其語法與HTTP的語法相同,默認端口為443
基本格式:
https://<host>:<port>/<path>?<query>#<frag>
示例:
https://www.hardware.com/secure.html
3、Mailto
Mailto URL指向的是E-mail地址。由于E-mail的行為與其他方案都有所不同(它并不指向任何可以直接訪問的對象),所以mailto URL的格式與標準URL的格式也有所不同
示例:
mailto:joe@hardware.com
4、ftp
文件傳輸協議URL可以用來從FTP服務器上下載或向其上傳文件,并獲取FTP服務器上的目錄結構內容的列表
在Web和URL出現之前,FTP就已經存在了。Web應用程序將FTP作為一種數據訪問方案使用
基本格式:
ftp://<user>:<password>@<host>:<port>/<path>;<params>
示例:
ftp://anonymous:joe%40hardware.com@prep.ai.mit.edu:21/pub/gnu/
5、rtsp,rtspu
RTSP URL是可以通過實時流傳輸協議(Real Time Streaming Protocol)解析的音/視頻媒體資源的標識符
方案rtspu中的u表示它是使用UDP協議來獲取資源的
基本格式:
rtsp://<user>:<password>@<host>:<port>/<path> rtspu://<user>:<password>@<host>:<port>/<path>
示例:
rtsp://www.hardware.com:554/interview/cto_video
6、file
方案file表示一臺指定主機(通過本地磁盤、網絡文件系統或其他一些文件共享系統)上可直接訪問的文件。各字段都遵循通用格式。如果省略了主機名,就默認為正在使用URL的本地主機
基本格式:
file ://<host>/<path>
示例:
file://OFFICE-FS/policies/casual-fridays.doc
7、telnet
方案telnet用于訪問交互式業務。它表示的不是對象自身,而是可通過telnet協議訪問的交互式應用程序(資源)
基本格式:
telnet://<user>:<password>@<host>:<port>/
示例:
telnet://slurp:webhound@joes-hardware.com:23/
[注意]除了以上常見的7種方案之外,如果要查看全部的URI方案列表,請移步至https://www.w3.org/Addressing/schemes.html
【主機和端口】
要想在因特網上找到資源,應用程序要知道是哪臺機器裝載了資源,以及在那臺機器的什么地方可以找到能對目標資源進行訪問的服務器。URL的主機和端口組件提供了這兩組信息
主機組件標識了因特網上能夠訪問資源的宿主機器??梢杂蒙鲜鲋鳈C名(www.hardware.com)或者IP地址來表示主機名
[注意]IP地址可以是192.168.1.1這類IPv4地址名,還可以是[0:0:0:0:0:0:0:1]這樣用括號括起來的IPv6地址名
比如,下面兩個URL就指向同一個資源——第一個URL通過主機名,第二個通過IP地址指向服務器:
http://www.hardware.com:80/index.html http://161.58.228.45:80/index.html
端口組件標識了服務器正在監聽的網絡端口。對下層使用了TCP協議的HTTP來說,默認端口號為80
【用戶名和密碼】
很多服務器都要求輸入用戶名和密碼才會允許用戶訪問數據。FTP服務器就是這樣一個常見的實例
ftp://ftp.prep.ai.mit.edu/pub/gnu ftp://anonymous@ftp.prep.ai.mit.edu/pub/gnu ftp://anonymous:my_passwd@ftp.prep.ai.mit.edu/pub/gnu http://joe:joespasswd@www.joes-hardware.com/sales_info.txt
第一個例子沒有用戶或密碼組件,只有標準的方案、主機和路徑。如果某應用程序使用的URL方案要求輸入用戶名和密碼,比如FTP,但用戶沒有提供,它通常會插入一個默認的用戶名和密碼。比如,如果向瀏覽器提供一個FTP URL,但沒有指定用戶名和密碼,它就會插入anonymous(匿名用戶)作為你的用戶名,并發送一個默認的密碼(IE會發送IEUser)
第二個例子顯示了一個指定為anonymous的用戶名。這個用戶名與主機組件組合在一起,看起來就像E-mail地址一樣。字符將用戶和密碼組件與URL的其余部分分隔開來
在第三個例子中,指定了用戶名(anonymous)和密碼(my_passwd),兩者之間由字符“:”分隔
【路徑】
URL的路徑組件說明了資源位于服務器的什么地方。路徑通常很像一個分級的文件系統路徑
http://www.hardware.com:80/seasonal/index-fall.html
這個URL中的路徑為/seasonal/index-fall.html,很像UNIX文件系統中的文件系統路徑。路徑是服務器定位資源時所需的信息。可以用字符“/”將HTTP URL的路徑組件劃分成一些路徑段(path segment),每個路徑段都有自己的參數(param)組件
【參數】
對很多方案來說,只有簡單的主機名和到達對象的路徑是不夠的。除了服務器正在監聽的端口,以及是否能夠通過用戶名和密碼訪問資源外,很多協議都還需要更多的信息才能工作
負責解析URL的應用程序需要這些協議參數來訪問資源。否則,另一端的服務器可能就不會為請求提供服務,或者更糟糕的是,提供錯誤的服務。比如,像FTP這樣的協議,有兩種傳輸模式:二進制和文本形式??隙ú幌M晕谋拘问絹韨魉投M制圖片,這樣的話,二進制圖片可能會變得一團糟
為了向應用程序提供它們所需的輸入參數,以便正確地與服務器進行交互,URL中有一個參數組件。這個組件就是URL中的名值對列表,由字符“;”將其與URL的其余部分(以及各名值對)分隔開來。它們為應用程序提供了訪問資源所需的所有附加信息。比如:
ftp://prep.ai.mit.edu/pub/gnu;type=d
在這個例子中,有一個參數type=d,參數名為type,值為d
如前所述,HTTP URL的路徑組件可以分成若干路徑段。每段都可以有自己的參數。比如:
http://www.hardware.com/hammers;sale=false/index.html;graphics=true
這個例子就有兩個路徑段,hammers和index.html。hammers路徑段有參數sale,其值為false。index.html段有參數graphics,其值為true
【查詢字符串】
很多資源,比如數據庫服務,都是可以通過提問題或進行査詢來縮小所請求資源類型范圍的。假設數據庫中維護著一個未售貨物的清單,并可以對淸單進行査詢,以判斷產品是否有貨,那就可以用下列URL來査詢Web數據庫網關,看看編號為12731、顏色為blue、尺寸為large的條目是否有貨:
http://www.hardware.com/inventory-check.cgi?item=12731&color=blue&size=large
這個URL的大部分都與我們見過的其他URL類似。只有問號(?)右邊的內容是新出現的。這部分被稱為查詢(query)組件。URL的査詢組件和標識網關資源的URL路徑組件一起被發送給網關資源
除了有些不合規則的字符需要特別處理之外,對査詢組件的格式沒什么要求。按照常規,很多網關都希望査詢字符串以一系列“名/值”對的形式出現,名值對之間用字符“&”分隔
【片段】
有些資源類型,比如HTML,除了資源級之外,還可以做進一步的劃分。比如,對一個帶有章節的大型文本文檔來說,資源的URL會指向整個文本文檔,但理想的情況是,能夠指定資源中的那些章節
為了引用部分資源或資源的一個片段,URL支持使用片段(frag)組件來表示一個資源內部的片段。比如,URL可以指向HTML文檔中一個特定的圖片或小節
片段掛在URL的右手邊,最前面有一個字符“#”。比如:
http://www.hardware.com/tools.html#drills
在這個例子中,片段drills引用了Web服務器上頁面/tools.html中的一個部分。這部分的名字叫做drills
HTTP服務器通常只處理整個對象,而不是對象的片段,客戶端不能將片段傳送給服務器。瀏覽器從服務器獲得了整個資源之后,會根據片段來顯示感興趣的那部分資源
字符
URL的設計者們認識到有時人們可能會希望URL中包含除通用的安全字母表之外的二進制數據或字符。因此,需要有一種轉義機制,能夠將不安全的字符編碼為安全字符,再進行傳輸
人們設計了一種編碼機制,用來在URL中表示各種不安全的字符。這種編碼機制就是通過一種“轉義”表示法來表示不安全字符的,這種轉義表示法包含一個百分號(%),后面跟著兩個表示字符ASCII碼的十六進制數
下面是一些例子
在URL中,有幾個字符被保留起來,有著特殊的含義。有些字符不在定義的US- ASCII可打印字符集中。還有些字符會與某些因特網網關和協議產生混淆,因此不贊成使用
下面列出了保留及受限的字符
【encodeURI()】
encodeURI()函數把字符串作為URI進行編碼,實際上enchodeURI()函數只把參數中的空格編碼為%20,其余特殊字符均不會轉換
encodeURI()的不編碼字符有82個:
! # $ & ' ( ) * + , - . / : ;=? @ _ ~ 0-9 a-z A-Z
使用方式
//'http://www.w3school.com.cn<br />' console.log(encodeURI("http://www.w3school.com.cn")+ "<br />") //'http://www.w3school.com.cn/My%20first/' console.log(encodeURI("http://www.w3school.com.cn/My first/")) //',/?:@&=+$#' console.log(encodeURI(",/?:@&=+$#"))
[注意]encodeURI()可以編碼中文
//'%E6%B5%8B%E8%AF%95' console.log(encodeURI('測試'));
【decodeURI()】
decodeURI()函數可對encodeURI()函數編碼過的URI進行解碼。實際上,decodeURI()僅僅會把%20轉換為空格顯示
//"http://www.w3school.com.cn/My first/" console.log(decodeURI("http://www.w3school.com.cn/My%20first/"));
【encodeURIComponent()】
encodeURIComponent()函數可把字符串作為URI組件進行編碼。該方法主要對;/?:@&=+$,#等這些用于分隔URI組件的字符以及中文進行編碼
encodeURIComponent不編碼字符有71個:
!, ',(,),*,-,.,_,~,0-9,a-z,A-Z
由于此方法對:/都進行了編碼,所以不能用它來對網址進行編碼,而適合對URI中的參數進行編碼
[注意]encodeURIComponent()可以編碼中文
var uri="http://www.wrox.com/illegal value.htm#start"; //'http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start' console.log(encodeURIComponent(uri)); //'%E6%B5%8B%E8%AF%95' console.log(encodeURIComponent('測試'));
【decodeURIComponent()】
decodeURIComponent()函數可對encodeURIComponent()函數編碼的URI進行解碼
var uri='http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start'; //'http://www.wrox.com/illegal value.htm#start' console.log(decodeURIComponent(uri));
【escape()】
escape()函數對字符串進行編碼,將字符的unicode編碼轉化為16進制序列
ES3中反對escape()的使用,并建議用encodeURI和encodeURIComponent代替,不過escape()依然被廣泛的用于cookie的編碼,因為escape()恰好編碼了cookie中的非法字符并且對路徑中常出現的“/”不進行編碼
escape()的不編碼字符有69個:
* + - . / @ _ 0-9 a-z A-Z
[注意]escape()可以編碼中文
var uri="http://www.wrox.com/illegal value.htm#start"; //'http%3A//www.wrox.com/illegal%20value.htm%23start' console.log(escape(uri)); //%u6D4B%u8BD5 console.log(escape('測試'));
【unescape()】
unescape()函數用于對escape()函數編碼的URI進行解碼
//http://www.wrox.com/illegal value.htm#start console.log(unescape('http%3A//www.wrox.com/illegal%20value.htm%23start')); //'測試' console.log(unescape('%u6D4B%u8BD5'));
鏈接文章:
https://www.cnblogs.com/xiaohuochai/p/6144157.html
https://www.cnblogs.com/xiaohuochai/p/6144157.html
https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_URL
JS文件添加右鍵菜單,一鍵完成JS混淆加密
將“JS混淆加密”集成到鼠標右鍵菜單
Windows一鍵JS混淆加密:功能集成到鼠標右鍵菜單
目標:將“JS混淆加密”功能集成到鼠標右鍵菜單,一鍵點擊完成JavaScript代碼混淆加密。
原因:為什么實現這個功能,有什么好處?
答:方便、易用。JavaScript程序員經常需要將代碼進行混淆加密。
操作過程,通常情況下,需要將代碼復制或提交到網站或軟件中,以使用JShaman為例,需要復制代碼到JShaman網站,完成JS混淆加密,再把代碼粘貼回來。
JShaman網站不需要注冊、不需要登錄,打開網站、復制粘貼就可使用。雖然已經很方便,但集成到鼠標右鍵菜單可以更方便。
效果展示:
實現方法:
環境需求:Node.JS
1、NodeJS腳本文件準備
實現此功能,需要用NodeJS運行一個腳本JS文件,以便調用JShaman WebApi接口,進行JavaScript混淆加密。
JS代碼如下,將此文件保存為obfuscate.js,放于任一目錄下:
/**
* 調用JShaman.com WebAPI接口 實現JavaScript混淆加密
*/
/**
* 配置部分
*/
//JShaman.com VIP碼,免費使用設為"free",如已購買VIP碼,在此修改
const vip_code="free";
//混淆加密參數配置,免費使用時無需配置
//參數詳細說明請參見JShaman官網,設為"true"啟用功能、設為"false"不啟用
const config={
//壓縮代碼
compact: "true",
//混淆全局變量名和函數名
renameGlobalFunctionVariable: "false",
//平展控制流
controlFlowFlattening: "true",
//僵尸代碼植入
deadCodeInjection: "false",
//字符串陣列化
stringArray: "true",
//陣列字符串加密
stringArrayEncoding: "false",
//禁用命令行輸出
disableConsoleOutput: "false",
//反瀏覽器調試
debugProtection: "false",
//時間限定
time_range: "false",
//時間限定起始時間、結束時間,時間限定啟用時此2參數生效
time_start: "20240118",
time_end: "20240118",
//域名鎖定
domainLock: [],
//保留關鍵字
reservedNames: [],
}
/**
* 以下代碼實現向JShaman.com WebApi發送請求完成JavaScript混淆加密,無需修改
*/
const fs=require("fs");
const readline=require("readline");
const request=require("request");
//獲取命令行參數中的文件路徑
//獲取右鍵菜單調用的文件路徑
const filePath=process.argv[2];
if(!filePath) {
console.error("未選中文件");
process.exit(1);
}
//確保文件存在
if(!fs.existsSync(filePath)) {
console.error(`文件 ${filePath} 不存在`);
process.exit(1);
}
console.log(`正在處理文件:${filePath}`);
//從文件中獲取JavaScript代碼
var javascript_code=fs.readFileSync(filePath,"utf8").toString();
//Post請求配置
var options={
url: "https://www.jshaman.com:4430/submit_js_code/",
method: "POST",
json: true,
body: {
//JavaScript代碼
"js_code": javascript_code,
//JShaman VIP碼
"vip_code": vip_code,
}
};
//使用free為VIP碼、免費調用JShaman WebAPI接口時,不能配置參數,僅可實現較低強度代碼保護
//如果購買了JShaman的VIP碼,則可啟用配置,實現高強度JavaScript混淆加密
if(options.body.vip_code !="free"){
//混淆加密參數
options.body.config=config;
}
console.log("正在向JShaman.com提交混淆加密請求...")
//發送請求到JShaman服務器,進行JavaScript混淆加密
request(options, function(error, response, body) {
if (!error && response.statusCode===200) {
//輸出返回消息
console.log(body.message);
//返回狀態值為0是成功標識
if(body.status==0){
const rl=readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("是否覆蓋原文件?y為是,n則在同目錄下另存。(y/n) ", (answer)=> {
if (answer.toLowerCase()==="y") {
//輸入y,覆蓋原文件
fs.writeFileSync(filePath, body.content.toString());
console.log("文件已覆蓋。");
} else {
var obfuscated_file=filePath + ".obfuscated.js";
fs.writeFileSync(obfuscated_file, body.content.toString());
console.log("混淆加密后的Js文件:", obfuscated_file);
}
rl.close();
});
}
} else {
console.error("向JShaman.com發送請求時出現錯誤:", error);
}
});
//按下任意鍵退出程序
process.on("beforeExit", (code)=> {
const rl=readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log("按下任意鍵退出程序...");
rl.on("line", (input)=> {
rl.close();
process.exit(0);
});
});
說明:此JS腳本默認調用JShaman提供的免費Web API接口。JShaman提供有免費、商業兩種接口。如您已獲得JShaman VIP 碼,可修改上述代碼中的VIP碼、使用商業接口,能使配置參數生效、獲得更好的JS混淆加密效果。
2、注冊表修改
修改注冊表是為給JS文件添加右鍵菜單,以便在右鍵點擊.js文件時菜單中顯示“混淆加密”功能。
將下面的代碼保存為right_click_menu.reg:
Windows Registry Editor Version 5.00
; 為 .js 文件類型定義新的上下文菜單項
[HKEY_CLASSES_ROOT\SystemFileAssociations\.js\Shell\JavaScript Obfuscate]
"MuiVerb"="@shell32.dll,-30525"
"Icon"="imageres.dll,-102"
"Position"="Bottom"
; 定義點擊菜單項時要執行的命令
[HKEY_CLASSES_ROOT\SystemFileAssociations\.js\Shell\JavaScript Obfuscate\command]
@="\"C:\\Program Files\\nodejs12\\node.exe\" \"C:\\Users\\WangLiwen\\Desktop\\JShaman_JavaScript_Obfuscator\\obfuscate.js\" \"%1\""
說明:“JavaScript Obfuscate”是菜單中的顯示的文字內容,可修改。
保存之后,雙擊將內容導入注冊表:
僅此兩步,已經大功告成,可以使用了。
JS文件混淆加密測試:
測試,加密一個JS文件,如上圖所示。
使用感受:
如此進行“JS文件混淆加密”,非常方便、又快又好!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。