vite學(xué)習(xí)與簡(jiǎn)易實(shí)現(xiàn)

Vite 介紹

Vite 概念
Vite 是一個(gè)面向現(xiàn)代化瀏覽器的一個(gè)更輕瞻惋、更快的 web 應(yīng)用應(yīng)用開(kāi)發(fā)工具
它基于 ECMAScript 標(biāo)準(zhǔn)原生模塊系統(tǒng)(ES Module)實(shí)現(xiàn)的
它的出現(xiàn)是為了解決 Webpack 在開(kāi)發(fā)階段,使用 webpack-dev-server 冷啟動(dòng)時(shí)間過(guò)長(zhǎng)和 Webpack MHR 熱更新反應(yīng)慢的問(wèn)題。

使用 Vite 創(chuàng)建的項(xiàng)目,默認(rèn)就是一個(gè)普通的 Vue3應(yīng)用盐杂,相比于 Vue CLI創(chuàng)建的項(xiàng)目辙纬,會(huì)少了很多文件和依賴。

Vite 的特性

  • 快速冷啟動(dòng)
  • 模塊熱更新
  • 按需編譯
  • 開(kāi)箱即用

Vite 項(xiàng)目依賴

Vite 創(chuàng)建的默認(rèn)項(xiàng)目难捌,開(kāi)發(fā)依賴很少也很簡(jiǎn)單,只包含了:

  • Vite
  • @vue/compiler-sfc(用來(lái)編譯.vue 結(jié)尾的單文件文件)

需要注意的是皂贩,Vite 目前創(chuàng)建的 Vue 項(xiàng)目只支持 3.0 的版本栖榨。在創(chuàng)建項(xiàng)目的時(shí)候,通過(guò)指定不同的模板明刷,也可以創(chuàng)建其他框架的項(xiàng)目婴栽。

Vite 提供的命令

  • vite serve
    工作原理
    用于啟動(dòng)一個(gè)開(kāi)發(fā)的 web 服務(wù)器,在啟動(dòng)服務(wù)器的時(shí)候不需要編譯所有的模塊啟動(dòng)速度非常的快辈末。

我們來(lái)看看下面這張圖:

工作原理.png

在運(yùn)行vite serve 的時(shí)候愚争,不需要打包,直接開(kāi)啟了一個(gè) web 服務(wù)器挤聘。當(dāng)瀏覽器請(qǐng)求服務(wù)器時(shí)轰枝,例如是一個(gè) css,或者是一個(gè)單文件組件组去,這個(gè)時(shí)候在服務(wù)器會(huì)把這個(gè)瀏覽器請(qǐng)求的文件先編譯鞍陨,然后直接把編譯后的結(jié)果返回給瀏覽器。

這里的編譯是在服務(wù)器端从隆,并且诚撵,模塊的處理是在請(qǐng)求到服務(wù)器端處理的缭裆。

我們來(lái)回顧一下,Vue CLI 創(chuàng)建的應(yīng)用


vue cli APP.png

Vue CLI 創(chuàng)建的項(xiàng)目啟動(dòng) web 服務(wù)器用的是 vue-cli-service寿烟,當(dāng)運(yùn)行它的時(shí)候澈驼,它內(nèi)部會(huì)使用 Webpack 去打包所有的模塊(如果模塊很多的情況下,編譯的速度會(huì)很慢)筛武,打包完成后會(huì)將編譯好的模塊存儲(chǔ)到內(nèi)存中缝其,然后啟動(dòng)一個(gè) web 服務(wù)器,瀏覽器請(qǐng)求 web 服務(wù)器徘六,最后才會(huì)從內(nèi)存中把編譯好的內(nèi)容内边,返回到瀏覽器。

Webpack這樣的工具硕噩,它的做法是將所有的模塊提前都編譯打包進(jìn)內(nèi)存里假残,不管模塊是否被執(zhí)行是否被調(diào)用,它會(huì)都打包編譯炉擅,隨著項(xiàng)目越來(lái)越大,打包后的內(nèi)容也會(huì)越來(lái)越大阳惹,打包的速度也會(huì)越來(lái)越慢谍失。

Vite 使用現(xiàn)代化瀏覽器原生支持的 ES Module 模塊化的特性,省略了模塊的打包環(huán)節(jié)莹汤。對(duì)于需要編譯的文件快鱼,例如樣式模塊和單文件組件等,vite 采用了即時(shí)編譯纲岭,也就是說(shuō)當(dāng)加載到這個(gè)文件的時(shí)候抹竹,才會(huì)去服務(wù)端編譯好這個(gè)文件。

所以止潮,這種即時(shí)編譯的好處體現(xiàn)在按需編譯窃判,速度會(huì)更快。

HMR

  • Vite HMR
    立即編譯當(dāng)前所修改的文件
  • Webpack HMR
    會(huì)自動(dòng)以這個(gè)文件為入口重新編譯一次喇闸,所有的涉及到的依賴也會(huì)被加載一次

Vite 默認(rèn)也支持 HMR 模塊熱更新袄琳,相對(duì)于Webpack中的 HMR 效果會(huì)更好,因?yàn)?Webpack 的 HMR 模塊熱跟新會(huì)從你修改的文件開(kāi)始全部在編譯一遍

vite build

  • Rollup
  • Dynamic import
    Polyfill
    Vite創(chuàng)建的項(xiàng)目使用 Vite build 進(jìn)行生產(chǎn)模式的打包燃乍,這個(gè)命令內(nèi)部使用過(guò)的是 Rollup 打包唆樊,最終也是把文件都打包編譯在一起。對(duì)于代碼切割的需求刻蟹,Vite 內(nèi)部采用的是原生的動(dòng)態(tài)導(dǎo)入的方式實(shí)現(xiàn)的逗旁,所以打包的結(jié)果只能支持現(xiàn)代化的瀏覽器(不支持 ie)。不過(guò)相對(duì)應(yīng)的 Polyfill 可以解決

是否還需要打包舆瘪?
隨著Vite 的出現(xiàn)片效,我們需要考慮一個(gè)問(wèn)題仓洼,是否還必要打包應(yīng)用。之前我們使用Webpack 進(jìn)行打包堤舒,會(huì)把所有的模塊都打包進(jìn)bundle.js 中色建,主要有兩個(gè)原因:

  • 瀏覽器環(huán)境對(duì)原生 ES Module 的支持
  • 零零散散的模塊文件會(huì)產(chǎn)生大量的 HTTP 請(qǐng)求

但是,現(xiàn)在目前大部分的瀏覽器都已經(jīng)支持了 ES Module舌缤。并且我們也可以使用 HTTP2 長(zhǎng)鏈接去解決大量的 HTTP 請(qǐng)求箕戳。那是否還需要對(duì)應(yīng)用進(jìn)行打包,取決于你的團(tuán)隊(duì)和項(xiàng)目應(yīng)用的運(yùn)行環(huán)境国撵。

個(gè)人覺(jué)得這以后會(huì)是一個(gè)趨勢(shì)陵吸。

開(kāi)箱即用

  • TypeScript - 內(nèi)置支持
  • less/sass/stylus/postcss - 內(nèi)置支持(需要單獨(dú)安裝)
  • JSX
  • Web Assemby

實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的 vite

接下來(lái),我們來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)易版本的 vite介牙,來(lái)深入理解 vite 的工作原理壮虫,分為以下五個(gè)步驟:

  • 靜態(tài) web 服務(wù)器
  • 修改第三方模塊的路徑
  • 加載第三方模塊
  • 編譯單文件組件
  • HMR(通過(guò) WebSocket 實(shí)現(xiàn),跳過(guò))

靜態(tài) web 服務(wù)器

vite內(nèi)部使用過(guò)的是koa 來(lái)開(kāi)啟靜態(tài)服務(wù)器的环础,這里我們也使用 koa 來(lái)開(kāi)啟一個(gè)靜態(tài)服務(wù)器囚似,把當(dāng)前運(yùn)行的目錄作為靜態(tài)服務(wù)器的根目錄

創(chuàng)建一個(gè)名為 my-vite 的空文件夾,進(jìn)入該文件夾初始化 package.json线得,并且安裝 koakoa-send

package.json 來(lái)配置 bin 字段:

"bin": "index.js"

新建 index.js 文件饶唤,并且在第一行配置 node 的運(yùn)行環(huán)境(因?yàn)槲覀円_(kāi)發(fā)的是一個(gè)基于 node的命令行工具,所以要指定運(yùn)行node 的位置)

#!/usr/bin/env node

接下來(lái)贯钩,基于koa 啟動(dòng)一個(gè) web 靜態(tài)服務(wù)器:

#!/usr/bin/env node
const Koa = require('koa')
const send = require('koa-send')

const app = new Koa()

// 1.開(kāi)啟靜態(tài)文件服務(wù)器
app.use(async (ctx, next) => {
  await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
  next()
})

app.listen(3000)
console.log('Serve running @ http://localhost:3000')

接著募狂,使用 npm link 到全局,然后打開(kāi)一個(gè)使用 vue3 寫(xiě)的項(xiàng)目(可以用 vite 創(chuàng)建一個(gè)默認(rèn)項(xiàng)目)角雷,進(jìn)入命令行終端祸穷,輸入 myvite。如果沒(méi)有報(bào)錯(cuò)的話勺三,會(huì)打印出"Serve running @ http://localhost:3000"這句話雷滚,我們打開(kāi)瀏覽器,打開(kāi)這個(gè)網(wǎng)址檩咱。

不過(guò)是一片空白的揭措,接著我們打開(kāi) F12,會(huì)看到一個(gè)報(bào)錯(cuò)刻蚯,報(bào)錯(cuò)的信息的意思是绊含,解析 vue 模塊的時(shí)候失敗了,使用 import 導(dǎo)入模塊的時(shí)候炊汹,模塊的開(kāi)頭必須是"/", "./", or "../"這三種其中的一個(gè)躬充。

我們來(lái)做一個(gè)對(duì)比,我們把使用vite 創(chuàng)建的項(xiàng)目啟動(dòng)后, vite-cli 創(chuàng)建的項(xiàng)目啟動(dòng)后的 main.js 在瀏覽器響應(yīng)中的區(qū)別

myvite
vite-cli

通過(guò)上面兩幅圖的對(duì)比充甚,你會(huì)發(fā)現(xiàn)以政,vite 它會(huì)處理這個(gè)模塊引入的路徑,它會(huì)加載一個(gè)不存在的路徑@modules伴找,并且請(qǐng)求這個(gè)路徑的 js 文件也是可以請(qǐng)求成功的盈蛮。

這是 vite 創(chuàng)建的項(xiàng)目啟動(dòng)后的 vue.js 的請(qǐng)求,觀察響應(yīng)頭中的 Content-Type字段技矮,他是 application/javascript;所以我們可以通過(guò)這個(gè)類(lèi)型抖誉,在返回的時(shí)候去處理這個(gè)js 中的第三方路徑問(wèn)題。

修改第三方模塊的路徑

通過(guò)上面的觀察和理解衰倦,我們得出一個(gè)思路袒炉,可以把不是"/", "./", or "../"開(kāi)頭的引用,全部替換成“/@modules/”樊零。

我們創(chuàng)建多一個(gè)中間件我磁,用來(lái)做這件事情。

// 2.修改第三方模塊的路徑
app.use(async (ctx, next) => {
  // 判斷瀏覽器請(qǐng)求的文件類(lèi)型驻襟,如果是js文件夺艰,在這里進(jìn)行解析。
  if (ctx.type === 'application/javascript') {
    //將流轉(zhuǎn)化成字符串
    const contents = await streamToString(ctx.body)
    // 在js的import當(dāng)中塑悼,只會(huì)出現(xiàn)以下的幾種情況:
    // 1劲适、import vue from 'vue'
    // 2、import App from '/App.vue'
    // 3厢蒜、import App from './App.vue'
    // 4、import App from '../App.vue'
    // 2烹植、3斑鸦、4這三種情況,現(xiàn)代化瀏覽器都可以識(shí)別草雕,只有第一種情況不能識(shí)別巷屿,這里只處理第一種情況
    // 思路是用正則匹配到 (from ') 或者 是 (from ") 開(kāi)頭,替換成"/@modules/"

    /**
     * 這里進(jìn)行分組的全局匹配
     * 第一個(gè)分組匹配以下內(nèi)容:
     *  from 匹配 from
     *  \s+ 匹配空格
     *  ['"]匹配單引號(hào)或者是雙引號(hào)
     * 第二個(gè)分組匹配以下內(nèi)容:
     *  ?! 不匹配這個(gè)分組的結(jié)果
     *  \.\/ 匹配 ./
     *  \.\.\/ 匹配 ../
     * $1表示第一個(gè)分組的結(jié)果
     */
    ctx.body = contents.replace(
      /(from\s+['"])(?![\.\/\.\.\\/])/g,
      '$1/@modules/'
    )
  }
})

// 將流轉(zhuǎn)化成字符串墩虹,是一個(gè)異步線程嘱巾,返回一個(gè)promise
const streamToString = (stream) =>
  new Promise((resolve, reject) => {
    // 用于存儲(chǔ)讀取到的buffer
    const chunks = []
    //監(jiān)聽(tīng)讀取到buffer,并存儲(chǔ)到chunks數(shù)組中
    stream.on('data', (chunk) => chunks.push(chunk))
    //當(dāng)數(shù)據(jù)讀取完畢之后诫钓,把結(jié)果返回給resolve旬昭,這里需要把讀取到的buffer合并并且轉(zhuǎn)換為字符串
    stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
    //如果讀取buffer失敗,返回reject
    stream.on('error', reject)
  })

加載第三方模塊

現(xiàn)在我們要做的是菌湃,將 /@modules/開(kāi)頭的引用问拘,去 node_modules 中找到并且替換它的返回內(nèi)容。我們需要在創(chuàng)建一個(gè)中間件,這個(gè)中間件需要在創(chuàng)建靜態(tài)服務(wù)器之前被調(diào)用骤坐。

// 3.加載第三方模塊
app.use(async (ctx, next) => {
  // ctx.path --> /@modules/vue
  // 判斷第三方模塊的路徑是否有/@modules/開(kāi)頭
  if (ctx.path.startsWith('/@modules/')) {
    // 對(duì)字符串進(jìn)行截取绪杏,獲取到模塊名稱
    const moduleName = ctx.path.substr(10)
    // 找到該模塊名稱在node_moduls中的package.json路徑
    const pkgPath = path.join(
      process.cwd(),
      'node_modules',
      moduleName,
      'package.json'
    )
    // 通過(guò)require加載當(dāng)前package.json
    const pkg = require(pkgPath)
    // 將內(nèi)容替換成node_modules中的內(nèi)容
    ctx.path = path.join('/node_modules', moduleName, pkg.module)
  }
  // 返回執(zhí)行下一個(gè)中間件
  await next()
})

編寫(xiě)完后,我們需要重新啟動(dòng)一下服務(wù)器纽绍,啟動(dòng)完成后蕾久,我們重新打開(kāi) network網(wǎng)絡(luò)面板,看看 vue 這個(gè)模塊是否被加載了進(jìn)來(lái)拌夏。


我們看到僧著,vue 這個(gè)模塊已經(jīng)被加載進(jìn)來(lái)了。

但是辖佣,我們發(fā)現(xiàn) /@modules/@vue/runtime-dom/@modules/@vue/shared 卻沒(méi)有被加載進(jìn)來(lái)霹抛,并且控制臺(tái)卻報(bào)了兩個(gè)錯(cuò)誤:加載模塊App.vueindex.css失敗

編譯單文件組件

我們先觀察一下,原本的 vite 啟動(dòng)后卷谈,sfc 單文件夾組件的請(qǐng)求是如何處理的杯拐,


編譯單文件.png

我們來(lái)看 app.vue 的響應(yīng)內(nèi)容,它引入了一些組件世蔗,然后把它編譯成一個(gè)選項(xiàng)對(duì)象端逼,然后它又去加載了app.vue 并且在后面加上了一個(gè)參數(shù) type=template,并且解構(gòu)出了一個(gè) render函數(shù)污淋,然后把 render 函數(shù)掛載到選項(xiàng)對(duì)象上顶滩,然后又設(shè)置了兩個(gè)屬性(這兩個(gè)屬性不模擬),最后導(dǎo)出這個(gè)選項(xiàng)對(duì)象寸爆。

從這段代碼我們可以觀察到礁鲁,當(dāng)請(qǐng)求到單文件組件的時(shí)候,服務(wù)器會(huì)來(lái)編譯這個(gè)單文件組件赁豆,并把相對(duì)應(yīng)的結(jié)果返回給瀏覽器仅醇。

我們?cè)趤?lái)編寫(xiě)一個(gè)中間件,在編寫(xiě)中間件的時(shí)候魔种,我們需要安裝一個(gè)模塊 @vue/compiler-sfc并且導(dǎo)入析二,這個(gè)模塊的作用主要是編譯單文件組件的。

代碼如下:

// 4. 處理單文件組件
app.use(async (ctx, next) => {
  // 當(dāng)請(qǐng)求的文件是單文件組件的時(shí)候节预,就是.vue結(jié)尾的時(shí)候
  if (ctx.path.endsWith('.vue')) {
    // 獲取文件內(nèi)容叶摄,它的內(nèi)容是一個(gè)流,需要轉(zhuǎn)換為字符串
    const contents = await streamToString(ctx.body)
    // compilerSFC.parse用來(lái)編譯單文件組件安拟,它返回一個(gè)對(duì)象蛤吓,它有兩個(gè)成員 descriptor、errors
    const { descriptor } = compilerSFC.parse(contents)
    // 最終返回瀏覽器的內(nèi)容
    let code
    // 第一次請(qǐng)求去扣,沒(méi)有參數(shù)的時(shí)候柱衔,就是沒(méi)有帶type的時(shí)候
    if (!ctx.query.type) {
      // 第一次請(qǐng)求樊破,把單文件組件編譯成一個(gè)對(duì)象
      code = descriptor.script.content
      code = code.replace(/export\s+default\s+/g, 'const __script = ')
      code += `
        import { render as __render } from "${ctx.path}?type=template"
        __script.render = __render
        export default __script
      `
    }
    // 第二次請(qǐng)求,參數(shù)中是否有type參數(shù)唆铐,并且是否是template
    else if (ctx.query.type === 'template') {
      // compilerSFC.compileTemplate 編譯模板
      const templateRender = compilerSFC.compileTemplate({
        // 編譯內(nèi)容
        source: descriptor.template.content,
      })
      code = templateRender.code
    }
    // 設(shè)置文件類(lèi)型
    ctx.type = 'application/javascript'
    // 轉(zhuǎn)化成流
    ctx.body = stringToStream(code)
  }
  await next()
})

然后哲戚,重啟一下服務(wù),需要注意的是艾岂,需要把圖片和其他和 js 或者 vue 無(wú)關(guān)的文件都注釋掉顺少,因?yàn)槲覀冞@里只處理了vue 文件。

源碼

#!/usr/bin/env node
const path = require('path')
const { Readable } = require('stream')
const Koa = require('koa')
const send = require('koa-send')
const compilerSFC = require('@vue/compiler-sfc')

const app = new Koa()

// 3.加載第三方模塊
app.use(async (ctx, next) => {
  // ctx.path --> /@modules/vue
  // 判斷第三方模塊的路徑是否有/@modules/開(kāi)頭
  if (ctx.path.startsWith('/@modules/')) {
    // 對(duì)字符串進(jìn)行截取王浴,獲取到模塊名稱
    const moduleName = ctx.path.substr(10)
    // 找到該模塊名稱在node_moduls中的package.json路徑
    const pkgPath = path.join(
      process.cwd(),
      'node_modules',
      moduleName,
      'package.json'
    )
    // 通過(guò)require加載當(dāng)前package.json
    const pkg = require(pkgPath)
    // 將內(nèi)容替換成node_modules中的內(nèi)容
    ctx.path = path.join('/node_modules', moduleName, pkg.module)
  }
  await next()
})

// 1.開(kāi)啟靜態(tài)文件服務(wù)器
app.use(async (ctx, next) => {
  await send(ctx, ctx.path, { root: process.cwd(), index: 'index.html' })
  await next()
})

// 4. 處理單文件組件
app.use(async (ctx, next) => {
  // 當(dāng)請(qǐng)求的文件是單文件組件的時(shí)候脆炎,就是.vue結(jié)尾的時(shí)候
  if (ctx.path.endsWith('.vue')) {
    // 獲取文件內(nèi)容,它的內(nèi)容是一個(gè)流氓辣,需要轉(zhuǎn)換為字符串
    const contents = await streamToString(ctx.body)
    // compilerSFC.parse用來(lái)編譯單文件組件秒裕,它返回一個(gè)對(duì)象,它有兩個(gè)成員 descriptor钞啸、errors
    const { descriptor } = compilerSFC.parse(contents)
    // 最終返回瀏覽器的內(nèi)容
    let code
    // 第一次請(qǐng)求几蜻,沒(méi)有參數(shù)的時(shí)候,就是沒(méi)有帶type的時(shí)候
    if (!ctx.query.type) {
      // 第一次請(qǐng)求体斩,把單文件組件編譯成一個(gè)對(duì)象
      code = descriptor.script.content
      code = code.replace(/export\s+default\s+/g, 'const __script = ')
      code += `
        import { render as __render } from "${ctx.path}?type=template"
        __script.render = __render
        export default __script
      `
    }
    // 第二次請(qǐng)求梭稚,參數(shù)中是否有type參數(shù),并且是否是template
    else if (ctx.query.type === 'template') {
      // compilerSFC.compileTemplate 編譯模板
      const templateRender = compilerSFC.compileTemplate({
        // 編譯內(nèi)容
        source: descriptor.template.content,
      })
      code = templateRender.code
    }
    // 設(shè)置文件類(lèi)型
    ctx.type = 'application/javascript'
    // 轉(zhuǎn)化成流
    ctx.body = stringToStream(code)
  }
  await next()
})

// 2.修改第三方模塊的路徑
app.use(async (ctx, next) => {
  // 判斷瀏覽器請(qǐng)求的文件類(lèi)型絮吵,如果是js文件弧烤,在這里進(jìn)行解析。
  if (ctx.type === 'application/javascript') {
    //將流轉(zhuǎn)化成字符串
    const contents = await streamToString(ctx.body)
    // 在js的import當(dāng)中蹬敲,只會(huì)出現(xiàn)以下的幾種情況:
    // 1暇昂、import vue from 'vue'
    // 2、import App from '/App.vue'
    // 3伴嗡、import App from './App.vue'
    // 4话浇、import App from '../App.vue'
    // 2、3闹究、4這三種情況,現(xiàn)代化瀏覽器都可以識(shí)別食店,只有第一種情況不能識(shí)別渣淤,這里只處理第一種情況
    // 思路是用正則匹配到 (from ') 或者 是 (from ") 開(kāi)頭,替換成"/@modules/"

    /**
     * 這里進(jìn)行分組的全局匹配
     * 第一個(gè)分組匹配以下內(nèi)容:
     *  from 匹配 from
     *  \s+ 匹配空格
     *  ['"]匹配單引號(hào)或者是雙引號(hào)
     * 第二個(gè)分組匹配以下內(nèi)容:
     *  ?! 不匹配這個(gè)分組的結(jié)果
     *  \.\/ 匹配 ./
     *  \.\.\/ 匹配 ../
     * $1表示第一個(gè)分組的結(jié)果
     */
    ctx.body = contents
      .replace(/(from\s+['"])(?![\.\/\.\.\\/])/g, '$1/@modules/')
      .replace(/process\.env\.NODE_ENV/g, '"development"') // 替換process對(duì)象
  }
})

// 將流轉(zhuǎn)化成字符串吉嫩,是一個(gè)異步線程价认,返回一個(gè)promise
const streamToString = (stream) =>
  new Promise((resolve, reject) => {
    // 用于存儲(chǔ)讀取到的buffer
    const chunks = []
    //監(jiān)聽(tīng)讀取到buffer,并存儲(chǔ)到chunks數(shù)組中
    stream.on('data', (chunk) => chunks.push(chunk))
    //當(dāng)數(shù)據(jù)讀取完畢之后自娩,把結(jié)果返回給resolve用踩,這里需要把讀取到的buffer合并并且轉(zhuǎn)換為字符串
    stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')))
    //如果讀取buffer失敗渠退,返回reject
    stream.on('error', reject)
  })

// 將字符串轉(zhuǎn)化成流
const stringToStream = (text) => {
  const stream = new Readable()
  stream.push(text)
  stream.push(null)
  return stream
}

app.listen(3000)
console.log('Serve running @ http://localhost:3000')
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市脐彩,隨后出現(xiàn)的幾起案子碎乃,更是在濱河造成了極大的恐慌,老刑警劉巖惠奸,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梅誓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡佛南,警方通過(guò)查閱死者的電腦和手機(jī)梗掰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嗅回,“玉大人及穗,你說(shuō)我怎么就攤上這事∶嘣兀” “怎么了埂陆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)尘分。 經(jīng)常有香客問(wèn)我猜惋,道長(zhǎng),這世上最難降的妖魔是什么培愁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任著摔,我火速辦了婚禮,結(jié)果婚禮上定续,老公的妹妹穿的比我還像新娘谍咆。我一直安慰自己,他們只是感情好私股,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布摹察。 她就那樣靜靜地躺著,像睡著了一般倡鲸。 火紅的嫁衣襯著肌膚如雪供嚎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天峭状,我揣著相機(jī)與錄音克滴,去河邊找鬼。 笑死优床,一個(gè)胖子當(dāng)著我的面吹牛劝赔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胆敞,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼着帽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼杂伟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起仍翰,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赫粥,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后歉备,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體傅是,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蕾羊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喧笔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡龟再,死狀恐怖书闸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情利凑,我是刑警寧澤浆劲,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站哀澈,受9級(jí)特大地震影響牌借,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜割按,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一膨报、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧适荣,春花似錦现柠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至丈氓,卻和暖如春周循,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背万俗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工鱼鼓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人该编。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像硕淑,于是被迫代替她去往敵國(guó)和親课竣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘉赎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345