磨人的Webpack Hash

這是一篇廢話(huà)連篇的文章波势。

從接觸Webpack以來(lái),自己是做內(nèi)部系統(tǒng)為主,每次拿起chunkhash就是干清寇,所以對(duì)Webpack的文件編譯并沒(méi)有太深入的研究。直到最近踩了幾個(gè)坑之后护蝶,我才重新梳理了一下Webpack的hash华烟。

為什么要使用hash?

日常開(kāi)發(fā)編譯打包生成靜態(tài)資源文件時(shí),我們總是會(huì)利用文件名帶上hash的方式持灰,保證瀏覽器能夠持久化緩存盔夜。更具體地解釋就是我們希望達(dá)到這樣一個(gè)目的:

相關(guān)代碼沒(méi)有發(fā)生變化時(shí),盡可能地利用瀏覽器緩存堤魁,而不是頻繁地請(qǐng)求靜態(tài)資源服務(wù)器喂链。

Webpack的hash類(lèi)型

說(shuō)hash之前,我們先拋出 Webapck 里面的兩個(gè)概念 chunkmodule妥泉。

image.png

簡(jiǎn)單地來(lái)說(shuō)椭微,一個(gè)或多個(gè)資源(js/css/img)組成module,一個(gè)或多個(gè)module又組成了chunk盲链,其中包括entry chunknormal chunk蝇率。每個(gè)chunk最終生成一個(gè)file,就是我們的靜態(tài)資源文件刽沾。也就是說(shuō)瓢剿,chunk最終都一個(gè)hash

Webpack作為時(shí)下最主流的業(yè)務(wù)代碼編譯打包工具悠轩,內(nèi)置了以下三種hash處理方式:

  • hash
    Using the unique hash generated for every build
  • chunkhash
    Using hashes based on each chunks' content
  • contenthash
    Using hashes generated for extracted content

hash是根據(jù)每次編譯生成间狂,chunkhash則是根據(jù)每個(gè)chunk的內(nèi)容生成,contenthash用來(lái)對(duì)付css等其他資源火架。

由于我們的項(xiàng)目基本上都是多個(gè)entry(入口)鉴象,如果每一次編譯所有的文件都生成一個(gè)全新的hash,就會(huì)造成緩存的大量失效何鸡,這并不是我們期望的纺弊。我們最終想要達(dá)到的效果就是:

每當(dāng)修改一個(gè)module時(shí),只有引用到它的chunk才會(huì)更新對(duì)應(yīng)的 hash骡男。

于是淆游,chunkhash 脫穎而出了。


實(shí)際在使用chunkhash時(shí),由于對(duì)webpack編譯過(guò)程的不了解犹菱, chunkhash并沒(méi)有像我期望的那樣工作拾稳,這也讓我踩坑不少。

接下來(lái)通過(guò)一個(gè)循序漸進(jìn)的例子來(lái)展示chunkhash到底是個(gè)什么玩意兒腊脱。

準(zhǔn)備數(shù)據(jù)

假設(shè)我們有入口文件 entry-a.js entry-b.js entry-c访得,ab 分別依賴(lài) common-a.jscommon-b.js,三個(gè)入口文件都依賴(lài) common-abc.js

// entry-a.js
import ca from './common-a'
import cabc from './common-abc'

ca()
cabc()
console.log('I\'m entry a')

// entry-b.js
import cb from './common-b'
import cabc from './common-abc'

cb()
cabc()
console.log('I\'m entry b')

// entry-c.js
import cabc from './common-abc'

cabc()
console.log('I\'m entry c')

// common-a.js
export default function () {
  console.log('I\'m common a')
}

// common-b.js
export default function () {
  console.log('I\'m common b')
}

// common-abc.js
export default function () {
  console.log('I am common-abc')
}

Webpack 配置如下:

// webpack.config.js
  entry: {
    'entry-a': './src/entry-a.js',
    'entry-b': './src/entry-b.js',
    'entry-c': './src/entry-c.js'
  },
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].js',
  }

編譯結(jié)果如下:

                              Asset      Size  Chunks             Chunk Names
    entry-a.d702a9dfe4bd9fd8d29e.js  1.14 KiB       0  [emitted]  entry-a
    entry-b.e349f63455e20b60f6d5.js  1.14 KiB       1  [emitted]  entry-b
    entry-c.f767774953520bfd7cea.js  1.11 KiB       2  [emitted]  entry-c

[0] ./src/common-abc.js 64 bytes {0} {1} {2} [built]
[1] ./src/entry-c.js 69 bytes {2} [built]
[2] ./src/entry-a.js + 1 modules 171 bytes {0} [built]
    | ./src/entry-a.js 104 bytes [built]
    | ./src/common-a.js 62 bytes [built]
[3] ./src/entry-b.js + 1 modules 171 bytes {1} [built]
    | ./src/entry-b.js 104 bytes [built]
    | ./src/common-b.js 62 bytes [built]

module

  • test1entry-a需要增加一個(gè)依賴(lài)common-a2
// common-a2.js
export default function () {
  console.log('I\'m common a2')
}

編譯結(jié)果

                              Asset      Size  Chunks             Chunk Names
    entry-a.fe41f6501454aaba37de.js  1.17 KiB       0  [emitted]  entry-a
    entry-b.e349f63455e20b60f6d5.js  1.14 KiB       1  [emitted]  entry-b
    entry-c.f767774953520bfd7cea.js  1.11 KiB       2  [emitted]  entry-c

[0] ./src/common-abc.js 64 bytes {0} {1} {2} [built]
[1] ./src/entry-c.js 69 bytes {2} [built]
[2] ./src/entry-a.js + 2 modules 272 bytes {0} [built]
    | ./src/entry-a.js 142 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[3] ./src/entry-b.js + 1 modules 171 bytes {1} [built]
    | ./src/entry-b.js 104 bytes [built]
    | ./src/common-b.js 62 bytes [built]

一切都很順利陕凹,entry-a增加了一個(gè)依賴(lài)悍抑,只有entry-a的 hash 發(fā)生了變化,從d702a9dfe4bd9fd8d29e -> fe41f6501454aaba37de杜耙,entry-bentry-c依然不變搜骡,完美!

王菲有一個(gè)歌叫《暗涌》佑女,我個(gè)人一直非常喜歡浆兰,給大家推薦一下。

上面這個(gè)實(shí)驗(yàn)表面上是很成功珊豹,可到此為止了嗎?實(shí)際上就像暗涌一下榕订,表面平靜店茶,底下卻潮水涌動(dòng)。

為了方便對(duì)比hash的變化劫恒,我簡(jiǎn)單寫(xiě)了個(gè)plugin贩幻,去替代上面那種要對(duì)比兩大坨編譯結(jié)果才能定位到具體是哪個(gè)hash發(fā)生了變化。

// ChunkPlugin.js
...
MyChunkPlugin.prototype.apply = function (compiler) {
  compiler.hooks.thisCompilation.tap('MyChunkPlugin', compilation => {
    compilation.hooks.afterOptimizeChunkAssets.tap('MyChunkPlugin', chunks => {
      const chunkMap = {}
      chunks.forEach(chunk => (chunkMap[chunk.name] = chunk.renderedHash))
      const result = fs.readFileSync('./hash.js', 'utf-8')
      const diff = [];
      if (result) {
        const source = JSON.parse(result);
        Object.keys(chunkMap).forEach(key => {
          if (source[key] && chunkMap[key] !== source[key]) {
            diff.push(`${key}: ${source[key]} -> ${chunkMap[key]} `)
          } else {
            diff.push(`${key}: '' -> ${chunkMap[key]} `)
          }
        })
      }
      fs.writeFile('./hash.js', `${JSON.stringify(chunkMap, null, '\t')}`)
      fs.writeFile('./diff.js', diff.length ? diff.join('\n') : 'nothing changed')
    })
  })
}

重復(fù)上面的操作后生成結(jié)果如下:

 entry-a: d702a9dfe4bd9fd8d29e -> fe41f6501454aaba37de
  • test-2entry-b移除依賴(lài) common-b两嘴,讓entry-b只依賴(lài)于公共的模塊common-abc
// entry-b.js
import cabc from './common-abc'

cabc()
console.log('I\'m entry b')

繼續(xù)編譯:

entry-a: fe41f6501454aaba37de -> 409266c0e175d92e5f40 
entry-b: e349f63455e20b60f6d5 -> 45eed8a58e4742f5c01d 
entry-c: f767774953520bfd7cea -> 3c651c9b9fa129486a53 

很遺憾丛楚,事情并沒(méi)有跟我們想象的那樣進(jìn)行,僅僅是減少了entry-b的一個(gè)依賴(lài)之后憔辫,entry-aentry-chash也發(fā)生了變化趣些。

原因其實(shí)很簡(jiǎn)單,contenthash是根據(jù)計(jì)算的贰您,生成的文件內(nèi)容發(fā)生了變化坏平,計(jì)算出來(lái)的hash也就跟著變了。

那為什么在沒(méi)有改變ac及其依賴(lài)模塊的內(nèi)容時(shí)锦亦,它們最終生成的文件hash也發(fā)生了變化舶替。

  • module id

每一個(gè)入口模塊都會(huì)引入各個(gè)不同的被依賴(lài)模塊,Webpack在編譯文件時(shí)杠园,會(huì)給所有的模塊聲明唯一的id顾瞪,并生成一些函數(shù),幫助入口模塊去找到所有的依賴(lài)。

下面是entry-a在沒(méi)有壓縮混淆下的部分生成代碼:

//...
var _common_a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./common-a */ 1);
var _common_a2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./common-a2 */ 2);
var _common_abc__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./common-abc */ 3);
//...

我們大概可以猜出Webpack為幾個(gè)被依賴(lài)模塊分別生成了 module id 1 2 3 ...

結(jié)合webpack文檔可以發(fā)現(xiàn)默認(rèn)情況下module id 是根據(jù)模塊的調(diào)用順序陈醒,以數(shù)字自增的方式賦值的惕橙。

如何保持module id的穩(wěn)定性?


HashedModuleIdsPlugin是webpack內(nèi)置的一個(gè)適用于生產(chǎn)環(huán)境的插件孵延。它根據(jù)每個(gè)模塊的相對(duì)路徑計(jì)算出一個(gè)四個(gè)字符的hash串吕漂,解決了數(shù)值型id不穩(wěn)定的問(wèn)題。


修改一下webpack配置文件:

// webpack.config.js
// ...
plugins: [
  // ...
  new webpack.HashedModuleIdsPlugin()
]

重復(fù)上一個(gè)實(shí)驗(yàn)尘应,entry-b依賴(lài) common-b

// entry-b.js
import cb from './common-b'
import cabc from './common-abc'
cb()
cabc()
console.log('I\'m entry b')

------------------------------------------------------------
// 編譯結(jié)果
                              Asset      Size  Chunks             Chunk Names
    entry-a.59fcd77ff264f62591d3.js  1.19 KiB       0  [emitted]  entry-a
    entry-b.408073538586b4495dd7.js  1.16 KiB       1  [emitted]  entry-b
    entry-c.1f28d5213db6b69b83ed.js  1.13 KiB       2  [emitted]  entry-c

[F85t] ./src/common-abc.js 64 bytes {0} {1} {2} [built]
[GUDB] ./src/entry-a.js + 2 modules 272 bytes {0} [built]
    | ./src/entry-a.js 142 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[aIzb] ./src/entry-c.js 69 bytes {2} [built]
[grd8] ./src/entry-b.js + 1 modules 171 bytes {1} [built]
    | ./src/entry-b.js 104 bytes [built]
    | ./src/common-b.js 62 bytes [built]

去掉common-b依賴(lài):

//entry-b.js
import cabc from './common-abc'
cabc()
console.log('I\'m entry b')
------------------------------------------------
// 編譯結(jié)果
                              Asset      Size  Chunks             Chunk Names
    entry-a.59fcd77ff264f62591d3.js  1.19 KiB       0  [emitted]  entry-a
    entry-b.a75ec2de235c6595507a.js  1.13 KiB       1  [emitted]  entry-b
    entry-c.1f28d5213db6b69b83ed.js  1.13 KiB       2  [emitted]  entry-c

[F85t] ./src/common-abc.js 64 bytes {0} {1} {2} [built]
[GUDB] ./src/entry-a.js + 2 modules 272 bytes {0} [built]
    | ./src/entry-a.js 142 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[aIzb] ./src/entry-c.js 69 bytes {2} [built]
[grd8] ./src/entry-b.js 69 bytes {1} [built]
// diff
entry-b: 408073538586b4495dd7 -> a75ec2de235c6595507a 

和我們期望的答案一樣;棠(可以重復(fù)幾次實(shí)驗(yàn))

至此,收獲持久化緩存第一招:

HashedModuleIdsPlugin

chunk

繼續(xù)基于上面的實(shí)驗(yàn)

  • test-3 這個(gè)實(shí)驗(yàn)我們分兩步進(jìn)行

1犬钢、給entry-a增加異步加載chunkasync.js

// entry-a.js
import ca from './common-a'
import ca2 from './common-a2'
import cabc from './common-abc'

ca()
ca2()
cabc()

(async function () {
  const asy = await import(/* webpackChunkName: "async" */ './async')
  asy()
})()

console.log('I\'m entry a')

// async.js
export default function () {
  console.log('I am async')
}
---------------------------------------------------------------------------------------------- 
// 編譯結(jié)果
                              Asset       Size  Chunks             Chunk Names
      async.5411b81525bb7e4c771e.js  205 bytes       0  [emitted]  async
    entry-a.a9c2efa137c11a449854.js   9.45 KiB       1  [emitted]  entry-a
    entry-b.5f44a689594f78eb9b62.js   1.13 KiB       2  [emitted]  entry-b
    entry-c.220cbeddf5b77bf44a0d.js   1.13 KiB       3  [emitted]  entry-c

[F85t] ./src/common-abc.js 64 bytes {1} {2} {3} [built]
[GUDB] ./src/entry-a.js + 2 modules 961 bytes {1} [built]
    | ./src/entry-a.js 821 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[TSF4] ./src/async.js 59 bytes {0} [built]
[aIzb] ./src/entry-c.js 69 bytes {3} [built]
[grd8] ./src/entry-b.js 69 bytes {2} [built]
    + 4 hidden modules
// diff.js
async: '' -> 5411b81525bb7e4c771e 
entry-a: 59fcd77ff264f62591d3 -> a9c2efa137c11a449854 
entry-b: a75ec2de235c6595507a -> 5f44a689594f78eb9b62 
entry-c: 1f28d5213db6b69b83ed -> 220cbeddf5b77bf44a0d 

2苍鲜、在這個(gè)基礎(chǔ)上再增加一個(gè)入口文件 entry-a2

// entry-a2.js
export default function () {
  console.log('I\'m entry a2')
}

// webpack.config.js
  entry: {
    'entry-a': './src/entry-a.js',
    'entry-a2': './src/entry-a2.js',
    'entry-b': './src/entry-b.js',
    'entry-c': './src/entry-c.js',
  },
----------------------------------------------------------------------------------------------
// 編譯結(jié)果:
                               Asset       Size  Chunks             Chunk Names
       async.5411b81525bb7e4c771e.js  205 bytes       0  [emitted]  async
     entry-a.a9c2efa137c11a449854.js   9.45 KiB       1  [emitted]  entry-a
    entry-a2.cbf75fa37ffde273148a.js   1.04 KiB       2  [emitted]  entry-a2
     entry-b.ed39f7105ea4f26b42e3.js   1.13 KiB       3  [emitted]  entry-b
     entry-c.adebd02c1ec23be8edeb.js   1.13 KiB       4  [emitted]  entry-c

[F85t] ./src/common-abc.js 64 bytes {1} {3} {4} [built]
[GUDB] ./src/entry-a.js + 2 modules 961 bytes {1} [built]
    | ./src/entry-a.js 821 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[PV30] ./src/entry-a2.js 62 bytes {2} [built]
[TSF4] ./src/async.js 59 bytes {0} [built]
[aIzb] ./src/entry-c.js 69 bytes {4} [built]
[grd8] ./src/entry-b.js 69 bytes {3} [built]
    + 4 hidden modules
// diff
entry-a2: '' -> cbf75fa37ffde273148a 
entry-b: 5f44a689594f78eb9b62 -> ed39f7105ea4f26b42e3 
entry-c: 220cbeddf5b77bf44a0d -> adebd02c1ec23be8edeb 

本來(lái)我們期望的結(jié)果應(yīng)該是這樣的:

  • entry-a增加一個(gè)異步加載chunkentry-ahash發(fā)生變化玷犹,其他entry保持不變混滔。
  • 增加一個(gè)全新的entry,已有的chunk(入口chunk/異步加載chunk)都應(yīng)該保持不變歹颓。

但上面的實(shí)驗(yàn)得到的答案卻是:

  • 每次增加一個(gè)chunk坯屿,總是有部分毫不相干的chunk受到了影響。

重復(fù)多次上述實(shí)驗(yàn)會(huì)發(fā)現(xiàn)這樣一個(gè)規(guī)律:

chunkmodule一樣巍扛,默認(rèn)以數(shù)字自增的方式為所有chunk分配一個(gè)id领跛,每次增加或減少一個(gè)chunk,排在其后面的chunkid受到了影響撤奸,進(jìn)而其hash也跟著發(fā)生了變化吠昭。

如何保持chunk id的穩(wěn)定性?


namedChunks是webpack的一個(gè)解決這個(gè)問(wèn)題的配置胧瓜,它用chunkname替代了數(shù)字自增的方法為chunk id賦值矢棚,從而讓chunk不受其他chunk id影響。

// webpack.config.js
module.exports = {
  //...
  optimization: {
    namedChunks: true
  }
};

  • test-4namedChunks測(cè)試一下chunk id是否能保持穩(wěn)定

重復(fù)前面的實(shí)驗(yàn) test -3:

// 原始編譯結(jié)果

                              Asset      Size   Chunks             Chunk Names
    entry-a.0864367d249b191a3a0e.js  1.19 KiB  entry-a  [emitted]  entry-a
    entry-b.5c7b3532d418453241f4.js  1.13 KiB  entry-b  [emitted]  entry-b
    entry-c.6887e26445575eff0402.js  1.13 KiB  entry-c  [emitted]  entry-c

[F85t] ./src/common-abc.js 64 bytes {entry-a} {entry-b} {entry-c} [built]
[GUDB] ./src/entry-a.js + 2 modules 272 bytes {entry-a} [built]
    | ./src/entry-a.js 142 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[aIzb] ./src/entry-c.js 69 bytes {entry-c} [built]
[grd8] ./src/entry-b.js 69 bytes {entry-b} [built]

1府喳、給entry-a增加異步加載chunkasync.js

// entry-a.js
import ca from './common-a'
import ca2 from './common-a2'
import cabc from './common-abc'

ca()
ca2()
cabc()

(async function () {
  const asy = await import(/* webpackChunkName: "async" */ './async')
  asy()
})()

console.log('I\'m entry a')

// async.js
export default function () {
  console.log('I am async')
}
---------------------------------------------------------------------------------------------- 
// 編譯結(jié)果
                              Asset       Size   Chunks             Chunk Names
      async.3b06cb8d92816f773b08.js  211 bytes    async  [emitted]  async
    entry-a.f201c1668ae5af4b9b59.js   9.47 KiB  entry-a  [emitted]  entry-a
    entry-b.5c7b3532d418453241f4.js   1.13 KiB  entry-b  [emitted]  entry-b
    entry-c.6887e26445575eff0402.js   1.13 KiB  entry-c  [emitted]  entry-c

[F85t] ./src/common-abc.js 64 bytes {entry-a} {entry-b} {entry-c} [built]
[GUDB] ./src/entry-a.js + 2 modules 961 bytes {entry-a} [built]
    | ./src/entry-a.js 821 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[TSF4] ./src/async.js 59 bytes {async} [built]
[aIzb] ./src/entry-c.js 69 bytes {entry-c} [built]
[grd8] ./src/entry-b.js 69 bytes {entry-b} [built]
    + 4 hidden modules
// diff
async: '' -> 3b06cb8d92816f773b08 
entry-a: 0864367d249b191a3a0e -> f201c1668ae5af4b9b59 

從編譯結(jié)果可以看到蒲肋,增加了async之后,只有引入它的entry-a發(fā)生了hash變化钝满,其他的chunk保持不變肉津。

2、在這個(gè)基礎(chǔ)上再增加一個(gè)入口文件 entry-a2

// entry-a2.js
export default function () {
  console.log('I\'m entry a2')
}

----------------------------------------------------------------------------------------------
// 編譯結(jié)果:
                               Asset       Size    Chunks             Chunk Names
       async.3b06cb8d92816f773b08.js  211 bytes     async  [emitted]  async
     entry-a.f201c1668ae5af4b9b59.js   9.47 KiB   entry-a  [emitted]  entry-a
    entry-a2.820dc92f91bed3570102.js   1.04 KiB  entry-a2  [emitted]  entry-a2
     entry-b.5c7b3532d418453241f4.js   1.13 KiB   entry-b  [emitted]  entry-b
     entry-c.6887e26445575eff0402.js   1.13 KiB   entry-c  [emitted]  entry-c

[F85t] ./src/common-abc.js 64 bytes {entry-a} {entry-b} {entry-c} [built]
[GUDB] ./src/entry-a.js + 2 modules 961 bytes {entry-a} [built]
    | ./src/entry-a.js 821 bytes [built]
    | ./src/common-a.js 62 bytes [built]
    | ./src/common-a2.js 63 bytes [built]
[PV30] ./src/entry-a2.js 62 bytes {entry-a2} [built]
[TSF4] ./src/async.js 59 bytes {async} [built]
[aIzb] ./src/entry-c.js 69 bytes {entry-c} [built]
[grd8] ./src/entry-b.js 69 bytes {entry-b} [built]
    + 4 hidden modules
// diff
entry-a2: '' -> 820dc92f91bed3570102 

這個(gè)編譯結(jié)果依然符合我們的期望舱沧,增加了一個(gè)全新的entry妹沙,已存在的所有chunk都不會(huì)受到影響。

多重復(fù)幾次實(shí)驗(yàn)熟吏,執(zhí)行結(jié)果依然符合期望距糖。

在這里玄窝,收獲持久化緩存第二招:

optimization.namedChunks: true

webpack文檔里其實(shí)對(duì)這個(gè)配置的定義是便于開(kāi)發(fā)模式下調(diào)試,所以在development模式下該配置默認(rèn)是true悍引,而在production下則相反恩脂。這里我其實(shí)是比較費(fèi)解,僅僅是因?yàn)?code>namedChunk生成的chunk id比默認(rèn)的numeric idsize稍大一點(diǎn)趣斤,就降低了chunk id的穩(wěn)定性俩块,但其帶來(lái)的所謂size的精簡(jiǎn)在碩大的工程里簡(jiǎn)直是無(wú)足輕重,感覺(jué)有點(diǎn)舍本逐末浓领。

另外玉凯,在webpack 5之后,namedChunks將會(huì)變成一個(gè)deprecated配置联贩,取而代之的是optimization.chunkIds: named漫仆。

總結(jié)

在一大堆無(wú)聊的實(shí)驗(yàn)之,得到以下結(jié)論

  • 給生成的文件名加入[chunkhash]
  • 使用HashedModuleIdsPluginmodule id保持穩(wěn)定
  • 使用namedChunkschunk id保持穩(wěn)定

webpack優(yōu)化的方式其實(shí)還有很多泪幌,自己動(dòng)手踩坑盲厌,看一下webpack生成后的代碼還有官方文檔,總是能發(fā)現(xiàn)并解決問(wèn)題祸泪。就好比我做完上述實(shí)驗(yàn)吗浩,又發(fā)現(xiàn)了一個(gè)問(wèn)題,等著下次解決吧没隘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末懂扼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子升略,更是在濱河造成了極大的恐慌,老刑警劉巖屡限,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件品嚣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡钧大,警方通過(guò)查閱死者的電腦和手機(jī)翰撑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)啊央,“玉大人眶诈,你說(shuō)我怎么就攤上這事」霞ⅲ” “怎么了逝撬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)乓土。 經(jīng)常有香客問(wèn)我宪潮,道長(zhǎng)溯警,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任狡相,我火速辦了婚禮梯轻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尽棕。我一直安慰自己喳挑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布滔悉。 她就那樣靜靜地躺著伊诵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氧敢。 梳的紋絲不亂的頭發(fā)上日戈,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音孙乖,去河邊找鬼浙炼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛唯袄,可吹牛的內(nèi)容都是我干的弯屈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼恋拷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼资厉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蔬顾,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宴偿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后诀豁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體窄刘,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年舷胜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了娩践。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烹骨,死狀恐怖翻伺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沮焕,我是刑警寧澤吨岭,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站峦树,受9級(jí)特大地震影響未妹,放射性物質(zhì)發(fā)生泄漏簿废。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一络它、第九天 我趴在偏房一處隱蔽的房頂上張望族檬。 院中可真熱鬧,春花似錦化戳、人聲如沸单料。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扫尖。三九已至,卻和暖如春掠廓,著一層夾襖步出監(jiān)牢的瞬間换怖,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工蟀瞧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沉颂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓悦污,卻偏偏與公主長(zhǎng)得像铸屉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子切端,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容