国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

你用 webpack 1.x 輸出的 hash 靠譜不?

sunny5541 / 2506人閱讀

摘要:舉例這個(gè)方案還有些小缺點(diǎn),就是用模塊文件路徑作為哈希輸入還不是百分百完美,如果文件名改了,那么模塊就不穩(wěn)定了。其實(shí),可以用模塊文件內(nèi)容作為哈希輸入,考慮到效率問(wèn)題,權(quán)衡之下還是用路徑好了。

來(lái)自 http://zhenyong.site/2016/10/...

使用 webpack 構(gòu)建輸出文件時(shí),通常會(huì)給文件名加上 hash,該 hash 值根據(jù)文件內(nèi)容計(jì)算得到,只要文件內(nèi)容不變,hash 就不變,于是就可以利用瀏覽器緩存來(lái)節(jié)省下載流量??墒?webpack 提供的 hash 似乎不那么靠譜...

本文只圍繞如何保證 webpack 1.x 在 生產(chǎn)發(fā)布階段 輸出穩(wěn)定的 hash 值展開(kāi)討論,如果對(duì) webpack 還沒(méi)了解的,可以戳 webpack。

本文 基于 webpack 1.x 的背景展開(kāi)討論,畢竟有些問(wèn)題在 webpack 2 已經(jīng)得到解決。為了方便描述問(wèn)題,文中展示的代碼、配置可能很挫,也許不是工程最佳實(shí)踐,請(qǐng)輕拍。

懶得看文章的可以考慮直接讀插件源碼 zhenyong/webpack-stable-module-id-and-hash

目標(biāo)

除了 html 文件以外,其他靜態(tài)資源文件名都帶上哈希值,根據(jù)文件本身的內(nèi)容計(jì)算得到,保證文件沒(méi)變化,則構(gòu)建后的文件名跟上次一樣。

webpack 提供的 hash [hash]

假設(shè)文件目錄長(zhǎng)這樣:

/src
  |- pageA.js (入口1)
  |- pageB.js (入口2)

使用 webpack 配置:

entry: {
    pageA: "./src/pageA.js",
    pageB: "./src/pageB.js",
},
output: {
    path: __dirname + "/build",
    // [hash:4] 表示截取 [hash] 前四位
    filename: "[name].[hash:4].js"
},

首次構(gòu)建輸出:

pageA.c56c.js  1.47 kB       0  [emitted]  pageA
pageB.c56c.js  1.47 kB       1  [emitted]  pageB

再次構(gòu)建輸出:

pageA.c56c.js  1.47 kB       0  [emitted]  pageA
pageB.c56c.js  1.47 kB       1  [emitted]  pageB

hash 值是穩(wěn)定的呀,是不是就可以了呢?且慢!

根據(jù) Configuration · webpack/docs Wiki :

[hash] is replaced by the hash of the compilation.

意譯:

[hash] 是根據(jù)一個(gè) compilation 對(duì)象計(jì)算得出的哈希值,如果 compilation 對(duì)象的信息不變,則 [hash] 不變

結(jié)合 how to write a plugin 提到:

A compilation object represents a single build of versioned assets. While running Webpack development middleware, a new compilation will be created each time a file change is detected, thus generating a new set of compiled assets. A compilation surfaces information about the present state of module resources, compiled assets, changed files, and watched dependencies.

意譯:

compilation 對(duì)象代表對(duì)某個(gè)版本進(jìn)行一次編譯構(gòu)建的過(guò)程,如果在開(kāi)發(fā)模式下(例如用 --watch 檢測(cè)變化,實(shí)時(shí)編譯),則每次內(nèi)容變化時(shí)會(huì)新建一個(gè) complidation,包含了構(gòu)建所需的上下文信息(構(gòu)建器配置、文件、文件依賴(lài))。

我們來(lái)動(dòng)一下 pageA.js,再次構(gòu)建:

pageA.e6a9.js  1.48 kB       0  [emitted]  pageA
pageB.e6a9.js  1.47 kB       1  [emitted]  pageB

發(fā)現(xiàn) hash 變了,并且所有文件的 hash 值總是一樣,這似乎就跟文檔描述的一致,只要構(gòu)建過(guò)程依賴(lài)的任何資源(代碼)發(fā)生變化,compilation 的信息就會(huì)跟上一次不一樣了。

那是不是肯定說(shuō),源碼不變的話,hash 值就一定穩(wěn)定呢?也不是的,我們改一下 webpack 配置:

entry: {
    pageA: "./src/pageA.js",
    // 不再構(gòu)建入口 pageB
    // pageB: "./src/pageB.js",
},

再次構(gòu)建:

pageA.1f01.js  1.48 kB       0  [emitted]  pageA

compilation 的信息還包括構(gòu)建上下文,所以,移除入口或者換個(gè)loader 都會(huì)引起 hash 改變。

[hash] 的缺點(diǎn)很明顯,不是根據(jù)內(nèi)容來(lái)計(jì)算哈希,但是 hash 值是"穩(wěn)定的",用這種方案能保證『每次上線,瀏覽器訪問(wèn)到的靜態(tài)資源都是新的(url 變了)』

你接受用 [hash] 嗎,我是接受不了?于是我們看 webpack 提供的另一種根據(jù)內(nèi)容計(jì)算 hash 的配置。

[chunkhash]

[chunkhash] is replaced by the hash of the chunk.

意譯:

[chunkhash] 根據(jù) chunk 的內(nèi)容計(jì)算得到。(chunk 可以理解成一個(gè)輸出文件,其中可能包含多個(gè) js 模塊)

我們改下配置:

entry: {
    pageA: "./src/pageA.js",
    pageB: "./src/pageB.js",
},
output: {
    path: __dirname + "/build",
    filename: "[name].[chunkhash:4].js",
},

構(gòu)建試試:

pageA.f308.js  1.48 kB       0  [emitted]  pageA
pageB.53a9.js  1.47 kB       1  [emitted]  pageB

動(dòng)下 pageA.js 再構(gòu)建:

pageA.16d6.js  1.48 kB       0  [emitted]  pageA
pageB.53a9.js  1.47 kB       1  [emitted]  pageB

發(fā)現(xiàn)只有 pageA 的 hash 變了,似乎 [chunkhash] 就能解決問(wèn)題了?且慢!

我們目前的代碼沒(méi)涉及到 css,先加點(diǎn) css 文件依賴(lài):

/src
  |- pageA.js
  |- pageA.css

//pageA.js
require("./a.css");

給 webpack 配置 css 文件的 loader,并且抽取所有樣式輸出到一個(gè)文件

module: {
    loaders: [{
        test: /.css$/,
        loader: ExtractTextPlugin.extract("style-loader", "css-loader")
    }],
},
plugins: [
    // 這里的 contenthash 是 ExtractTextPlugin 根據(jù)抽取輸出的文件內(nèi)容計(jì)算得到
    new ExtractTextPlugin("[name].[contenthash:4].css")
],

構(gòu)建:

pageA.ab4b.js    1.6 kB       0  [emitted]  pageA
pageA.b9bc.css  36 bytes       0  [emitted]  pageA

改一下樣式,那么樣式的 hash 肯定會(huì)變的,那 pageA.js 的 hash 變不變呢?

答案是『變了』:

pageA.0482.js    1.6 kB       0  [emitted]  pageA
pageA.c61a.css  31 bytes       0  [emitted]  pageA

記得之前說(shuō) webpack 的 [chunkhash] 是根據(jù) chunk 的內(nèi)容計(jì)算的,而 pageA.js 這個(gè) chunk 的輸出在 webpack 看來(lái)是包括 css 文件的,只不過(guò)被你抽取出來(lái)罷了,所以你改 css 也就改了這個(gè) chunk 的內(nèi)容,這體驗(yàn)很不好吧,怎么讓 css 不影響 js 的 hash 呢?

自定義 chunkhash

源碼 webpack/Compilation.js:

...
this.applyPlugins("chunk-hash", chunk, chunkHash);
chunk.hash = chunkHash.digest(hashDigest);
...

通過(guò)這段代碼可以發(fā)現(xiàn),通過(guò)在 "chunk-hash" "鉤子" 中替換掉 chunk 的 digest 方法,就可以自定義 chunk.hash 了。

查看文檔 how to write a plugin 了解怎么寫(xiě)插件來(lái)注冊(cè)一個(gè)鉤子方法:

plugins: [
        ...
        new ContentHashPlugin() // 添加插件(生產(chǎn)發(fā)布階段使用)
    ],
};

// 插件函數(shù)
function ContentHashPlugin() {}
// webpack 會(huì)執(zhí)行插件函數(shù)的 apply 方法
ContentHashPlugin.prototype.apply = function(compiler) {
    compiler.plugin("compilation", function(compilation) {
        compilation.plugin("chunk-hash", function(chunk, chunkHash) {
            // 這里注冊(cè)了之前說(shuō)到的 "chunk-hash" 鉤子
            chunk.digest = function () {
                return "這就是自定義的 hash 值";
            }
        });
    });
};

那么這個(gè) hash 值如何計(jì)算好呢?

可以將 chunk 所依賴(lài)的各個(gè)模塊 (單個(gè)源碼文件) 的內(nèi)容拼接后計(jì)算一個(gè) md5 作為 hash 值,當(dāng)然需要對(duì)所有文件排序后再拼接:

var crypto = require("crypto");

var md5Cache = {}

function md5(content) {
    if (!md5Cache[content]) {
        md5Cache[content] = crypto.createHash("md5") //
            .update(content, "utf-8").digest("hex")
    }
    return md5Cache[content];
}

function ContentHashPlugin() {}

ContentHashPlugin.prototype.apply = function(compiler) {
    var context = compiler.options.context;

    function getModFilePath(mod) {
        // 獲取形如 "./src/pageA.css" 這樣的路徑
        // libIdent 方法會(huì)處理好不同平臺(tái)的路徑分隔符問(wèn)題
        return mod.libIdent({
            context: context
        });
    }

    // 根據(jù)模塊對(duì)應(yīng)的文件路徑排序
    //(可以根據(jù)模塊ID,但是暫時(shí)不靠譜,后面會(huì)講)
    function compareMod(modA, modB) {
        var modAPath = getModFilePath(modA);
        var modBPath = getModFilePath(modB);
        return modAPath > modBPath ? 1 : modAPath < modBPath ? -1 : 0;
    }

    // 獲取模塊源碼,開(kāi)發(fā)階段別用
    function getModSrc(mod) {
        return mod._source && mod._source._value || "";
    }

    compiler.plugin("compilation", function(compilation) {
        compilation.plugin("chunk-hash", function(chunk, chunkHash) {
            var source = chunk.modules.sort(compareMod).map(getModSrc).join("");
            chunkHash.digest = function() {
                return md5(source);
            };
        });
    });
};

module.exports = ContentHashPlugin;

此時(shí),pageA.css 修改之后,再也不會(huì)影響 pageA.js 的 hash 值。

另外要注意,ExtractTextPlugin 會(huì)把 pageA.css 的內(nèi)容抽取之后,替換該模塊的內(nèi)容 mod._source._value 為:

// removed by extract-text-webpack-plugin

由于每一個(gè) css 模塊都對(duì)應(yīng)這段內(nèi)容,所以不會(huì)影響效果。

erm0l0v/webpack-md5-hash 插件也是為了解決類(lèi)似問(wèn)題,但是它其中的『排序』算法是基于模塊的 id,而模塊的 id 理論上是不穩(wěn)定的,接下來(lái)我們就討論不穩(wěn)定的模塊 ID 帶來(lái)的坑。

模塊 ID 的坑

我們簡(jiǎn)單的把每個(gè)文件理解為一個(gè)模塊(module),在 webpack 處理模塊依賴(lài)關(guān)系時(shí),會(huì)給每個(gè)模塊定義一個(gè) ID,查看 webpack/Compilation.js 發(fā)現(xiàn),webpack 根據(jù)收集 module 的順序給每個(gè)模塊分配遞增數(shù)字作為 ID,至于『收集的 module 順序』,在你開(kāi)發(fā)生涯里,這玩意絕對(duì)是不穩(wěn)定!不穩(wěn)定的!

Module ID 不穩(wěn)定怎么了

我們的文件結(jié)構(gòu)現(xiàn)在長(zhǎng)這樣:

/src
    |- pageA.js
    |- pageB.js
    |- a.js
    |- b.js
    |- c.js

pageA.js

require("./a.js") // a.js
require("./b.js") // b.js
var a = "this is pageA";

pageB.js

require("./b.js") //  b.js"
require("./c.js") // c.js
var b = "this is pageB";

更新配置,把引用達(dá)到 2 次的模塊抽取出來(lái):

  output: {
        chunkFilename: "[id].[chunkhash:4].bundle.js",
    ...
plugins: [
    new webpack.optimize.CommonsChunkPlugin({
        name: "commons",
        minChunks: 2,
        chunks: ["pageA", "pageB"],
    }),
    ...

build build build:

  pageA.1cda.js  262 bytes       0  [emitted]  pageA
  pageB.0752.js  280 bytes       1  [emitted]  pageB
commons.14bf.js    3.64 kB       2  [emitted]  commons

觀察 pageB.0752.js,有一段:

__webpack_require__(2) //  b.js"
__webpack_require__(3) // c.js
var b = "this is pageB";

從上面看出,webpack 構(gòu)建時(shí)給 b.js 的模塊 ID 為 2

這時(shí),我們改一下 pageA.js

// 移除對(duì) a.js 的依賴(lài)
// require("./a.js") // a.js
require("./b.js") // b.js
var a = "this is pageA";

build build build :

  pageA.a945.js  200 bytes       0  [emitted]  pageA
  pageB.0752.js  271 bytes       1  [emitted]  pageB
commons.14bf.js    3.65 kB       2  [emitted]  commons

嗯! 只有 pageA.js 的 hash 變了,挺合理合理,我們進(jìn)去 pageB.0752.js 看看

    __webpack_require__(1) //  b.js"
    __webpack_require__(2) // c.js
    var b = "this is pageB";

看出來(lái)了沒(méi)!這次構(gòu)建,webpack 給 b.js 的 ID 是 1

我們 pageB.js 的 hash 沒(méi)變,因?yàn)楸澈笠蕾?lài)的模塊內(nèi)容 (b.js、c.js) 沒(méi)有變呀,但是此時(shí) pageB.0752.js 的內(nèi)容確實(shí)變了,如果你用 CDN 上傳這個(gè)文件,也許會(huì)傳不上去,因?yàn)槲募笮『兔Q(chēng)一模一樣,就是這個(gè)不穩(wěn)定的模塊 ID 給坑的!

怎么解決呢?

第一念頭:把原來(lái)計(jì)算 hash 的方式改一下,就那構(gòu)建輸出后的文件內(nèi)容來(lái)計(jì)算?

細(xì)想: 不要,明明 pageB 這一次就不用重新上傳的,浪費(fèi)。

比較優(yōu)雅的思路就是:讓模塊 ID 給我穩(wěn)定下來(lái)!??!

給我穩(wěn)定的 Module ID webpack 1 的官方方案

webpack 文檔提供了幾種方案

OccurrenceOrderPlugin

這個(gè)插件根據(jù) module 被引用的次數(shù)(被 entry 引用、被 chunk 引用)來(lái)排序分配 ID,如果你的整個(gè)應(yīng)用的文件依賴(lài)是沒(méi)太多變化,那么模塊 ID 就穩(wěn)定,但是誰(shuí)能保證呢?

recordsPath 配置

>Store/Load compiler state from/to a json file. This will result in persistent ids of modules and chunks.

會(huì)記錄每一次打包的模塊的"文件處理路徑"使用的 ID,下次打包同樣的模塊直接使用記錄中的 ID:
  "node_modules/style-loader/index.js!node_modules/css-loader/index.js!src/b.css": 9,

這就要求每個(gè)人都得提交這份文件了,港真,我覺(jué)得體驗(yàn)很差咯。

另外一旦你修改文件名,或者是增減 loader,原來(lái)的路徑就無(wú)效了,從而再次入坑!

DllPlugin 和 DllReferencePlugin

原理就是在你打包源碼前,你得新建一個(gè)構(gòu)建配置用 [DllPlugin](https://github.com/webpack/webpack/tree/master/examples/dll) 多帶帶打包生成一份模塊文件路徑對(duì)應(yīng)的 ID 記錄,然后在你的原來(lái)配置使用 [DllReferencePlugin](https://github.com/webpack/webpack/tree/master/examples/dll-user) 引用這份記錄,跟 recordsPath 大同小異,但是更高效和穩(wěn)定,但是這個(gè)額外的構(gòu)建,我覺(jué)得不夠優(yōu)雅,至于能快多少呢,我目前還不在意這個(gè)速度,另外還是得提交多一份記錄文件。

webpack 2 的思路

webpack/HashedModuleIdsPlugin.js at master · webpack/webpack

webpack/NamedModulesPlugin.js at master · webpack/webpack

以上兩個(gè)插件的思路都是用模塊對(duì)應(yīng)的文件路徑直接作為模塊 ID,而不是 webpack 1 中的默認(rèn)使用數(shù)字,另外 webpack 1 不接受非數(shù)字作為 模塊 ID。

我們的思路

把模塊對(duì)應(yīng)的文件路徑通過(guò)一個(gè)哈希計(jì)算映射為數(shù)字,用這個(gè)全局唯一的數(shù)字作為 ID 就解決了,妥妥的!

參考:

webpack/Compilation.js 中暴露的 before-module-ids 鉤子

webpack/HashedModuleIdsPlugin.js

給出 webpack 1.x 中的解決方案:

...

xx.prototype.apply = function(compiler) {

  function hexToNum(str) {
    str = str.toUpperCase();
    var code = ""
    for (var i = 0; i < str.length; i++) {
      var c = str.charCodeAt(i) + "";
      if ((c + "").length < 2) {
        c = "0" + c
      }
      code += c
    }
    return parseInt(code, 10);
  }

  var usedIds = {};

  function genModuleId(module) {
    var modulePath = module.libIdent({
      context: compiler.options.context
    });
    var id = md5(modulePath);
    var len = 4;
    while (usedIds[id.substr(0, len)]) {
      len++;
    }
    id = id.substr(0, len);
    return hexToNum(id)
  }

  compiler.plugin("compilation", function(compilation) {
    compilation.plugin("before-module-ids", function(modules) {
      modules.forEach(function(module) {
        if (module.libIdent && module.id === null) {
          module.id = genModuleId(module);
          usedIds[module.id] = true;
        }
      });
    });
  });
};
...

注冊(cè)鉤子的思路跟之前的 content hash 插件差不多,獲取到模塊文件路徑后,通過(guò) md5 計(jì)算輸出 16 進(jìn)制的字符串([0-9A-E]),再把字符串的字符逐個(gè)轉(zhuǎn)為 ascii 形式的整數(shù),由于 16 進(jìn)制字符串只會(huì)包含 [0-9A-E],所以保證單個(gè)字符轉(zhuǎn)化的整數(shù)是兩位就能保證這個(gè)算法是有效的。

舉例:

path = "/node_module/xxx"
md5Hash = md5(path) // => A3E...
nul = hexToNum(md5Hash) // => 650369 

這個(gè)方案還有些小缺點(diǎn),就是用模塊文件路徑作為哈希輸入還不是百分百完美,如果文件名改了,那么模塊 ID 就 "不穩(wěn)定了"。其實(shí),可以用模塊文件內(nèi)容作為哈希輸入,考慮到效率問(wèn)題,權(quán)衡之下還是用路徑好了。

總結(jié)

為了保證 webpack 1.x 生產(chǎn)階段的文件 hash 值能夠完美跟文件內(nèi)容一一映射,查閱了大量信息,根據(jù)目前 github 上討論的解決方案算是大體解決了問(wèn)題,但是還不夠優(yōu)雅和完美,于是借鑒 webpack 2 的思路加上一點(diǎn)小技巧,比較優(yōu)雅地解決了這個(gè)問(wèn)題。

插件放在 Github: zhenyong/webpack-stable-module-id-and-hash『有用的話給個(gè) star 嘛 O(∩_∩)O』

參考資料

Vendor chunkhash changes when app code changes · Issue #1315 · webpack/webpack

Vendor chunkhash changes when app code changes · Issue #1315 · webpack/webpack

Webpack中hash與chunkhash的區(qū)別,以及js與css的hash指紋解耦方案 - zhoujunpeng - 博客園

webpack使用優(yōu)化 | Web前端 騰訊AlloyTeam Blog | 愿景: 成為地球卓越的Web團(tuán)隊(duì)!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/61830.html

相關(guān)文章

  • 手把手教你用es6+vue2+webpack2+vue-router2搭建個(gè)人blog

    摘要:更新日志更新完成靜態(tài)頁(yè)面原型修復(fù)使用的正確姿勢(shì)更新添加靜態(tài)頁(yè)面更新添加使用方法請(qǐng)戳我主要作用就是在你開(kāi)發(fā)環(huán)節(jié)在后端同學(xué)還未開(kāi)發(fā)完成的情況下,提供一個(gè)。 底下評(píng)論說(shuō)是標(biāo)題黨,或者是光扔個(gè)github地址上來(lái)的同學(xué)我就不說(shuō)什么了。你們有看看倉(cāng)庫(kù)的提交記錄么?我還沒(méi)有吃撐到開(kāi)個(gè)倉(cāng)庫(kù)去騙star.我的出發(fā)點(diǎn)就是每天更新一部分代碼,教大家用我所提到的技術(shù)棧搭建一個(gè)blog,我的出發(fā)點(diǎn)就是這么簡(jiǎn)單...

    weapon 評(píng)論0 收藏0
  • 前端臨床手札——webpack構(gòu)建逐步解構(gòu)(下)

    摘要:續(xù)前端臨床手札構(gòu)建逐步解構(gòu)上工作流程案例最近添加了雪碧圖功能,并把替換成的,詳細(xì)可以看分支構(gòu)建生產(chǎn)上一篇說(shuō)完了本地測(cè)試和是如何工作,接下來(lái)分析構(gòu)建生產(chǎn)模式下配置如何配置和每個(gè)模塊干了什么。 續(xù) 前端臨床手札——webpack構(gòu)建逐步解構(gòu)(上) 工作流程 showImg(https://segmentfault.com/img/bVCXjo?w=793&h=410); 案例:multip...

    TerryCai 評(píng)論0 收藏0
  • 容器內(nèi)應(yīng)用日志收集方案

    摘要:容器化應(yīng)用日志收集挑戰(zhàn)應(yīng)用日志的收集分析和監(jiān)控是日常運(yùn)維工作重要的部分,妥善地處理應(yīng)用日志收集往往是應(yīng)用容器化重要的一個(gè)課題。日志來(lái)源識(shí)別采用統(tǒng)一應(yīng)用日志收集方案,日志分散在很多不同容器的相互隔離的環(huán)境中,需要解決日志的來(lái)源識(shí)別問(wèn)題。 容器化應(yīng)用日志收集挑戰(zhàn) 應(yīng)用日志的收集、分析和監(jiān)控是日常運(yùn)維工作重要的部分,妥善地處理應(yīng)用日志收集往往是應(yīng)用容器化重要的一個(gè)課題。 Docker處理日志...

    ormsf 評(píng)論0 收藏0
  • webpack4 開(kāi)發(fā)環(huán)境配置說(shuō)明

    摘要:的開(kāi)發(fā)環(huán)境配置說(shuō)明完整的的配置地址開(kāi)發(fā)環(huán)境的搭建,總體而言就比較輕松,因?yàn)橛脩?hù)就是開(kāi)發(fā)者們。的做法是在的字段配置類(lèi)似這樣這樣配置后,當(dāng)運(yùn)行時(shí),在里通過(guò)可以取到值以來(lái)做判斷就可以啦。 webpack4 的開(kāi)發(fā)環(huán)境配置說(shuō)明 完整的webpack4的配置clone地址: https://github.com/ziwei3749/... 開(kāi)發(fā)環(huán)境的搭建,總體而言就比較輕松,因?yàn)橛脩?hù)就是開(kāi)發(fā)者們...

    fancyLuo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<